mirror of
https://github.com/rickbarrette/redmine_qbo.git
synced 2025-11-09 01:14:23 -05:00
Compare commits
1388 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 086632e804 | |||
| d37ff922fc | |||
| 3483efa100 | |||
| f65eea2820 | |||
| a4111e0a11 | |||
| ebe5373d82 | |||
| 5b8c7d42c5 | |||
| b8fc57d583 | |||
| 7c42197cb1 | |||
| cc0ffce892 | |||
| 0fd2abbec3 | |||
| 215b219a6d | |||
| ea71542d81 | |||
| 5dbf486b50 | |||
| b734125d6b | |||
| 06e6295c6e | |||
| fd383ad9d4 | |||
| 4eb6c533f1 | |||
| 5af7d73768 | |||
| 1d0ae34261 | |||
| 21656b3e14 | |||
| 131976cd71 | |||
| 88c1b9c9a2 | |||
| 5ea9aed3cb | |||
| 41e10d9b0e | |||
| 45859bef3e | |||
| f5c40738dc | |||
| bfa37ee634 | |||
| 787b55f3d7 | |||
| 61f882e98c | |||
| 37db0d3d72 | |||
| 4f2dec3069 | |||
| 35a7c3cfeb | |||
| cbbaf5a95c | |||
| 647923e5e6 | |||
| 70ca4e9964 | |||
| 7fb40ad4a8 | |||
| 36083d23a0 | |||
| 2ec57f2bbf | |||
| 278708e566 | |||
| 23f2b92e8d | |||
| 5d92eeddfb | |||
| 384a8c033c | |||
| 32b12b60f9 | |||
| 93db447239 | |||
| 19a6180e15 | |||
| 3408ee173c | |||
| b817e842dd | |||
| 51c3b8338e | |||
| c6a3edfbc1 | |||
| 21d8d90465 | |||
| 04c0fa57c6 | |||
| f5ad761712 | |||
| 9b80485915 | |||
| 87de865c00 | |||
| 1ea27e8511 | |||
| 8f0ca00b09 | |||
| 859a1d505b | |||
| cd109653a2 | |||
| cab723bbcd | |||
| 3dd712629b | |||
| cdf2603e12 | |||
| 5df9d324bc | |||
| f78c0338b4 | |||
| fe6aa7908f | |||
| aa45338e36 | |||
| 213dca2621 | |||
| fee710d717 | |||
| 65eac58f6c | |||
| b4f5112fc3 | |||
| fa5dcbf9a9 | |||
| e0aebb1c23 | |||
| 6d176acc2b | |||
| 9e9b29fef9 | |||
| 1af846537d | |||
| d6c5daff49 | |||
| 61c76ad80a | |||
| 0d514790fd | |||
| 748d431d35 | |||
| 87b8d99c41 | |||
| a0da53b6cf | |||
| 02d630c631 | |||
| 15b214c800 | |||
| 1b5e185087 | |||
| 102309600e | |||
| 6acc7db91b | |||
| 02898883a8 | |||
| ce02b70bc3 | |||
| d4d4a555f8 | |||
| c2663cd0a0 | |||
| d48609361f | |||
| 70995f6e55 | |||
| 05a0472939 | |||
| cff9f3fde3 | |||
| e24b704571 | |||
| 4d99f54c79 | |||
| e65725c334 | |||
| 4829daab7c | |||
| 260e9f3e4a | |||
| e3ce2445b8 | |||
| 2b333667ed | |||
| 1077cf214c | |||
| f27fdf5274 | |||
| 1dbcca4ca0 | |||
| 558e2359f7 | |||
| f99ef648b3 | |||
| 5e4e3329c8 | |||
| b0a66aba0a | |||
| f2dd500536 | |||
| 7412ac4f91 | |||
| 2acb3efe5a | |||
| 2cc0d06bc5 | |||
| 4070cb7c49 | |||
| fcd196355a | |||
| ea502d5b7b | |||
| 1f33009f89 | |||
| 3509ae9725 | |||
| 49858c45c9 | |||
| b78cd44cc9 | |||
| 39fcd6d4dd | |||
| 8838d36793 | |||
| 63fa94e6f2 | |||
| 17183f9643 | |||
| 667d0bfa97 | |||
| 88a6be0d27 | |||
| c3eaddff97 | |||
| f03adad463 | |||
| bd03e3ac32 | |||
| 299a28a0d2 | |||
| cee8ddced1 | |||
| 738cd21b1f | |||
| b8186e4b52 | |||
| d98a8b8cc4 | |||
| dba6c4b131 | |||
| 118812f16f | |||
| 0b96a1412c | |||
| 29de191d26 | |||
| f86af9ca71 | |||
| d25de7b30f | |||
| 273bd3d6be | |||
| ac446723f1 | |||
| c21bc1333f | |||
| 4e4255995e | |||
| c68b540597 | |||
| 1358871ccc | |||
| 908511f299 | |||
| 6260de21f9 | |||
| e2f276097c | |||
| 205bb67a6a | |||
| 05edafec4c | |||
| 4a073d3a71 | |||
| f2cbf31e17 | |||
| 22b22780ea | |||
| 71cfa28817 | |||
| 8b2d88f80b | |||
| eaf0a57e51 | |||
| 512f5ad7ba | |||
| 8d2351d3f9 | |||
| c5a20c9e7f | |||
| 4a3b663333 | |||
| e43635b5d8 | |||
| 7044377f16 | |||
| 7ced1bf942 | |||
| 7ca3315ce5 | |||
| 2b8c4b4d4d | |||
| a359e8815b | |||
| 01cf82813c | |||
| 625e400c48 | |||
| 56793cee7c | |||
| 3ba5337812 | |||
| 129e3d4821 | |||
| 4d524a7d61 | |||
| 429fb920fb | |||
| 77c7f0b6fe | |||
| 1a043bea76 | |||
| e4d770c272 | |||
| fce3931858 | |||
| 43cdade6e1 | |||
| 4374f9436c | |||
| 7c63c3c816 | |||
| b3f491a60b | |||
| 4adcbba840 | |||
| baccb42455 | |||
| d0842dd803 | |||
| 02aabe6045 | |||
| 0e47f9eb5f | |||
| f1d2d63f20 | |||
| f322f9f7ab | |||
| 6db8b76902 | |||
| 61adce1299 | |||
| daffb3719e | |||
| 1b8626d28f | |||
| b119344fad | |||
| 4381d403d4 | |||
| 26bfaca1d6 | |||
| 0c68d8094a | |||
| 6230175ba5 | |||
| 5dc4dc5637 | |||
| ac15307fb8 | |||
| ec5ce497d8 | |||
| 01fe52157d | |||
| 75737cf2fd | |||
| 7824edf5aa | |||
| 6b70b447a5 | |||
| 5a6b679099 | |||
| 72835dcf65 | |||
| b9e2349983 | |||
| ef13ec7e11 | |||
| 00b40da8c4 | |||
| 2be25adf18 | |||
| 5ab9a777f6 | |||
| 7fbb1d6ba3 | |||
| 786c80609c | |||
| efb554824d | |||
| c615abc896 | |||
| 8ecc3414da | |||
| 505def8d23 | |||
| da155de514 | |||
| 7d727e1ad8 | |||
| 3dcb5155fc | |||
| 4424593e63 | |||
| 8eae838ef8 | |||
| d5e8b4bbc4 | |||
| fc8efa53e9 | |||
| 15ea3aeaa2 | |||
| 35bf300f2d | |||
| 72bf10680f | |||
| bd8706deee | |||
| e8619529d4 | |||
| fd3c8e15e6 | |||
| 166c1d3002 | |||
| 773d60fb23 | |||
| cc46902095 | |||
| acb2628c7a | |||
| e4914590f8 | |||
| e3a8e464ae | |||
| 8a6bb45b6a | |||
| 3decf83a7b | |||
| 1b7b286d1b | |||
| a8804f6704 | |||
| 5d03e261d1 | |||
| 1ae766b8bd | |||
| 119c36569f | |||
| 3be69d5efd | |||
| b55dd99efd | |||
| eff1f97ab2 | |||
| 06050bd139 | |||
| a48840ddfb | |||
| 9b9aabee11 | |||
| 7782627286 | |||
| 41a113dc59 | |||
| b84e249dfb | |||
| 6b45f767a4 | |||
| a34b6a07fc | |||
| 2ce811bbbf | |||
| 02153de8b0 | |||
| 68be20459b | |||
| bbd03cc337 | |||
| 4fc71a93f2 | |||
| 8e7e1908e4 | |||
| 89fba883ef | |||
| 15f317fba1 | |||
| 894ee9abfd | |||
| ca17807117 | |||
| a70ba2f164 | |||
| 78ac97298c | |||
| 72cd349c1b | |||
| 6fc1d27dca | |||
| 525c6b99d6 | |||
| 3eaff0ab30 | |||
| 85b40bc9cf | |||
| 37a2b95447 | |||
| 33feb91713 | |||
| f7357f30ce | |||
| c0ae01018b | |||
| 4353e910c8 | |||
| bef9774c4e | |||
| 863437b1b7 | |||
| 7cfa15910a | |||
| 2154a3d001 | |||
| fdab090a3d | |||
| 3f32b7fef1 | |||
| 14422bc549 | |||
| 6bb66597e8 | |||
| 32b750b545 | |||
| 5fd3141746 | |||
| 2c38361234 | |||
| 81b7b1492d | |||
| 57ef1ac5a1 | |||
| 6597c5a13c | |||
| 8af97072fb | |||
| 48b6df0cef | |||
| 853b7ad804 | |||
| 6a74baff5e | |||
| 8b21b0ff75 | |||
| d22a6303cd | |||
| 807d6643f4 | |||
| c725c2774c | |||
| ae0abae333 | |||
| fed2282212 | |||
| 545960e676 | |||
| 66781f0625 | |||
| 504b9b93e4 | |||
| b71bba473c | |||
| aef3c453c4 | |||
| 6de3ed94dc | |||
| daada08ea7 | |||
| fa37c98500 | |||
| 0ee59704b3 | |||
| a22cbb9520 | |||
| c0d3f64d82 | |||
| 66d2bf4aa4 | |||
| 2b90c953ba | |||
| 0d5e5d679e | |||
| 4da891cc07 | |||
| d3475a9b53 | |||
| eb583f78ec | |||
| 47bebf0a1a | |||
| f9bd149f21 | |||
| 5371b0f193 | |||
| 95497e5514 | |||
| 1a74abe76c | |||
| 7f11d3cdc4 | |||
| da01d79325 | |||
| 82807cfede | |||
| a268d10819 | |||
| ba4bdd9ecb | |||
| 0a72e05e03 | |||
| 8d143ff06d | |||
| 3ddb585a58 | |||
| 1606ceb743 | |||
| 97578f8380 | |||
| 32beae70ef | |||
| 13fbc9e14f | |||
| c57a45c85e | |||
| 3711a9ca43 | |||
| d0c1693f38 | |||
| 36534ee129 | |||
| 188c460054 | |||
| d9e3cb096b | |||
| b57b19493f | |||
| 8c569db541 | |||
| 997257f42d | |||
| f9f1af17bc | |||
| f1f44d0048 | |||
| 7e8511090d | |||
| 4ca6a3138b | |||
| 21e1132e0e | |||
| dc15424014 | |||
| f90cdcd86b | |||
| 5e53c18098 | |||
| 52d13ea7bc | |||
| 5e24c5084e | |||
| abdb61cc41 | |||
| 03556cc670 | |||
| 7f6cd99aba | |||
| ba513fb950 | |||
| 837ddd722c | |||
| f2b0cd3748 | |||
| f2a8878af4 | |||
| 13fccec54b | |||
| eca2b986a9 | |||
| a06599b7f9 | |||
| 7fda4dc577 | |||
| 9e47152e12 | |||
| 83d21da41a | |||
| a692f03bfa | |||
| 994cdf908f | |||
| b022d17fc0 | |||
| 644899c0b5 | |||
| be3a3b920d | |||
| d546eb026f | |||
| fdc59feb13 | |||
| 186b726a7b | |||
|
|
fa362bad55 | ||
|
|
fcf55bb504 | ||
|
|
2185667665 | ||
|
|
772483817e | ||
|
|
178ddd32c7 | ||
| 08e047c90e | |||
| b3c3314385 | |||
| 9fd5e01bb4 | |||
| cd62f65fcd | |||
| fb40833abd | |||
| 6aae155933 | |||
| f9e0ae8fef | |||
| 489e335ca4 | |||
| 874d0b4db9 | |||
| 49e8f70b46 | |||
| 77ea20171e | |||
| 11d4034c37 | |||
| 64369470de | |||
| 7b483f3290 | |||
| 32bec79c28 | |||
| dfd9622ab7 | |||
| 334d3c930b | |||
| 8cf2f370bf | |||
| 3965bed6c4 | |||
| 52396eb384 | |||
| 9cfab7bea1 | |||
| c8ef3bbd4e | |||
| 39e7d3c062 | |||
| 6fa96e11df | |||
| ecde64193a | |||
| f701af9a4d | |||
| 6d99702a11 | |||
| 138f8f2c2f | |||
| 61ddf7378d | |||
| f5b72f30be | |||
| 1863b33955 | |||
| a4573fce1c | |||
| 0461801ee0 | |||
| 4c2eaac013 | |||
| 7ca56ccd2e | |||
| 915a59afa4 | |||
| ac61950d48 | |||
| b257fef563 | |||
| 504c27c197 | |||
| a7a5e2c731 | |||
| 21d72dcc33 | |||
| da7ba40e61 | |||
| b4d6fc55ea | |||
| 515b8feff7 | |||
| 8bc05db033 | |||
| 34cd6b08dc | |||
| 41195dc095 | |||
| 33a83c8f76 | |||
| 4f613d3fe1 | |||
| 1c7cdec600 | |||
| 7ae60c0e62 | |||
| 5dd04925e0 | |||
| 92eedbd4d3 | |||
| 5545d72adf | |||
| 226d44cd28 | |||
| b7152d6124 | |||
| f3e9b58c87 | |||
| 5209315236 | |||
| f38a9e1ff0 | |||
| 80fb296e24 | |||
| 9dda339a32 | |||
| 1098accc8a | |||
| 12826cf436 | |||
| f9f77fdcb1 | |||
| 0bc935d3dd | |||
| fc40e4a6fe | |||
| 99b658a03d | |||
| dacde1e050 | |||
| 365219eddb | |||
| bc4dbbadbb | |||
| c06d2300f2 | |||
| 5e34587a53 | |||
| eb39b297f9 | |||
| 798a7c9933 | |||
| 9dee336e76 | |||
| 9ba43d63b9 | |||
| 8126671df9 | |||
| 3f0ccd79f3 | |||
| e61023acd5 | |||
| 35c2e7a951 | |||
| 81e8a9594f | |||
| 63415f8e58 | |||
| ed9b1ea7b9 | |||
| 6026f9cdfc | |||
| 1924156a8c | |||
| 3927b2b007 | |||
| 7a2e984df7 | |||
| 2c9559104a | |||
| 07e342845a | |||
| dbab5bfbca | |||
| cc70c95115 | |||
| 03bcff2b9a | |||
| 9c4ed3d9e1 | |||
| 8156657eb2 | |||
| c2ffedc8de | |||
| 3600c3e80a | |||
| 2970bd092c | |||
| a2cac188bb | |||
| e542f098a8 | |||
| 04a1670ac2 | |||
| c189bc5dca | |||
| 4b7cf407e8 | |||
| 332deed21e | |||
| 41313029cd | |||
| bbc3b138cf | |||
| e4d5770bdc | |||
| 53a0a47dd6 | |||
| 48edc85e2c | |||
| c685aaa245 | |||
| 2a79389b18 | |||
| c3e4d0dbc2 | |||
| 89123fed31 | |||
| 571811ace6 | |||
| cb24967713 | |||
| 449a59188c | |||
| 907448ce3e | |||
| 4f08af3987 | |||
| 00285e1f24 | |||
| 5b56d7a878 | |||
| 5c0d1def9f | |||
| fc3e252fff | |||
| d605f617e4 | |||
| eb390e09d8 | |||
| bde29ef9d0 | |||
| 5ffc9ed01c | |||
| c992370962 | |||
| b3acf9f29d | |||
| dca3735ce1 | |||
| c7a5c1147f | |||
| 8940e72091 | |||
| 1ed7c6fe63 | |||
| a197dcdefc | |||
| e00f73a48d | |||
| 1c977a6687 | |||
| b3e93bb465 | |||
| 628c798238 | |||
| b34bd9dd7c | |||
| 4d40093fe9 | |||
| e573da2c11 | |||
| f47316efbe | |||
| f57c3c3df0 | |||
| 9b91e4fd63 | |||
| a264e707a8 | |||
| a75a784e8d | |||
| 1e04b6ae9f | |||
| 6dfbfccced | |||
| 27288c2eb2 | |||
| 53a1be9761 | |||
| d1c6492ea3 | |||
| 0f72d88c71 | |||
| 9edfcecdaa | |||
| 5303b3dfef | |||
| 1213c2e57a | |||
| 49ca69aa78 | |||
| 586f7c8fb9 | |||
| 7a68fcfa92 | |||
| 357e5d4490 | |||
| af25326c23 | |||
| e1db312982 | |||
| 59a418727e | |||
| c3d9833acb | |||
| 59ebeb48ce | |||
| 7bd23e993e | |||
| 9b15f3f4f6 | |||
| f087d3c6c0 | |||
| 806b4719fe | |||
| 126f4abe0a | |||
| 5ec76737b3 | |||
| 1d7bcc24fe | |||
| 76a6fce406 | |||
| 3d44bcb04d | |||
| fd8b5c280c | |||
| ef6f104d5f | |||
| 92538a58e3 | |||
| 44bc2f47f1 | |||
| 3c9316340f | |||
| 3ef9236388 | |||
| dfdde631f9 | |||
| 372a6a1b6a | |||
| 5a4996abac | |||
| 416df8d3f1 | |||
| 0aa7fe8e73 | |||
| f14e82a01b | |||
| 5a10065bb0 | |||
| fb801e9260 | |||
| 0fa31f815e | |||
| 76bd0d4e08 | |||
| b31c3ad550 | |||
| af7c1b0130 | |||
| 224b0b4238 | |||
| e6fee6bd97 | |||
| 731b811cfe | |||
| 63d969c844 | |||
| 1138b0d5c9 | |||
| 758810135d | |||
| 6eff61b19d | |||
| b3bc17f327 | |||
| 67d4ac0ebf | |||
| 11d3a2d0bf | |||
| 1f76333af7 | |||
| 816daeb429 | |||
| b1bc19fb7a | |||
| 578258f9e2 | |||
| 41fe8f6a5d | |||
| fee0548899 | |||
| 5ddb45ba24 | |||
| 98c965c607 | |||
| 6e1d23af4e | |||
| 8da45bd348 | |||
| 620c4b395e | |||
| d75208a75a | |||
| 4f08825fb1 | |||
| 865470fc11 | |||
| d827936c85 | |||
| fcc614ff54 | |||
| b7abe2610e | |||
| d916464423 | |||
| 32164157c2 | |||
| ce4b957c8e | |||
| 3735629073 | |||
| d41d618be5 | |||
| d97f3cb2a3 | |||
| dcf31116b4 | |||
| 219141eeee | |||
| 765b5b6024 | |||
| 4efca93d03 | |||
| e2a4908420 | |||
| 183b8d17e6 | |||
| ed2b84c697 | |||
| 59410e6d77 | |||
| a134d1b601 | |||
| 56161f12d0 | |||
| 146dbb137c | |||
| 4f23439dac | |||
| 8b33aa6f6a | |||
| 4f72a8e5ad | |||
| fae4782ef0 | |||
| 37ea01de8c | |||
| 2c53155207 | |||
| dbe585ca2a | |||
| 6434092306 | |||
| 8720176b57 | |||
| 5bdf313fa5 | |||
| 4527e74d29 | |||
| 8e6e543c5b | |||
| dbbd4a2593 | |||
| 6b55f92454 | |||
| fba9645932 | |||
| 0cc867b410 | |||
| 40f738d976 | |||
| 82ecaae156 | |||
| 8d4ac896fa | |||
| 7c6246a539 | |||
| ce88cdd258 | |||
| f179b04af1 | |||
| 0070264d51 | |||
| 22db89a6d9 | |||
| 78dfad9875 | |||
| 6a55138f7c | |||
| f95ee10290 | |||
| adb864c9ca | |||
| f3f92e48e0 | |||
| 87a9d978c2 | |||
| 9981b9ef70 | |||
| 94f10dc9cd | |||
| e3fdc070df | |||
| 4cae63d02b | |||
| d856ceeec7 | |||
| d461570b14 | |||
| 924e0d7bc9 | |||
| 6b3280edaf | |||
| 722d66f130 | |||
| 16083a6f30 | |||
| 1b6fe073dc | |||
| 9f6103ad89 | |||
| 8a67cdf37c | |||
| 9b444d638b | |||
| abab81158f | |||
| 26c0716d35 | |||
| 7f7c724ef1 | |||
| f083e8257a | |||
| 4cb588e992 | |||
| 245d2b49a2 | |||
| e888bd0d38 | |||
| 6cfd56ed01 | |||
| 647af6f87a | |||
| 0e2f9b1031 | |||
| 86ee8908b3 | |||
| a0618b51ba | |||
| 272369ba4c | |||
| 6319c24b5c | |||
| a3180a318c | |||
| aeb890cbed | |||
| 4a94ca1d17 | |||
| 63d845ed97 | |||
| cb67cab974 | |||
| df94564d9b | |||
| 540e008f68 | |||
| c85d3ba8d5 | |||
| 1df335ed16 | |||
| 7ca5076477 | |||
| 6605946e62 | |||
| ba45c776ae | |||
| dcf17052b6 | |||
| 8b9acccb8a | |||
| 2afed176f0 | |||
| 577788110e | |||
| d251ea066f | |||
| 609e65b7cd | |||
| 6c2dd29a57 | |||
| 98896ac0a6 | |||
| 19ba3abade | |||
| 3347490b82 | |||
| d1457b09be | |||
| 1b71439f19 | |||
| 55c09d7e9d | |||
| 8429c29c30 | |||
| 10f8a7e124 | |||
| e8763ea923 | |||
| e5601030b1 | |||
| ad8d15203e | |||
| 3b4e55727c | |||
| 5dc2921d40 | |||
| 0c4ef8abe9 | |||
| 8165523acf | |||
| 7d1e9bb838 | |||
| 0d9140958f | |||
| 16ca8177e9 | |||
| a0e9061a8f | |||
| a56c01fe6d | |||
| 1cb9639f03 | |||
| 7af89db442 | |||
| fae815fd7f | |||
| 1b533d6dd8 | |||
| bc38361348 | |||
| a0a365c10e | |||
| 162c76471b | |||
| 328a50be47 | |||
| 7cc84277c6 | |||
| fbac6b6d77 | |||
| 33b5ac8c87 | |||
| 74f179d64b | |||
| 3cef188ff3 | |||
| 1b767f78d2 | |||
| f380969082 | |||
| df6acde327 | |||
| 0318ffaa10 | |||
| 51c1b38197 | |||
| d96bd1a3f4 | |||
| b6e43b5837 | |||
| 62fa98a656 | |||
| bb5a080f25 | |||
| 2afa9e4166 | |||
| b489a2771f | |||
| 0495ac1bc5 | |||
| e3b49358bb | |||
| 08b365e69e | |||
| 5d4c49c85d | |||
| 5bc9ca34a4 | |||
| 630a1d144b | |||
| 491684f7df | |||
| 9a28247b7f | |||
| 5a91e21d45 | |||
| f6f1ca4c04 | |||
| 8daa10888f | |||
| 82449642d3 | |||
| 06ad2d6971 | |||
| 4c4ca67be8 | |||
| b994f7c142 | |||
| 97b483031d | |||
| c624c20354 | |||
| 695e3bd24c | |||
| ce4883cd4c | |||
| 49e19cb73f | |||
| 31bb242a61 | |||
| 2e533e8798 | |||
| e70d0c8d17 | |||
| d96ecd2b66 | |||
| a588ac19a6 | |||
| 2bc8ec4f56 | |||
| 578a5a1228 | |||
| 416595ffea | |||
| d3be59fbc5 | |||
| 3c3b4da313 | |||
| 501834419b | |||
| 75b25a9e44 | |||
| 15912b2197 | |||
| b093f6136e | |||
| 33db0a53ba | |||
| 7237d2e643 | |||
| 77b1c1dbef | |||
| f3090bd1a4 | |||
| 89a131018c | |||
| de17fb80d1 | |||
| a5f1d15156 | |||
| d3463ce41b | |||
| 4503150b02 | |||
| 36cd00822e | |||
| d285344a61 | |||
| 8418dfc0b5 | |||
| eb039368bb | |||
| 0dea5917a7 | |||
| a8ccde6c81 | |||
| 787ae1b8df | |||
| 276c89d4ac | |||
| 9a395ee25c | |||
| 475c86eabe | |||
| 259737a488 | |||
| 362cb77381 | |||
| 8cfab17136 | |||
| f0018ab87d | |||
| 8f87eb3e60 | |||
| 2b093903b3 | |||
| 05017dcc4f | |||
| 0e9b5fa17a | |||
| dd335aff71 | |||
| 0f61bf54ce | |||
| 14cb22d743 | |||
| 702ab5013e | |||
| 235e2c6e7b | |||
| 2e89a60d63 | |||
| 3d5ef2cd8a | |||
| de8eff9bd2 | |||
| a9561d1694 | |||
| aa33de00d2 | |||
| ffc589fe80 | |||
| 229e4e8d39 | |||
| d6dda2cdd6 | |||
| b8a101fddb | |||
| c8a875b301 | |||
| df8e3a7465 | |||
| d91a6e3939 | |||
| 48a2d683dd | |||
| 44bf42c548 | |||
| d34e6cb0fd | |||
| d8e7356ca3 | |||
| 60e6dbaa6f | |||
| 47e5a7d0e4 | |||
| 9fa2165907 | |||
| 7385d7018c | |||
| 6124c1b307 | |||
| b73535c6da | |||
| 1581023656 | |||
| 0d21e2967d | |||
| 0dc7d83fbe | |||
| cd18067384 | |||
| 6c99f7095c | |||
| eeaafce427 | |||
| b7cb27b5da | |||
| 3e6286da7c | |||
| 30ceea7fd5 | |||
| de9e973fd9 | |||
| 49a3bd5790 | |||
| f1745930b1 | |||
| d9beda8171 | |||
| 65f343fb74 | |||
| 892bd65fac | |||
| 0251191844 | |||
| 65f6f52252 | |||
| 4d94308bcc | |||
| 7dcd8b24d2 | |||
| 11da8e7a43 | |||
| 56c895388d | |||
| 8ec9567f15 | |||
| be3dd0d131 | |||
| 92f51d9884 | |||
| c4904a0ac2 | |||
| 0d87e5fb21 | |||
| d38e3e1702 | |||
| fec59a7495 | |||
| a3b5ad0cb0 | |||
| bf21451819 | |||
| c6d3d9673b | |||
| 3f5334a92d | |||
| bde7b83752 | |||
| c788e5724a | |||
| 295cd12f9d | |||
| 4a432481d9 | |||
| 4a37d83694 | |||
| 15a2a16379 | |||
| 18fc7a6c8c | |||
| 7aba8cdce3 | |||
| 382e6675f1 | |||
| 116d6896f4 | |||
| c9ced52112 | |||
| 01b4bb4e53 | |||
| a266da2cd7 | |||
| 578e7ba807 | |||
| b923e15d46 | |||
| 1310d1e63e | |||
| a8e1e8429c | |||
| 1b54b40f6c | |||
| 6d7530922d | |||
| 23698986b1 | |||
| 1b4c377940 | |||
| d33c0c9aa5 | |||
| 09d8c0024f | |||
| 06e827fff8 | |||
| b1844689df | |||
| a4263a92ca | |||
| 14cc251809 | |||
| 471e8f3398 | |||
| dadbda62c6 | |||
| df47efe816 | |||
| 03cc6943a3 | |||
| 6f0163ce7d | |||
| 91110adad5 | |||
| c2f48d0277 | |||
| 06344b6498 | |||
| 4ff2b2bdc6 | |||
| a71dd310fe | |||
| 90da7a5d74 | |||
| 6505f54c7f | |||
| c4a488e5a7 | |||
| 71817f5ca8 | |||
| 77c97ef2c1 | |||
| 875ec19e38 | |||
| f47e77f816 | |||
| 144a52f813 | |||
| f9a5269fd7 | |||
| bc1445f8bb | |||
| 01e5415074 | |||
| 697ff4f9d5 | |||
| fe7cfc6b1d | |||
| 7a7e148719 | |||
| 95db8f9839 | |||
| 7fb91ae10b | |||
| 0c5c778c75 | |||
| 38865bd062 | |||
| e201765f02 | |||
| d9ccffe3d6 | |||
| 86e084574e | |||
| 756e60b865 | |||
| 5c49094b40 | |||
| 336d1c7c7b | |||
| 632b788082 | |||
| ddd00a3e9a | |||
| 54e59fbd98 | |||
| 3f29a024f9 | |||
| c5f03ed03c | |||
| 547880443c | |||
| 838733fdc3 | |||
| 4b068266a9 | |||
| 57c78f27a7 | |||
| 3af5caef4a | |||
| 49425656ee | |||
| 3e85216e66 | |||
| 443d6fc47c | |||
| 4d7bc59bd3 | |||
| 4dbeee0aa1 | |||
| 9b137fed69 | |||
| 9c667c20da | |||
| e503c965c3 | |||
| 933d1eb730 | |||
| c99fe57074 | |||
| 77fc54dc31 | |||
| 37f6518a15 | |||
| bcaf011166 | |||
| 27807e963d | |||
| f0fabc5e10 | |||
| e7c85eac4d | |||
| 01ea01fef6 | |||
| 134fb776f9 | |||
| 9cd143c5ef | |||
| ba18275ef8 | |||
| 6e92648d8b | |||
| 8ddc612bba | |||
| 2324aadcd5 | |||
| baff3f5a1b | |||
| 68b6ea7649 | |||
| c46cab6a6f | |||
| 74807c73b0 | |||
| a26214fef7 | |||
| ec77a004a2 | |||
| f33203b0e3 | |||
| 297dd8ec4e | |||
| bbc2ae4750 | |||
| fb1a560751 | |||
| bee48c4f0f | |||
| 0f8fbfb8df | |||
| d71e9a78a1 | |||
| 7ac778586c | |||
| 2558cb69b1 | |||
| 55ac8d12a5 | |||
| a5dc5ce921 | |||
| de65cc0926 | |||
| 80d3eed224 | |||
| 76beccfb9f | |||
| 5579cd9255 | |||
| 236e84f11a | |||
| ed61dc6bbf | |||
| 2b7ac05338 | |||
| 36e63995aa | |||
| 58d16fbc7d | |||
| aa78482c36 | |||
| c35b6a3f6b | |||
| 8d52c46a53 | |||
| 325f124e4e | |||
| 18d71a69f8 | |||
| 7d5fd72297 | |||
| a625f6d9fc | |||
| ede89cc6cf | |||
| c60f06e8ed | |||
| 863a5efa38 | |||
| 670b0aac67 | |||
| d261b156bd | |||
| c49bdb731a | |||
| dc2993bdea | |||
| 09e1c0ad48 | |||
| 370153bed9 | |||
| b115c4bf67 | |||
| 90a7ac1267 | |||
| 887d330ba9 | |||
| fe97a589d9 | |||
| 37d0b2321f | |||
| 47aa454895 | |||
| fecc4956b4 | |||
| 0d5fb8d3e3 | |||
| ca6e51911b | |||
| 8159487631 | |||
| 392b27563a | |||
| 60dced41db | |||
| 8b02a80904 | |||
| ff977cc364 | |||
| 3578908832 | |||
| 4fcde967f1 | |||
| 4e81c16617 | |||
| 8149f5ab9b | |||
| 7800e52299 | |||
| c6f8fd7561 | |||
| e3d26cea23 | |||
| 4fd35d4cb6 | |||
| cf00331497 | |||
| dc8ea82b61 | |||
| aab99e3abe | |||
| f240a5a6a4 | |||
| 05ce348d8a | |||
| 67afbff93d | |||
| bd92ca8f2c | |||
| 7ef3e31465 | |||
| f59aa18be8 | |||
| 2699b37e4f | |||
| 22f8138422 | |||
| 0727257d72 | |||
| f8a9ffbe15 | |||
| aac442ab55 | |||
| b7f0bf6049 | |||
| b7b9177edf | |||
| f5d7e00ec6 | |||
| 4f8c57032c | |||
| 96fcb81d9e | |||
| 1b5663fa99 | |||
| a2588040ff | |||
| 947f56899d | |||
| 731e709174 | |||
| c3cce531d6 | |||
| ca63852b4f | |||
| 9342ef146c | |||
| 00b6939571 | |||
| 0376233ace | |||
| 70533ae3c3 | |||
| d7613bd5ba | |||
| 06bd0dfcce | |||
| 78708733be | |||
| e7ae654066 | |||
| 5c0c0cc657 | |||
| 9d043e4bc8 | |||
| 08b539dc1e | |||
| e5147b3893 | |||
| 01a2b2a1b1 | |||
| e89f2d17e7 | |||
| 7e0888d375 | |||
| f435f7f3c8 | |||
| 0d2be843f5 | |||
| 6e7ec44188 | |||
| ba152e69d2 | |||
| 42212a073c | |||
| 469f8c739c | |||
| 53505857d6 | |||
| 89a02d85fe | |||
| 6249b057af | |||
| f760ee057d | |||
| 291bff0813 | |||
| 79edbed9ce | |||
| 54c797c114 | |||
| 201a577588 | |||
| 4dfbdbfdda | |||
| 3e5008aeb2 | |||
| bfb6d3bc4e | |||
| d829c2f83a | |||
| ec1063f80f | |||
| 3b20c29d0a | |||
| f2dc4b03ef | |||
| 22e4aba1cf | |||
| ece22c73ae | |||
| ed7e5d04ec | |||
| 6a595ab30c | |||
| 821606ee5e | |||
| 61440a2aca | |||
| 01eefe0a04 | |||
| 9057ef3edb | |||
| 4f333c9b94 | |||
| b64d91108e | |||
| fe063a029e | |||
| b6504333c0 | |||
| d0c3fefa77 | |||
| a81e6b9b66 | |||
| a760ddc7c1 | |||
| 3e0b327ea7 | |||
| c5296e7c5f | |||
| fec454f43e | |||
| bd18574bba | |||
| c22272143d | |||
| c3e8fb22c1 | |||
| 75defce2dc | |||
| 327d59d380 | |||
| 10ddd6731e | |||
| 181da3fef1 | |||
| fbcc3eba9d | |||
| d265d765d1 | |||
| 948c5d0146 | |||
| d0adb4ed4e | |||
| 86cf0b2edd | |||
| 4ac76a62a2 | |||
| 2995060ec6 | |||
| 6e092922ac | |||
| 07b72c9cb1 | |||
| a4904c80a0 | |||
| 181b928d19 | |||
| fd8ca2d44d | |||
| c72d375aea | |||
| 68e3a7cc7c | |||
| 69d047e687 | |||
| 12166839b2 | |||
| dfb59025f6 | |||
| 684e7a45aa | |||
| 7503c68820 | |||
| dcfe88b447 | |||
| b5eb3e2568 | |||
| 8cf39301bd | |||
| 0bb97a4e37 | |||
| 1fe10d53dd | |||
| c6b234bde1 | |||
| 68169de61d | |||
| e82d09c027 | |||
| 65a89407ff | |||
| 76778ece82 | |||
| dfbf5ab1af | |||
| b771199e3d | |||
| a715434f42 | |||
| 7009387a00 | |||
| 5117e7a479 | |||
| cae904b5a8 | |||
| 9876ebf6ed | |||
| c665f5e27b | |||
| 77e5022708 | |||
| a8a97ce748 | |||
| bb9d9e5650 | |||
| c5a461f12a | |||
| 4c659f9aca | |||
| 7bd74df165 | |||
| 5d04a8b246 | |||
| 4c847a5435 | |||
| dd79571272 | |||
| b931e2967f | |||
| 4ec8ceca1b | |||
| 798aea6a8b | |||
| e3fcc8c9be | |||
| 30255f1db8 | |||
| 24f4e9ecf2 | |||
| 454613c1ba | |||
| eeccece2a7 | |||
| c9c49c9425 | |||
| d345365e0b | |||
| 897f56eb23 | |||
| 4db96a1792 | |||
| ae572557a1 | |||
| 6605bd9467 | |||
| 3f352f1f19 | |||
| 939cd63d41 | |||
| 42c5eb797e | |||
| 16bb039917 | |||
| 2db29e1eec | |||
| 843c355a64 | |||
| 8b7cc3ffb6 | |||
| a1135115bc | |||
| 7c37cda14e | |||
| b1614941af | |||
| f19daef1d7 | |||
| 9d5d6d6c26 | |||
| 72d9bbebd0 | |||
| fc6ab6bb4e | |||
| 1360424d34 | |||
| 9838b831ec | |||
| 402f6f1097 | |||
| a64dc10471 | |||
| a9dcb183fd | |||
| 1259abc6c7 | |||
| 77438191fe | |||
| 0c2b6a8134 | |||
| b2c3e31671 | |||
| 8e62740d4d | |||
| fc93862528 | |||
| a68c7d5803 | |||
| 263030fd93 | |||
| 12a6bb575c | |||
| 0c2a5c0297 | |||
| 64b38aedea | |||
| ad1bd78afe | |||
| 74c3535335 | |||
| d8798547b1 | |||
| d48df86a49 | |||
| cd4e21daf4 | |||
| eed8daf87c | |||
| b4c79b08a2 | |||
| cec63b48ef | |||
| 2ebeef3f42 | |||
| bac9778203 | |||
| 0c4b8a24d9 | |||
| 476a194410 | |||
| 10ec2a1b1b | |||
| 936a48f205 | |||
| aba1cdf6d9 | |||
| c80e90b2d4 | |||
| b2e1d649ff | |||
| eaf2d9785c | |||
| 3925337df0 | |||
| b35b9cf478 | |||
| 65577b57dd | |||
| 8120296154 | |||
| 7a1d769581 | |||
| 5c9cd88279 | |||
| ec56b59d57 | |||
| ee088c65f2 | |||
| d7c2ef388f | |||
| 5a9aaa801a | |||
| 3f06e790f0 | |||
| 1d475185fb | |||
| a611afb475 | |||
| b65da8e2d9 | |||
| d9be3323f0 | |||
| f04e8e8ab2 | |||
| d13609ff4a | |||
| 6305dc73ac | |||
| 4e61d363ee | |||
| 9fe2119d5a | |||
| 1500493108 | |||
| 6896c94457 | |||
| 13dfca39d3 | |||
| 7570376f1b | |||
| fc626d4619 | |||
| b77c4eaf37 | |||
| c1ab2848df | |||
| 6318c2f7e0 | |||
| 1d5bf9f1c9 | |||
| 8447de006a | |||
| 82bab537b5 | |||
| c74c40f08d | |||
| e2d8f2f16c | |||
| 7f2a918f9e | |||
| 0b2fbba330 | |||
| 4df82ea8e1 | |||
| 3af5e28c9e | |||
| 319df60b51 | |||
| cf30ff3fe1 | |||
| 767f42c326 | |||
| 51c90f5fef | |||
| b82849938d | |||
| 1adfaa1fdb | |||
| 1542697ca1 | |||
| 9c63ffc08a | |||
| 9e331b83d3 | |||
| e3d4be434b | |||
| f919ae9b00 | |||
| 8fdcf7fb43 | |||
| 3fcb49a56b | |||
| 53629c59c3 | |||
| 7976c033ad | |||
| 0627722806 | |||
| 903ae789d2 | |||
| 8d5154a456 | |||
| b002730a95 | |||
| 20d3d61d1d | |||
| 8e6c5f81a1 | |||
| a70561b5bd | |||
| 5d6ff72ef7 | |||
| 039e9f97c6 | |||
| 3896560184 | |||
| 3d9c7f8ad9 | |||
| d70ce7b156 | |||
| a939804dd5 | |||
| b6ee0bb482 | |||
| a0b0c3762a | |||
| 6134906fdd | |||
| 1af02ec3de | |||
| 757ddf87ac | |||
| 5fb42e8cd8 | |||
| 836653fc38 | |||
| 604dd0830e | |||
| 3427b00e25 | |||
| ecaa0cda6a | |||
| 822dc4b705 | |||
| 259a4fdc58 | |||
| 29ea58db36 | |||
| b217126316 | |||
| 5dd7de79ab | |||
| dd280ddf51 | |||
| 2210b11a57 | |||
| 70cd5a5cdd | |||
| 23d4debc3a | |||
| 1a708a8a56 | |||
| 2bb87b74da | |||
| ccff5a5e8a | |||
| 573502ee77 | |||
| a020231224 | |||
| a1530fb952 | |||
| 98bbad8471 | |||
| 3de06cad11 | |||
| 6f5b903e5d | |||
| 5b6043a5de | |||
| 0d6240c089 | |||
| 56a3b2cffb | |||
| ca2992e334 | |||
| 9def9eb2c3 | |||
| 81b0902378 | |||
| d4a747e3a8 | |||
| a24f9b78b5 | |||
| 5d2acdac08 | |||
| f0b6c237cc | |||
| aea9bf5f40 | |||
| 97c65b703f | |||
| 23b06ced47 | |||
| c5164684ac | |||
| 0304acd2b2 | |||
| 86327723b8 | |||
| 40f4e6e00e | |||
| 49a42d2374 | |||
| 75f5cd2619 | |||
| be95de7066 | |||
| 3189d227fc | |||
| a4a043a2d9 | |||
| 19f1ffa89c | |||
| f51fa5c7d0 | |||
| 813a74d1af | |||
| a55f3e03de | |||
| 89e3e50a64 | |||
| 6e60b11be1 | |||
| c9225e028c | |||
| 16ed209cc9 | |||
| 47f6a0fa79 | |||
| e935f514c1 | |||
| 55913d3b5a | |||
| 781a1bfe61 | |||
| b6f0b35bf0 | |||
| 7d3c21771f | |||
| e26b593702 | |||
| 28324eb6ff | |||
| fea0979030 | |||
| 346fddad27 | |||
| a66adfc6d1 | |||
| 53a35e0fc3 | |||
| 325e8cd71c | |||
| 8c6d5acea1 | |||
| 832b948f80 | |||
| cba21fa1b6 | |||
| 1934ec9cfc | |||
| 783b63c9a0 | |||
| f9a31fd3f2 | |||
| 090fbba6f5 | |||
| 92baf905cd | |||
| 22241a3be4 | |||
| ed27fc559d | |||
| cb07f397e0 | |||
| 916a9bdb70 | |||
| 9750ef2f3b | |||
| ab6851ff2b | |||
| ebf0177dd7 | |||
| a8ce05d8a6 | |||
| 43876fb61d | |||
| 5b87733b54 | |||
| 0497220c68 | |||
| 9d3cbfa2e0 | |||
| 240b5de58b | |||
| 0a98e7cb74 | |||
| 04f64eadc7 | |||
| b5b30b2b03 | |||
| f96f01784a | |||
| fa09a50e84 | |||
| 923d09328b | |||
| 5a7cfbd2a6 | |||
| d2d61b5e87 | |||
| 253c566e90 | |||
| 39d2e172bc | |||
| 1bddea1e8f | |||
| e928ad58dc | |||
| 002e62f723 | |||
| d809e90627 | |||
| 150e18a2d0 | |||
| 4155547fb9 | |||
| 108ed72560 | |||
| 701112311d | |||
| 5f2c6d2c20 | |||
| e70adfd335 | |||
| a9ebc4107f | |||
| 76ffda9cad | |||
| b5d42fcd7b | |||
| 11e215189b | |||
| 1e426a8295 | |||
| 8aa3240c7e |
15
Gemfile
15
Gemfile
@@ -2,9 +2,14 @@ source 'https://rubygems.org'
|
|||||||
|
|
||||||
gem 'quickbooks-ruby'
|
gem 'quickbooks-ruby'
|
||||||
gem 'quickbooks-ruby-base'
|
gem 'quickbooks-ruby-base'
|
||||||
gem 'oauth-plugin'#, '~> 0.5.1'
|
gem 'oauth-plugin'
|
||||||
gem 'oauth'
|
gem 'oauth2'
|
||||||
gem 'roxml'
|
gem 'roxml'
|
||||||
gem 'nokogiri'
|
gem 'nhtsa_vin'
|
||||||
gem 'edmunds_vin'
|
gem 'will_paginate'
|
||||||
gem 'will_paginate', '~> 3.1.0'
|
gem 'rails-jquery-autocomplete'
|
||||||
|
gem 'jquery-ui-rails'
|
||||||
|
|
||||||
|
group :assets do
|
||||||
|
gem 'coffee-rails'
|
||||||
|
end
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 Rick Barrette
|
Copyright (c) 2018 Rick Barrette
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
73
README.md
73
README.md
@@ -1,27 +1,40 @@
|
|||||||
#Redmine Quickbooks Online
|
# 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
|
`Note: I am currently using this in a live production enviroment with no issues`
|
||||||
* 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.
|
|
||||||
|
|
||||||
##Prerequisites
|
#### 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/
|
* Sign up to become a developer for Intuit https://developer.intuit.com/
|
||||||
* Create your own aplication to obtain your API keys
|
* 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
|
## The Install
|
||||||
|
|
||||||
1. To install, clone this repo into your plugin folder
|
1. To install, clone this repo into your plugin folder
|
||||||
|
|
||||||
@@ -33,36 +46,38 @@ 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.
|
3. Navigate to the plugin configuration page and suppy your own OAuth key & secret.
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
4. After saving your key & secret, you need to click on the Authenticate link on the plugin configuration page to authenticate with QBO.
|
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
|
5. Assign an Employee to each of your users via the User Administration Page
|
||||||
|
|
||||||

|
## 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
|
## 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.
|
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
|
Note: After the inital synchronization, this plugin will recieve push notifications via Intuit's webhook service.
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## TODO
|
## 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
|
* 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
|
||||||
* Add a rake file to create required Trackers or statuses required
|
* Customer Deletion
|
||||||
* Add link Invoice PDF to issue after creation.
|
* 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.
|
||||||
* Clean up view hook code, possibly use a controller hook and reder partial views
|
|
||||||
* Add Setting for Sandbox Mode
|
* 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
|
## License
|
||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 rick barrette
|
Copyright (c) 2020 rick barrette
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
|||||||
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 |
Binary file not shown.
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 106 KiB |
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2017 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -13,51 +13,163 @@ class CustomersController < ApplicationController
|
|||||||
unloadable
|
unloadable
|
||||||
|
|
||||||
include AuthHelper
|
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 :add_customer, :only => :new
|
||||||
|
before_filter :view_customer, :except => :new
|
||||||
|
skip_before_filter :verify_authenticity_token, :check_if_login_required, :only => [:view]
|
||||||
|
|
||||||
|
default_search_scope :names
|
||||||
|
|
||||||
|
autocomplete :customer, :name, :full => true, :extra_data => [:id]
|
||||||
|
|
||||||
|
def filter_vehicles_by_customer
|
||||||
|
@filtered_vehicles = Vehicle.all.where(customer_id: params[:selected_customer])
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter_invoices_by_customer
|
||||||
|
@filtered_invoices = QboInvoice.all.where(customer_id: params[:selected_customer])
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter_estimates_by_customer
|
||||||
|
@filtered_estimates = QboEstimate.all.where(customer_id: params[:selected_customer])
|
||||||
|
end
|
||||||
|
|
||||||
# display a list of all customers
|
# display a list of all customers
|
||||||
def index
|
def index
|
||||||
@customers = QboCustomer.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
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
|
@customer = Customer.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
@customer = Customer.new(params[:customer])
|
||||||
|
if @customer.save
|
||||||
|
flash[:notice] = "New Customer Created"
|
||||||
|
redirect_to @customer
|
||||||
|
else
|
||||||
|
flash[:error] = @customer.errors.full_messages.to_sentence
|
||||||
|
redirect_to new_customer_path
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# display a specific customer
|
# display a specific customer
|
||||||
def show
|
def show
|
||||||
@customer = QboCustomer.find_by_id(params[:id])
|
begin
|
||||||
if @customer
|
@customer = Customer.find_by_id(params[:id])
|
||||||
@vehicles = @customer.vehicles.paginate(:page => params[:page])
|
@vehicles = @customer.vehicles.paginate(:page => params[:page])
|
||||||
else
|
@issues = @customer.issues
|
||||||
flash[:error] = "Customer Not Found"
|
rescue ActiveRecord::RecordNotFound
|
||||||
render :index
|
render_404
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# return an HTML form for editing a customer
|
# return an HTML form for editing a customer
|
||||||
def edit
|
def edit
|
||||||
@customer = QboCustomer.find_by_id(params[:id])
|
begin
|
||||||
|
@customer = Customer.find_by_id(params[:id])
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# update a specific customer
|
# update a specific customer
|
||||||
def update
|
def update
|
||||||
@customer = QboCustomer.find_by_id(params[:id])
|
begin
|
||||||
if @customer.update_attributes(params[:customer])
|
@customer = Customer.find_by_id(params[:id])
|
||||||
flash[:success] = "Customer updated"
|
if @customer.update_attributes(params[:customer])
|
||||||
redirect_to @customer
|
flash[:notice] = "Customer updated"
|
||||||
else
|
redirect_to @customer
|
||||||
render :edit
|
else
|
||||||
|
redirect_to edit_customer_path
|
||||||
|
flash[:error] = @customer.errors.full_messages.to_sentence if @customer.errors
|
||||||
|
end
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
begin
|
||||||
|
Customer.find_by_id(params[:id]).destroy
|
||||||
|
flash[:notice] = "Customer deleted successfully"
|
||||||
|
redirect_to action: :index
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Customer view for an issue
|
||||||
|
def view
|
||||||
|
|
||||||
|
User.current = User.find_by lastname: 'Anonymous'
|
||||||
|
|
||||||
|
@token = CustomerToken.where("token = ? and expires_at > ?", params[:token], Time.now)
|
||||||
|
@token = @token.first
|
||||||
|
if @token
|
||||||
|
session[:token] = @token.token
|
||||||
|
@issue = Issue.find @token.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 add_customer
|
||||||
|
global_check_permission(:add_customers)
|
||||||
|
end
|
||||||
|
|
||||||
|
def view_customer
|
||||||
|
global_check_permission(:view_customers)
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2017 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#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:
|
||||||
#
|
#
|
||||||
@@ -18,7 +18,7 @@ class EstimateController < ApplicationController
|
|||||||
# Downloads and forwards the estimate pdf
|
# Downloads and forwards the estimate pdf
|
||||||
#
|
#
|
||||||
def show
|
def show
|
||||||
base = QboEstimate.get_base.service
|
base = QboEstimate.get_base
|
||||||
estimate = base.fetch_by_id(params[:id])
|
estimate = base.fetch_by_id(params[:id])
|
||||||
@pdf = base.pdf(estimate)
|
@pdf = base.pdf(estimate)
|
||||||
send_data @pdf, filename: "estimate #{estimate.doc_number}.pdf", :disposition => 'inline', :type => "application/pdf"
|
send_data @pdf, filename: "estimate #{estimate.doc_number}.pdf", :disposition => 'inline', :type => "application/pdf"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2017 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -12,13 +12,14 @@ class InvoiceController < ApplicationController
|
|||||||
|
|
||||||
include AuthHelper
|
include AuthHelper
|
||||||
|
|
||||||
before_filter :require_user
|
before_filter :require_user, :unless => proc {|c| session[:token].nil? }
|
||||||
|
skip_before_filter :verify_authenticity_token, :check_if_login_required, :unless => proc {|c| session[:token].nil? }
|
||||||
|
|
||||||
#
|
#
|
||||||
# Downloads and forwards the invoice pdf
|
# Downloads and forwards the invoice pdf
|
||||||
#
|
#
|
||||||
def show
|
def show
|
||||||
base = QboInvoice.get_base.service
|
base = QboInvoice.get_base
|
||||||
invoice = base.fetch_by_id(params[:id])
|
invoice = base.fetch_by_id(params[:id])
|
||||||
@pdf = base.pdf(invoice)
|
@pdf = base.pdf(invoice)
|
||||||
send_data @pdf, filename: "invoice #{invoice.doc_number}.pdf", :disposition => 'inline', :type => "application/pdf"
|
send_data @pdf, filename: "invoice #{invoice.doc_number}.pdf", :disposition => 'inline', :type => "application/pdf"
|
||||||
|
|||||||
94
app/controllers/line_items_controler.rb
Normal file
94
app/controllers/line_items_controler.rb
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2018 rick barrette
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
# This controller class will handle map management
|
||||||
|
class LineItemsController < ApplicationController
|
||||||
|
unloadable
|
||||||
|
|
||||||
|
include AuthHelper
|
||||||
|
|
||||||
|
before_filter :require_user
|
||||||
|
|
||||||
|
# display all line items for an issue
|
||||||
|
def index
|
||||||
|
if params[:issue_id]
|
||||||
|
begin
|
||||||
|
@line_items = Issue.find_by_id(params[:issue_id]).line_items
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# return an HTML form for creating a new line item
|
||||||
|
def new
|
||||||
|
@line_item = LineItem.new
|
||||||
|
end
|
||||||
|
|
||||||
|
# create a new line item
|
||||||
|
def create
|
||||||
|
@line_item = LineItem.new(params[:line_item])
|
||||||
|
if @line_item.save
|
||||||
|
flash[:notice] = "New Line Item Created"
|
||||||
|
redirect_to @line_item.issue
|
||||||
|
else
|
||||||
|
flash[:error] = @line_item.errors.full_messages.to_sentence
|
||||||
|
redirect_to new_line_item_path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# display a specific line item
|
||||||
|
def show
|
||||||
|
begin
|
||||||
|
@line_item = LineItem.find_by_id(params[:id])
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# return an HTML form for editing a line item
|
||||||
|
def edit
|
||||||
|
begin
|
||||||
|
@line_item = LineItem.find_by_id(params[:id])
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# update a specific line item
|
||||||
|
def update
|
||||||
|
begin
|
||||||
|
@line_item = LineItem.find_by_id(params[:id])
|
||||||
|
if @line_item.update_attributes(params[:line_item])
|
||||||
|
flash[:notice] = "Line Item updated"
|
||||||
|
redirect_to @line_item
|
||||||
|
else
|
||||||
|
flash[:error] = @line_item.errors.full_messages.to_sentence if @line_item.errors
|
||||||
|
redirect_to edit_line_item_path
|
||||||
|
end
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# delete a specific line item
|
||||||
|
def destroy
|
||||||
|
begin
|
||||||
|
line_item = LineItem.find_by_id(params[:id])
|
||||||
|
issue = line_item.issue
|
||||||
|
line_item.destroy
|
||||||
|
flash[:notice] = "Line Item deleted successfully"
|
||||||
|
redirect_to issue
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
57
app/controllers/payments_controller.rb
Normal file
57
app/controllers/payments_controller.rb
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2017 rick barrette
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
class PaymentsController < ApplicationController
|
||||||
|
unloadable
|
||||||
|
|
||||||
|
include AuthHelper
|
||||||
|
|
||||||
|
before_filter :check_permissions
|
||||||
|
|
||||||
|
def new
|
||||||
|
@payment = Payment.new
|
||||||
|
|
||||||
|
@customers = Customer.all.sort_by &:name
|
||||||
|
|
||||||
|
@accounts = Qbo.get_base(:account).query("SELECT Id, Name FROM Account WHERE AccountType = 'Bank' Order By Name")
|
||||||
|
|
||||||
|
@payment_methods = Qbo.get_base(:payment_method).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 check_permissions
|
||||||
|
if !allowed_to?(:add_payments)
|
||||||
|
render :file => "public/401.html.erb", :status => :unauthorized, :layout =>true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def only_one_non_zero?( array )
|
||||||
|
found_non_zero = false
|
||||||
|
array.each do |val|
|
||||||
|
if val!=0
|
||||||
|
return false if found_non_zero
|
||||||
|
found_non_zero = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
found_non_zero
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2017 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -11,16 +11,19 @@
|
|||||||
class QboController < ApplicationController
|
class QboController < ApplicationController
|
||||||
unloadable
|
unloadable
|
||||||
|
|
||||||
|
require 'openssl'
|
||||||
|
|
||||||
include AuthHelper
|
include AuthHelper
|
||||||
|
|
||||||
before_filter :require_user
|
before_filter :require_user, :except => :qbo_webhook
|
||||||
|
skip_before_filter :verify_authenticity_token, :check_if_login_required, :only => [:qbo_webhook]
|
||||||
|
|
||||||
#
|
#
|
||||||
# Called when the QBO Top Menu us shown
|
# Called when the QBO Top Menu us shown
|
||||||
#
|
#
|
||||||
def index
|
def index
|
||||||
@qbo = Qbo.first
|
@qbo = Qbo.first
|
||||||
@qbo_customer_count = QboCustomer.count
|
@customer_count = Customer.count
|
||||||
@qbo_item_count = QboItem.count
|
@qbo_item_count = QboItem.count
|
||||||
@qbo_employee_count = QboEmployee.count
|
@qbo_employee_count = QboEmployee.count
|
||||||
@qbo_invoice_count = QboInvoice.count
|
@qbo_invoice_count = QboInvoice.count
|
||||||
@@ -31,52 +34,132 @@ class QboController < ApplicationController
|
|||||||
# Called when the user requests that Redmine to connect to QBO
|
# Called when the user requests that Redmine to connect to QBO
|
||||||
#
|
#
|
||||||
def authenticate
|
def authenticate
|
||||||
callback = request.base_url + qbo_oauth_callback_path
|
oauth2_client = Qbo.get_client
|
||||||
token = Qbo.get_oauth_consumer.get_request_token(:oauth_callback => callback)
|
callback = "https://redmine.rickbarrette.org/qbo/oauth_callback/"
|
||||||
session[:qb_request_token] = token
|
#callback = qbo_oauth_callback_url
|
||||||
redirect_to("https://appcenter.intuit.com/Connect/Begin?oauth_token=#{token.token}") and return
|
grant_url = oauth2_client.auth_code.authorize_url(redirect_uri: callback, response_type: "code", state: SecureRandom.hex(12), scope: "com.intuit.quickbooks.accounting")
|
||||||
|
redirect_to grant_url
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Called by QBO after authentication has been processed
|
# Called by QBO after authentication has been processed
|
||||||
#
|
#
|
||||||
def oauth_callback
|
def oauth_callback
|
||||||
at = session[:qb_request_token].get_access_token(:oauth_verifier => params[:oauth_verifier])
|
if params[:state].present?
|
||||||
token = at.token
|
oauth2_client = Qbo.get_client
|
||||||
secret = at.secret
|
# use the state value to retrieve from your backend any information you need to identify the customer in your system
|
||||||
realm_id = params['realmId']
|
#redirect_uri = qbo_oauth_callback_url
|
||||||
|
redirect_uri = "https://redmine.rickbarrette.org/qbo/oauth_callback/"
|
||||||
#There can only be one...
|
if resp = oauth2_client.auth_code.get_token(params[:code], redirect_uri: redirect_uri)
|
||||||
Qbo.destroy_all
|
# save your tokens here. For example:
|
||||||
|
# quickbooks_credentials.update_attributes(access_token: resp.token, refresh_token: resp.refresh_token,
|
||||||
# Save the authentication information
|
# realm_id: params[:realmId])
|
||||||
qbo = Qbo.new
|
|
||||||
qbo.token = token
|
Qbo.delete_all
|
||||||
qbo.secret = secret
|
|
||||||
qbo.token_expires_at = 6.months.from_now.utc
|
# Save the authentication information
|
||||||
qbo.reconnect_token_at = 5.months.from_now.utc
|
qbo = Qbo.new
|
||||||
qbo.realmId = realm_id
|
qbo.qb_token = resp.token
|
||||||
if qbo.save!
|
qbo.qb_secret = resp.refresh_token
|
||||||
redirect_to qbo_sync_path, :flash => { :notice => "Successfully connected to Quickbooks" }
|
qbo.token_expires_at = 6.months.from_now.utc
|
||||||
else
|
qbo.reconnect_token_at = 3.months.from_now.utc
|
||||||
redirect_to plugin_settings_path(:redmine_qbo), :flash => { :error => "Error" }
|
qbo.company_id = params[:realmId]
|
||||||
|
|
||||||
|
access_token = OAuth2::AccessToken.new(oauth2_client, resp.token, refresh_token: resp.refresh_token)
|
||||||
|
qbo.token = access_token
|
||||||
|
qbo.expire = 1.hour.from_now.utc
|
||||||
|
|
||||||
|
if qbo.save!
|
||||||
|
redirect_to qbo_sync_path, :flash => { :notice => "Successfully connected to Quickbooks" }
|
||||||
|
else
|
||||||
|
redirect_to plugin_settings_path(:redmine_qbo), :flash => { :error => "Error" }
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Manual Billing
|
||||||
|
def bill
|
||||||
|
i = Issue.find_by_id params[:id]
|
||||||
|
if i.customer
|
||||||
|
i.bill_time
|
||||||
|
redirect_to i, :flash => { :notice => "Successfully Billed #{i.customer.name}" }
|
||||||
|
else
|
||||||
|
redirect_to i, :flash => { :error => "Cannot bill without a customer assigned" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Quickbooks Webhook Callback
|
||||||
|
def qbo_webhook
|
||||||
|
|
||||||
|
# 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
|
||||||
|
render nothing: true, status: 400
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Synchronizes the QboCustomer table with QBO
|
# Synchronizes the QboCustomer table with QBO
|
||||||
#
|
#
|
||||||
def sync
|
def sync
|
||||||
if Qbo.exists?
|
# Update info in background
|
||||||
QboCustomer.update_all
|
Thread.new do
|
||||||
QboItem.update_all
|
if Qbo.exists?
|
||||||
QboEmployee.update_all
|
Customer.sync
|
||||||
QboEstimate.update_all
|
QboInvoice.sync
|
||||||
QboInvoice.update_all
|
QboItem.sync
|
||||||
QboPurchase.update_all
|
QboEmployee.sync
|
||||||
|
QboEstimate.sync
|
||||||
|
|
||||||
|
# Record the last sync time
|
||||||
|
Qbo.update_time_stamp
|
||||||
|
end
|
||||||
|
ActiveRecord::Base.connection.close
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to qbo_path(:redmine_qbo), :flash => { :notice => "Successfully synced to Quickbooks" }
|
redirect_to :home, :flash => { :notice => "Successfully synced to Quickbooks" }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2017 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#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:
|
||||||
#
|
#
|
||||||
@@ -18,59 +18,100 @@ class VehiclesController < ApplicationController
|
|||||||
|
|
||||||
# display a list of all vehicles
|
# display a list of all vehicles
|
||||||
def index
|
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
|
end
|
||||||
|
|
||||||
# return an HTML form for creating a new vehicle
|
# return an HTML form for creating a new vehicle
|
||||||
def new
|
def new
|
||||||
@vehicle = Vehicle.new
|
@vehicle = Vehicle.new
|
||||||
|
@customer = Customer.find_by_id(params[:customer_id]) if params[:customer_id]
|
||||||
end
|
end
|
||||||
|
|
||||||
# create a new vehicle
|
# create a new vehicle
|
||||||
def create
|
def create
|
||||||
@vehicle = Vehicle.new(params[:vehicle])
|
@vehicle = Vehicle.new(params[:vehicle])
|
||||||
@vehicle.save!
|
if @vehicle.save
|
||||||
redirect_to @vehicle
|
flash[:notice] = "New Vehicle Created"
|
||||||
|
redirect_to @vehicle
|
||||||
|
else
|
||||||
|
flash[:error] = @vehicle.errors.full_messages.to_sentence
|
||||||
|
redirect_to Vehicle.find_by_vin @vehicle.vin
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# display a specific vehicle
|
# display a specific vehicle
|
||||||
def show
|
def show
|
||||||
@vehicle = Vehicle.find_by_id(params[:id])
|
begin
|
||||||
if @vehicle
|
@vehicle = Vehicle.find_by_id(params[:id])
|
||||||
@customer = @vehicle.qbo_customer.name if @vehicle.qbo_customer
|
@vin = @vehicle.vin.scan(/.{1,9}/) if @vehicle.vin
|
||||||
else
|
rescue ActiveRecord::RecordNotFound
|
||||||
flash[:error] = "Vehicle Not Found"
|
render_404
|
||||||
render :index
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# return an HTML form for editing a vehicle
|
# return an HTML form for editing a vehicle
|
||||||
def edit
|
def edit
|
||||||
@vehicle = Vehicle.find_by_id(params[:id])
|
begin
|
||||||
@customer = @vehicle.qbo_customer.id if @vehicle.qbo_customer
|
@vehicle = Vehicle.find_by_id(params[:id])
|
||||||
|
@customer = @vehicle.customer
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# update a specific vehicle
|
# update a specific vehicle
|
||||||
def update
|
def update
|
||||||
@vehicle = Vehicle.find_by_id(params[:id])
|
@customer = params[:customer]
|
||||||
if @vehicle.update_attributes(params[:vehicle])
|
begin
|
||||||
flash[:success] = "Vehicle updated"
|
@vehicle = Vehicle.find_by_id(params[:id])
|
||||||
redirect_to @vehicle
|
if @vehicle.update_attributes(params[:vehicle])
|
||||||
else
|
flash[:notice] = "Vehicle updated"
|
||||||
render :edit
|
redirect_to @vehicle
|
||||||
|
else
|
||||||
|
redirect_to edit_vehicle_path
|
||||||
|
end
|
||||||
|
#show any errors anyways
|
||||||
|
flash[:error] = @vehicle.errors.full_messages.to_sentence unless @vehicle.errors.empty?
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# delete a specific vehicle
|
# delete a specific vehicle
|
||||||
def destroy
|
def destroy
|
||||||
v = Vehicle.find_by_id(params[:id])
|
begin
|
||||||
if v != nil
|
Vehicle.find_by_id(params[:id]).destroy
|
||||||
v.destroy
|
flash[:notice] = "Vehicle deleted successfully"
|
||||||
flash.now[:notice] = "Vehicle deleted successfully"
|
redirect_to action: :index
|
||||||
else
|
rescue ActiveRecord::RecordNotFound
|
||||||
flash.now[:error] = "No Vehicle Found"
|
render_404
|
||||||
end
|
end
|
||||||
redirect_to action: :index
|
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
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2017 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -11,8 +11,43 @@
|
|||||||
module AuthHelper
|
module AuthHelper
|
||||||
|
|
||||||
def require_user
|
def require_user
|
||||||
|
return unless session[:token].nil?
|
||||||
if !User.current.logged?
|
if !User.current.logged?
|
||||||
render :file => "public/401.html.erb", :status => :unauthorized, :layout =>true
|
render :file => "public/401.html.erb", :status => :unauthorized, :layout =>true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def allowed_to?(action)
|
||||||
|
return false if User.current.nil?
|
||||||
|
project = Project.find(params[:project_id])
|
||||||
|
return false if project.nil?
|
||||||
|
return true if User.current.allowed_to?(action, project)
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_permission(permission)
|
||||||
|
if !allowed_to?(permission)
|
||||||
|
render :file => "public/401.html.erb", :status => :unauthorized, :layout =>true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def global_check_permission(permission)
|
||||||
|
if !globaly_allowed_to?(permission)
|
||||||
|
render :file => "public/401.html.erb", :status => :unauthorized, :layout =>true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def globaly_allowed_to?( action)
|
||||||
|
return false if User.current.nil?
|
||||||
|
|
||||||
|
projects = Project.all
|
||||||
|
projects.each { |p|
|
||||||
|
if User.current.allowed_to?(action, p)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
}
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
module EstimateHelper
|
|
||||||
end
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
module InvoiceHelper
|
|
||||||
end
|
|
||||||
220
app/models/customer.rb
Normal file
220
app/models/customer.rb
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2020 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 Customer < ActiveRecord::Base
|
||||||
|
unloadable
|
||||||
|
|
||||||
|
has_many :issues
|
||||||
|
has_many :qbo_purchases
|
||||||
|
has_many :qbo_invoices
|
||||||
|
has_many :qbo_estimates
|
||||||
|
has_many :vehicles
|
||||||
|
|
||||||
|
attr_accessible :name, :notes, :email, :primary_phone, :mobile_phone, :phone_number
|
||||||
|
validates_presence_of :id, :name
|
||||||
|
|
||||||
|
self.primary_key = :id
|
||||||
|
|
||||||
|
# returns a human readable string
|
||||||
|
def to_s
|
||||||
|
return name
|
||||||
|
end
|
||||||
|
|
||||||
|
# Convenience Method
|
||||||
|
# returns the customer's email
|
||||||
|
def email
|
||||||
|
pull unless @details
|
||||||
|
begin
|
||||||
|
return @details.email_address.address
|
||||||
|
rescue
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Convenience Method
|
||||||
|
# Sets the email
|
||||||
|
def email=(s)
|
||||||
|
pull unless @details
|
||||||
|
@details.email_address = s
|
||||||
|
end
|
||||||
|
|
||||||
|
# Convenience Method
|
||||||
|
# returns the customer's primary phone
|
||||||
|
def primary_phone
|
||||||
|
pull unless @details
|
||||||
|
begin
|
||||||
|
return @details.primary_phone.free_form_number
|
||||||
|
rescue
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Convenience Method
|
||||||
|
# Updates the customer's primary phone number
|
||||||
|
def primary_phone=(n)
|
||||||
|
pull unless @details
|
||||||
|
pn = Quickbooks::Model::TelephoneNumber.new
|
||||||
|
pn.free_form_number = n
|
||||||
|
@details.primary_phone = pn
|
||||||
|
#update our locally stored number too
|
||||||
|
update_phone_number
|
||||||
|
end
|
||||||
|
|
||||||
|
# Convenience Method
|
||||||
|
# returns the customer's mobile phone
|
||||||
|
def mobile_phone
|
||||||
|
pull unless @details
|
||||||
|
begin
|
||||||
|
return @details.mobile_phone.free_form_number
|
||||||
|
rescue
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Convenience Method
|
||||||
|
# Updates the custome's mobile phone number
|
||||||
|
def mobile_phone=(n)
|
||||||
|
pull unless @details
|
||||||
|
pn = Quickbooks::Model::TelephoneNumber.new
|
||||||
|
pn.free_form_number = n
|
||||||
|
@details.mobile_phone = pn
|
||||||
|
#update our locally stored number too
|
||||||
|
update_mobile_phone_number
|
||||||
|
end
|
||||||
|
|
||||||
|
# update the localy stored phone number as a plain string with no special chars
|
||||||
|
def update_phone_number
|
||||||
|
begin
|
||||||
|
self.phone_number = self.primary_phone.tr('^0-9', '')
|
||||||
|
rescue
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# update the localy stored phone number as a plain string with no special chars
|
||||||
|
def update_mobile_phone_number
|
||||||
|
begin
|
||||||
|
self.mobile_phone_number = self.mobile_phone.tr('^0-9', '')
|
||||||
|
rescue
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Convenience Method
|
||||||
|
# Updates Both local DB name & QBO display_name
|
||||||
|
def name=(s)
|
||||||
|
pull unless @details
|
||||||
|
@details.display_name = s
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
# Magic Method
|
||||||
|
# Maps Get/Set methods to QBO customer object
|
||||||
|
def method_missing(sym, *arguments)
|
||||||
|
# Check to see if the method exists
|
||||||
|
if Quickbooks::Model::Customer.method_defined?(sym)
|
||||||
|
# download details if required
|
||||||
|
pull unless @details
|
||||||
|
method_name = sym.to_s
|
||||||
|
# Setter
|
||||||
|
if method_name[-1, 1] == "="
|
||||||
|
@details.method(method_name).call(arguments[0])
|
||||||
|
# Getter
|
||||||
|
else
|
||||||
|
return @details.method(method_name).call
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# proforms a bruteforce sync operation
|
||||||
|
# This needs to be simplified
|
||||||
|
def self.sync
|
||||||
|
service = Qbo.get_base(:customer)
|
||||||
|
|
||||||
|
# Sync ALL customers if the database is empty
|
||||||
|
#if count == 0
|
||||||
|
customers = service.all
|
||||||
|
#else
|
||||||
|
# last = Qbo.first.last_sync
|
||||||
|
# query = "Select Id, DisplayName From Customer"
|
||||||
|
# query << " Where Metadata.LastUpdatedTime >= '#{last.iso8601}' " if last
|
||||||
|
# customers = service.query(query)
|
||||||
|
#end
|
||||||
|
|
||||||
|
customers.each do |customer|
|
||||||
|
qbo_customer = Customer.find_or_create_by(id: customer.id)
|
||||||
|
if customer.active?
|
||||||
|
if not qbo_customer.name.eql? customer.display_name
|
||||||
|
qbo_customer.name = customer.display_name
|
||||||
|
qbo_customer.id = customer.id
|
||||||
|
qbo_customer.save_without_push
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if not qbo_customer.new_record?
|
||||||
|
qbo_customer.delete
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Searchs the database for a customer by name or phone number with out special chars
|
||||||
|
def self.search(search)
|
||||||
|
customers = where("name LIKE ? OR phone_number LIKE ? OR mobile_phone_number LIKE ?", "%#{search}%", "%#{search}%", "%#{search}%")
|
||||||
|
return customers.order(:name)
|
||||||
|
end
|
||||||
|
|
||||||
|
# proforms a bruteforce sync operation
|
||||||
|
# This needs to be simplified
|
||||||
|
def self.sync_by_id(id)
|
||||||
|
service = Qbo.get_base(:customer)
|
||||||
|
|
||||||
|
customer = service.fetch_by_id(id)
|
||||||
|
qbo_customer = Customer.find_or_create_by(id: customer.id)
|
||||||
|
if customer.active?
|
||||||
|
if not qbo_customer.name.eql? customer.display_name
|
||||||
|
qbo_customer.name = customer.display_name
|
||||||
|
qbo_customer.id = customer.id
|
||||||
|
qbo_customer.save_without_push
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if not qbo_customer.new_record?
|
||||||
|
qbo_customer.delete
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Push the updates
|
||||||
|
def save_with_push
|
||||||
|
begin
|
||||||
|
@details = Qbo.get_base(:customer).update(@details)
|
||||||
|
#raise "QBO Fault" if @details.fault?
|
||||||
|
self.id = @details.id
|
||||||
|
rescue Exception => e
|
||||||
|
errors.add(e.message)
|
||||||
|
end
|
||||||
|
save_without_push
|
||||||
|
end
|
||||||
|
|
||||||
|
alias_method :save_without_push, :save
|
||||||
|
alias_method :save, :save_with_push
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# pull the details
|
||||||
|
def pull
|
||||||
|
begin
|
||||||
|
raise Exception unless self.id
|
||||||
|
@details = Qbo.get_base(:customer).fetch_by_id(self.id)
|
||||||
|
rescue Exception => e
|
||||||
|
@details = Quickbooks::Model::Customer.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
23
app/models/customer_token.rb
Normal file
23
app/models/customer_token.rb
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2017 rick barrette
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
class 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'] || 'CONFIGURE_QBO__' + SecureRandom.uuid
|
||||||
|
|
||||||
|
def generate_token
|
||||||
|
self.token = SecureRandom.base64(15).tr('+/=lIO0', OAUTH_CONSUMER_SECRET)
|
||||||
|
end
|
||||||
|
end
|
||||||
34
app/models/line_item.rb
Normal file
34
app/models/line_item.rb
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2018 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 LineItem < ActiveRecord::Base
|
||||||
|
|
||||||
|
unloadable
|
||||||
|
|
||||||
|
belongs_to :issue
|
||||||
|
|
||||||
|
attr_accessible :amount, :description, :unit_price, :quantity, :item_id
|
||||||
|
validates_presence_of :amount, :description, :unit_price, :quantity
|
||||||
|
|
||||||
|
def add_to_invoice(invoice)
|
||||||
|
line_item = Quickbooks::Model::InvoiceLineItem.new
|
||||||
|
line_item.amount = amount
|
||||||
|
line_item.description = description
|
||||||
|
line_item.sales_item! do |detail|
|
||||||
|
detail.unit_price = unit_price
|
||||||
|
detail.quantity = quantity
|
||||||
|
detail.item_id = item_id # Item ID here... Where do i get this???
|
||||||
|
end
|
||||||
|
|
||||||
|
invoice.line_items << line_item
|
||||||
|
|
||||||
|
return invoice
|
||||||
|
end
|
||||||
|
end
|
||||||
37
app/models/payment.rb
Normal file
37
app/models/payment.rb
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2020 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).update(payment)
|
||||||
|
end
|
||||||
|
|
||||||
|
def save!
|
||||||
|
save
|
||||||
|
end
|
||||||
|
|
||||||
|
# Dummy stub to make validtions happy.
|
||||||
|
def update_attribute
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2020 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -11,33 +11,72 @@
|
|||||||
class Qbo < ActiveRecord::Base
|
class Qbo < ActiveRecord::Base
|
||||||
unloadable
|
unloadable
|
||||||
validates_presence_of :qb_token, :qb_secret, :company_id, :token_expires_at, :reconnect_token_at
|
validates_presence_of :qb_token, :qb_secret, :company_id, :token_expires_at, :reconnect_token_at
|
||||||
|
|
||||||
|
OAUTH_CONSUMER_KEY = Setting.plugin_redmine_qbo['settingsOAuthConsumerKey']
|
||||||
|
OAUTH_CONSUMER_SECRET = Setting.plugin_redmine_qbo['settingsOAuthConsumerSecret']
|
||||||
|
|
||||||
|
def self.get_client
|
||||||
|
oauth_params = {
|
||||||
|
site: "https://appcenter.intuit.com/connect/oauth2",
|
||||||
|
authorize_url: "https://appcenter.intuit.com/connect/oauth2",
|
||||||
|
token_url: "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer"
|
||||||
|
}
|
||||||
|
return OAuth2::Client.new(OAUTH_CONSUMER_KEY, OAUTH_CONSUMER_SECRET, oauth_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.get_oauth_consumer
|
||||||
|
# Quickbooks Config Info
|
||||||
|
return $qb_oauth_consumer
|
||||||
|
end
|
||||||
|
|
||||||
QB_KEY = Setting.plugin_redmine_qbo['settingsOAuthConsumerKey']
|
# Get a quickbooks base service object for type
|
||||||
QB_SECRET = Setting.plugin_redmine_qbo['settingsOAuthConsumerSecret']
|
|
||||||
|
|
||||||
# Quickbooks Config Info
|
|
||||||
$qb_oauth_consumer = OAuth::Consumer.new(QB_KEY, QB_SECRET, {
|
|
||||||
:site => "https://oauth.intuit.com",
|
|
||||||
:request_token_path => "/oauth/v1/get_request_token",
|
|
||||||
:authorize_url => "https://appcenter.intuit.com/Connect/Begin",
|
|
||||||
:access_token_path => "/oauth/v1/get_access_token"
|
|
||||||
})
|
|
||||||
|
|
||||||
# Configure quickbooks-ruby-base to access our database
|
|
||||||
Quickbooks::Base.configure do |c|
|
|
||||||
c.persistent_token = 'qb_token'
|
|
||||||
c.persistent_secret = 'qb_secret'
|
|
||||||
c.persistent_company_id = 'company_id'
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get a quickbooks base object for type
|
|
||||||
# @params type of base
|
# @params type of base
|
||||||
def self.get_base(type)
|
def self.get_base(type)
|
||||||
Quickbooks::Base.new(first, type)
|
oauth2_client = get_client
|
||||||
|
qbo = self.first
|
||||||
|
access_token = OAuth2::AccessToken.new(oauth2_client, qbo.qb_token , refresh_token: qbo.qb_secret)
|
||||||
|
|
||||||
|
# check to see if we need to refest the token
|
||||||
|
#if qbo.expire.to_date.past?
|
||||||
|
# new_access_token_object = access_token.refresh!
|
||||||
|
# qbo.token = new_access_token_object
|
||||||
|
# qbo.expire = 1.hour.from_now.utc
|
||||||
|
# qbo.save!
|
||||||
|
# access_token = new_access_token_object
|
||||||
|
#end
|
||||||
|
|
||||||
|
case type
|
||||||
|
when :item
|
||||||
|
return Quickbooks::Service::Item.new(:company_id => qbo.company_id, :access_token => access_token)
|
||||||
|
when :time_activity
|
||||||
|
return Quickbooks::Service::TimeActivity.new(:company_id => qbo.company_id, :access_token => access_token)
|
||||||
|
when :customer
|
||||||
|
return Quickbooks::Service::Customer.new(:company_id => qbo.company_id, :access_token => access_token)
|
||||||
|
when :invoice
|
||||||
|
return Quickbooks::Service::Invoice.new(:company_id => qbo.company_id, :access_token => access_token)
|
||||||
|
when :estimate
|
||||||
|
return Quickbooks::Service::Estimate.new(:company_id => qbo.company_id, :access_token => access_token)
|
||||||
|
when :account
|
||||||
|
return Quickbooks::Service::Account.new(:company_id => qbo.company_id, :access_token => access_token)
|
||||||
|
else
|
||||||
|
return access_token
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Get the QBO account
|
# Get the QBO account
|
||||||
def self.get_account
|
def self.get_account
|
||||||
first
|
first
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Updates last sync time stamp
|
||||||
|
def self.update_time_stamp
|
||||||
|
qbo = Qbo.first
|
||||||
|
qbo.last_sync = DateTime.now
|
||||||
|
qbo.save
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.last_sync
|
||||||
|
format_time(Qbo.first.last_sync)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,128 +0,0 @@
|
|||||||
#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 QboCustomer < ActiveRecord::Base
|
|
||||||
unloadable
|
|
||||||
|
|
||||||
has_many :issues
|
|
||||||
has_many :qbo_purchases
|
|
||||||
has_many :vehicles
|
|
||||||
|
|
||||||
attr_accessible :name
|
|
||||||
validates_presence_of :id, :name
|
|
||||||
|
|
||||||
after_initialize :get_details
|
|
||||||
|
|
||||||
self.primary_key = :id
|
|
||||||
|
|
||||||
# returns true if the customer is active
|
|
||||||
def active?
|
|
||||||
return @details.active? if @details
|
|
||||||
end
|
|
||||||
|
|
||||||
# returns a human readable string
|
|
||||||
def to_s
|
|
||||||
return name
|
|
||||||
end
|
|
||||||
|
|
||||||
# returns the customer's email
|
|
||||||
def email
|
|
||||||
begin
|
|
||||||
return @details.email_address.address
|
|
||||||
rescue
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# returns the customer's primary phone
|
|
||||||
def primary_phone
|
|
||||||
begin
|
|
||||||
return @details.primary_phone.free_form_number
|
|
||||||
rescue
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# returns the customer's mobile phone
|
|
||||||
def mobile_phone
|
|
||||||
begin
|
|
||||||
return @details.mobile_phone.free_form_number
|
|
||||||
rescue
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# returns the customer's notes
|
|
||||||
def notes
|
|
||||||
return @details.notes if @details
|
|
||||||
end
|
|
||||||
|
|
||||||
# updates the customer's notes in QBO
|
|
||||||
def notes=(s)
|
|
||||||
customer = get_customer(self.id)
|
|
||||||
customer.notes = s
|
|
||||||
get_base.update(customer)
|
|
||||||
end
|
|
||||||
|
|
||||||
# returns the bases QBO service for customers
|
|
||||||
def get_base
|
|
||||||
Qbo.get_base(:customer)
|
|
||||||
end
|
|
||||||
|
|
||||||
# returns the QBO customer
|
|
||||||
def get_customer (id)
|
|
||||||
get_base.find_by_id(id)
|
|
||||||
end
|
|
||||||
|
|
||||||
# proforms a bruteforce sync operation
|
|
||||||
# This needs to be simplified
|
|
||||||
def update_all
|
|
||||||
customers = get_base.service.all
|
|
||||||
|
|
||||||
transaction do
|
|
||||||
# Update the customer table
|
|
||||||
customers.each { |customer|
|
|
||||||
qbo_customer = QboCustomer.find_or_create_by(id: customer.id)
|
|
||||||
# only update if diffrent
|
|
||||||
if not qbo_customer.name == customer.display_name
|
|
||||||
qbo_customer.name = customer.display_name
|
|
||||||
qbo_customer.id = customer.id
|
|
||||||
qbo_customer.save!
|
|
||||||
end
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# remove deleted customers
|
|
||||||
where.not(customers.map(&:id)).destroy_all
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# init details
|
|
||||||
def get_details
|
|
||||||
if self.id
|
|
||||||
@details = get_customer(self.id)
|
|
||||||
update
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# update's the customers name if updated
|
|
||||||
def update
|
|
||||||
begin
|
|
||||||
if not self.name == @detils.display_name
|
|
||||||
self.name = @details.display_name
|
|
||||||
self.save
|
|
||||||
end
|
|
||||||
rescue
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2020 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -18,8 +18,8 @@ class QboEmployee < ActiveRecord::Base
|
|||||||
Qbo.get_base(:employee)
|
Qbo.get_base(:employee)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.update_all
|
def self.sync
|
||||||
employees = get_base.service.all
|
employees = get_base.all
|
||||||
|
|
||||||
transaction do
|
transaction do
|
||||||
# Update the item table
|
# Update the item table
|
||||||
@@ -30,8 +30,13 @@ class QboEmployee < ActiveRecord::Base
|
|||||||
qbo_employee.save!
|
qbo_employee.save!
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
end
|
||||||
#remove deleted employees
|
|
||||||
where.not(employees.map(&:id)).destroy_all
|
def self.sync_by_id(id)
|
||||||
|
employee = get_base.fetch_by_id(id)
|
||||||
|
qbo_employee = find_or_create_by(id: employee.id)
|
||||||
|
qbo_employee.name = employee.display_name
|
||||||
|
qbo_employee.id = employee.id
|
||||||
|
qbo_employee.save!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2020 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -10,36 +10,49 @@
|
|||||||
|
|
||||||
class QboEstimate < ActiveRecord::Base
|
class QboEstimate < ActiveRecord::Base
|
||||||
unloadable
|
unloadable
|
||||||
has_many :issues
|
|
||||||
attr_accessible :doc_number
|
|
||||||
validates_presence_of :id, :doc_number
|
|
||||||
|
|
||||||
|
has_and_belongs_to_many :issues
|
||||||
|
belongs_to :customer
|
||||||
|
attr_accessible :doc_number, :id
|
||||||
|
validates_presence_of :doc_number, :id
|
||||||
|
self.primary_key = :id
|
||||||
|
|
||||||
|
# return the QBO Estimate service
|
||||||
def self.get_base
|
def self.get_base
|
||||||
Qbo.get_base(:estimate)
|
Qbo.get_base(:estimate)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.update_all
|
# sync all estimates
|
||||||
estimates = get_base.service.all
|
def self.sync
|
||||||
|
estimates = get_base.all
|
||||||
# Update the item table
|
estimates.each { |estimate|
|
||||||
transaction do
|
process_estimate(estimate)
|
||||||
estimates.each { |estimate|
|
}
|
||||||
qbo_estimate = QboEstimate.find_or_create_by(id: estimate.id)
|
|
||||||
qbo_estimate.doc_number = estimate.doc_number
|
|
||||||
qbo_estimate.id = estimate.id
|
|
||||||
qbo_estimate.save!
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
#remove deleted estimates
|
#remove deleted estimates
|
||||||
where.not(estimates.map(&:id)).destroy_all
|
where.not(estimates.map(&:id)).destroy_all
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# sync only one estimate
|
||||||
|
def self.sync_by_id(id)
|
||||||
|
process_estimate(get_base.fetch_by_id(id))
|
||||||
|
end
|
||||||
|
|
||||||
|
# update an estimate
|
||||||
def self.update(id)
|
def self.update(id)
|
||||||
# Update the item table
|
# Update the item table
|
||||||
estimate = get_base.service.fetch_by_id(id)
|
estimate = get_base.fetch_by_id(id)
|
||||||
qbo_estimate = QboEstimate.find_or_create_by(id: id)
|
qbo_estimate = find_or_create_by(id: id)
|
||||||
qbo_estimate.doc_number = estimate.doc_number
|
qbo_estimate.doc_number = estimate.doc_number
|
||||||
qbo_estimate.save!
|
qbo_estimate.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
# process an estimate into the database
|
||||||
|
def self.process_estimate(estimate)
|
||||||
|
qbo_estimate = find_or_create_by(id: estimate.id)
|
||||||
|
qbo_estimate.doc_number = estimate.doc_number
|
||||||
|
qbo_estimate.customer_id = estimate.customer_ref.value
|
||||||
|
qbo_estimate.id = estimate.id
|
||||||
|
qbo_estimate.save!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2020 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -10,37 +10,146 @@
|
|||||||
|
|
||||||
class QboInvoice < ActiveRecord::Base
|
class QboInvoice < ActiveRecord::Base
|
||||||
unloadable
|
unloadable
|
||||||
has_many :issues
|
|
||||||
attr_accessible :doc_number
|
has_and_belongs_to_many :issues
|
||||||
validates_presence_of :id, :doc_number
|
belongs_to :customer
|
||||||
|
attr_accessible :doc_number, :id
|
||||||
|
validates_presence_of :doc_number, :id
|
||||||
|
self.primary_key = :id
|
||||||
|
|
||||||
def self.get_base
|
def self.get_base
|
||||||
Qbo.get_base(:invoice)
|
Qbo.get_base(:invoice)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.update_all
|
# sync ALL the invoices
|
||||||
#Pull the invoices from the quickbooks server
|
def self.sync
|
||||||
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.all
|
||||||
|
else
|
||||||
|
invoices = get_base.query()
|
||||||
|
end
|
||||||
|
|
||||||
# Update the invoice table
|
# Update the invoice table
|
||||||
transaction do
|
invoices.each { | invoice |
|
||||||
invoices.each { | invoice |
|
process_invoice 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!
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
#remove deleted invoices
|
|
||||||
where.not(invoices.map(&:id)).destroy_all
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.update(id)
|
#sync by invoice ID
|
||||||
# Update the item table
|
def self.sync_by_id(id)
|
||||||
invoice = get_base.service.fetch_by_id(id)
|
#update the information in the database
|
||||||
qbo_invoice = find_or_create_by(id: id)
|
invoice = get_base.fetch_by_id(id)
|
||||||
qbo_invoice.doc_number = invoice.doc_number
|
process_invoice invoice
|
||||||
qbo_invoice.save!
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Attach the invoice to the issue
|
||||||
|
def self.attach_to_issue(issue, invoice)
|
||||||
|
return if issue.nil?
|
||||||
|
|
||||||
|
# skip this issue if the issue customer is not the same as the invoice customer
|
||||||
|
return if issue.customer_id != invoice.customer_ref.value.to_i
|
||||||
|
|
||||||
|
# Load the invoice into the database
|
||||||
|
qbo_invoice = QboInvoice.find_or_create_by(id: invoice.id)
|
||||||
|
qbo_invoice.doc_number = invoice.doc_number
|
||||||
|
qbo_invoice.id = invoice.id
|
||||||
|
qbo_invoice.customer_id = invoice.customer_ref
|
||||||
|
qbo_invoice.save!
|
||||||
|
|
||||||
|
unless issue.qbo_invoices.include?(qbo_invoice)
|
||||||
|
issue.qbo_invoices << qbo_invoice
|
||||||
|
issue.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
compare_custom_fields(issue, invoice)
|
||||||
|
end
|
||||||
|
|
||||||
|
# processes the invoice into the system
|
||||||
|
def self.process_invoice(invoice)
|
||||||
|
# Check the private notes
|
||||||
|
if not invoice.private_note.nil?
|
||||||
|
invoice.private_note.scan(/#(\w+)/).flatten.each { |issue|
|
||||||
|
attach_to_issue(Issue.find_by_id(issue.to_i), invoice)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
# 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|
|
||||||
|
attach_to_issue(Issue.find_by_id(issue.to_i), invoice)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.compare_custom_fields(issue, invoice)
|
||||||
|
is_changed = false
|
||||||
|
|
||||||
|
# update the invoive custom fields with infomation from the work ticket if available
|
||||||
|
invoice.custom_fields.each { |cf|
|
||||||
|
|
||||||
|
# TODO Add some hooks here
|
||||||
|
|
||||||
|
# VIN from the attached vehicle
|
||||||
|
begin
|
||||||
|
if cf.name.eql? "VIN"
|
||||||
|
vin = Vehicle.find(issue.vehicles_id).vin
|
||||||
|
break if vin.nil?
|
||||||
|
if not cf.string_value.to_s.eql? vin
|
||||||
|
cf.string_value = vin.to_s
|
||||||
|
is_changed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
#do nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
# Custom Values
|
||||||
|
begin
|
||||||
|
value = issue.custom_values.find_by(custom_field_id: CustomField.find_by_name(cf.name).id)
|
||||||
|
|
||||||
|
# Check to see if the value is blank...
|
||||||
|
if not value.value.to_s.blank?
|
||||||
|
# Check to see if the value is diffrent
|
||||||
|
if not cf.string_value.to_s.eql? value.value.to_s
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# Use the max milage
|
||||||
|
elsif 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
|
||||||
|
else
|
||||||
|
# Everything else
|
||||||
|
cf.string_value = value.value.to_s
|
||||||
|
is_changed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
# Nothing to do here, there is no match
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO Add some hooks here
|
||||||
|
|
||||||
|
# Push updates
|
||||||
|
#invoice.sync_token += 1 if is_changed
|
||||||
|
get_base.update(invoice) if is_changed
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2020 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -13,25 +13,35 @@ class QboItem < ActiveRecord::Base
|
|||||||
has_many :issues
|
has_many :issues
|
||||||
attr_accessible :name
|
attr_accessible :name
|
||||||
validates_presence_of :id, :name
|
validates_presence_of :id, :name
|
||||||
|
|
||||||
|
self.primary_key = :id
|
||||||
|
|
||||||
def self.get_base
|
def self.get_base
|
||||||
Qbo.get_base(:item)
|
Qbo.get_base(:item)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.update_all
|
def self.sync
|
||||||
items = get_base.service.find_by(:type, "Service")
|
last = Qbo.first.last_sync
|
||||||
|
|
||||||
transaction do
|
query = "SELECT Id, Name FROM Item WHERE Type = 'Service' "
|
||||||
# Update the item table
|
query << " AND Metadata.LastUpdatedTime >= '#{last.iso8601}' " if last
|
||||||
items.each { |item|
|
|
||||||
qbo_item = QboItem.find_or_create_by(id: item.id)
|
if count == 0
|
||||||
qbo_item.name = item.name
|
items = get_base.all
|
||||||
qbo_item.id = item.id
|
else
|
||||||
qbo_item.save!
|
items = get_base.query(query)
|
||||||
|
end
|
||||||
|
|
||||||
|
unless items.count = 0
|
||||||
|
items.find_by(:type, "Service").each { |i|
|
||||||
|
qbo_item = QboItem.find_or_create_by(id: i.id)
|
||||||
|
qbo_item.name = i.name
|
||||||
|
qbo_item.id = i.id
|
||||||
|
qbo_item.save
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
#remove deleted items
|
# QboItem.where.not(items.map(&:id)).destroy_all
|
||||||
where.not(items.map(&:id)).destroy_all
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2020 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -19,12 +19,12 @@ class QboPurchase < ActiveRecord::Base
|
|||||||
Qbo.get_base(:purchase)
|
Qbo.get_base(:purchase)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.get_purchase(id)
|
def get_purchase(id)
|
||||||
get_base.service.find_by_id(id)
|
get_base.find_by_id(id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.update_all
|
def self.sync
|
||||||
QboPurchase.get_base.service.all.each { |purchase|
|
QboPurchase.get_base.all.each { |purchase|
|
||||||
|
|
||||||
purchase.line_items.all? { |line_item|
|
purchase.line_items.all? { |line_item|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2017 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -11,28 +11,40 @@
|
|||||||
class Vehicle < ActiveRecord::Base
|
class Vehicle < ActiveRecord::Base
|
||||||
|
|
||||||
unloadable
|
unloadable
|
||||||
belongs_to :qbo_customer
|
|
||||||
|
|
||||||
attr_accessible :year, :make, :model, :qbo_customer_id, :notes, :vin
|
belongs_to :customer
|
||||||
validates_presence_of :year, :make, :model, :qbo_customer_id
|
has_many :issues, :foreign_key => 'vehicles_id'
|
||||||
|
|
||||||
before_validation :decode_vin
|
attr_accessible :year, :make, :model, :customer_id, :notes, :vin
|
||||||
after_initialize :get_details
|
|
||||||
|
validates_presence_of :customer
|
||||||
|
validates :vin, uniqueness: true
|
||||||
|
before_save :decode_vin
|
||||||
|
#after_find :get_details
|
||||||
|
|
||||||
|
self.primary_key = :id
|
||||||
|
|
||||||
# returns a human readable string
|
# returns a human readable string
|
||||||
def to_s
|
def to_s
|
||||||
return "#{self.year} #{self.make} #{self.model}"
|
if year.nil? or make.nil? or model.nil?
|
||||||
|
return "#{vin}"
|
||||||
|
else
|
||||||
|
split_vin = vin.scan(/.{1,9}/)
|
||||||
|
return "#{year} #{make} #{model} - #{split_vin[1]}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# returns the raw JSON details from EMUNDS
|
# returns the raw JSON details from EMUNDS
|
||||||
def details
|
def details
|
||||||
|
get_details if @details.nil?
|
||||||
return @details
|
return @details
|
||||||
end
|
end
|
||||||
|
|
||||||
# returns the style of the vehicle
|
# returns the style of the vehicle
|
||||||
def style
|
def style
|
||||||
|
get_details if @details.nil?
|
||||||
begin
|
begin
|
||||||
return @details['years'][0]['styles'][0]['name'] if @details
|
return @details.trim if @details
|
||||||
rescue
|
rescue
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
@@ -40,49 +52,74 @@ class Vehicle < ActiveRecord::Base
|
|||||||
|
|
||||||
# returns the drive of the vehicle i.e. 2 wheel, 4 wheel, ect.
|
# returns the drive of the vehicle i.e. 2 wheel, 4 wheel, ect.
|
||||||
def drive
|
def drive
|
||||||
return @details['drivenWheels'] if @details
|
#todo fix this
|
||||||
|
#return @details.drive_type if @details
|
||||||
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# returns the number of doors of the vehicle
|
# returns the number of doors of the vehicle
|
||||||
def doors
|
def doors
|
||||||
return @details['numOfDoors'] if @details
|
get_details if @details.nil?
|
||||||
|
return @details.doors if @details
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
# Force Upper Case for make numbers
|
||||||
|
def make=(val)
|
||||||
# init method to pull JSON details from Edmunds
|
# The to_s is in case you get nil/non-string
|
||||||
def get_details
|
write_attribute(:make, val.to_s.titleize)
|
||||||
if self.vin?
|
|
||||||
@details = JSON.parse get_decoder.full(self.vin)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# returns the Edmunds decoder service
|
# Force Upper Case for model numbers
|
||||||
def get_decoder
|
def model=(val)
|
||||||
#TODO API Code via Settings
|
# The to_s is in case you get nil/non-string
|
||||||
return decoder = Edmunds::Vin.new('2dheutzvhxs28dzukx5tgu47')
|
write_attribute(:model, val.to_s.titleize)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Force Upper Case for VIN numbers
|
||||||
|
def vin=(val)
|
||||||
|
#strip VIN of all illegal chars (for barcode scanner)
|
||||||
|
val = val.to_s.upcase.gsub(/[^A-HJ-NPR-Za-hj-npr-z\d]+/,"")
|
||||||
|
write_attribute(:vin, val)
|
||||||
|
end
|
||||||
|
|
||||||
|
# search for a vin
|
||||||
|
def self.search(search)
|
||||||
|
where("vin LIKE ?", "%#{search}%")
|
||||||
|
end
|
||||||
|
|
||||||
# decodes a vin and updates self
|
# decodes a vin and updates self
|
||||||
def decode_vin
|
def decode_vin
|
||||||
get_details
|
get_details
|
||||||
if self.vin?
|
if @details
|
||||||
details
|
begin
|
||||||
self.year = @details['years'][0]['year']
|
self.year = @details.year unless @details.year.nil?
|
||||||
self.make = @details['make']['name']
|
self.make = @details.make unless @details.make.nil?
|
||||||
self.model = @details['model']['name']
|
self.model = @details.model unless @details.model.nil?
|
||||||
|
rescue Exception => e
|
||||||
|
errors.add(:vin, e.message)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
self.name = to_s
|
self.name = to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
# makes a squishvin
|
private
|
||||||
# https://api.edmunds.com/api/vehicle/v2/squishvins/#{vin}/?fmt=json&api_key=#{ENV['edmunds_key']}
|
|
||||||
def vin_squish
|
# init method to pull JSON details from Edmunds
|
||||||
if not self.vin? or self.vin.size < 11
|
def get_details
|
||||||
# this is to go ahead and query the API, letting them handle the error. :P
|
if self.vin?
|
||||||
return '1000000000A'
|
#validate the vin before calling a remote server
|
||||||
|
validation = NhtsaVin.validate(self.vin)
|
||||||
|
begin
|
||||||
|
#if the vin validation failed, raise an exception and exit
|
||||||
|
raise RuntimeError, validation.error unless validation.valid?
|
||||||
|
# query NHTSA for details on the vin
|
||||||
|
query = NhtsaVin.get(self.vin)
|
||||||
|
raise RuntimeError, query.error unless query.valid?
|
||||||
|
@details = query.response
|
||||||
|
rescue Exception => e
|
||||||
|
errors.add(:vin, e.message)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
v = self.vin[0,11]
|
|
||||||
return v.slice(0,8) + v.slice(9,11)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,34 +1,53 @@
|
|||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
|
||||||
<th>Customer</th>
|
|
||||||
<td><%= @customer.name %></td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
<td><%= @customer.email %></td>
|
<td><%= customer.email %></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Primary Phone</th>
|
<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>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Mobile Phone</th>
|
<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>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Notes</th>
|
<th>Notes</th>
|
||||||
<td><%= @customer.notes %></td>
|
<td><%= customer.notes %></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td/>
|
|
||||||
<td>
|
<td>
|
||||||
<%= button_to "Edit", edit_customer_path(@customer), method: :get%>
|
<%= button_to "Edit Customer", edit_customer_path(customer), method: :get%>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
60
app/views/customers/_form.html.erb
Normal file
60
app/views/customers/_form.html.erb
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<div class="row">
|
||||||
|
<div class="span6 columns">
|
||||||
|
<fieldset>
|
||||||
|
|
||||||
|
<%= form_for @customer do |f| %>
|
||||||
|
|
||||||
|
<div class="clearfix">
|
||||||
|
Display Name:
|
||||||
|
<div class="input">
|
||||||
|
<%= f.text_field :name, :required => true %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="clearfix">
|
||||||
|
Phone Number:
|
||||||
|
<div class="input">
|
||||||
|
<%= f.telephone_field :primary_phone %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="clearfix">
|
||||||
|
Mobile Phone Number:
|
||||||
|
<div class="input">
|
||||||
|
<%= f.telephone_field :mobile_phone %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="clearfix">
|
||||||
|
Email:
|
||||||
|
<div class="input">
|
||||||
|
<%= f.email_field :email %>
|
||||||
|
</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 @customer.new_record? %>
|
||||||
|
<%= content_tag 'span', :id => "issue_description_and_toolbar", :style => (@customer.new_record? ? nil : 'display:none') do %>
|
||||||
|
<%= 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>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<%= f.submit %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
6
app/views/customers/_search.html.erb
Normal file
6
app/views/customers/_search.html.erb
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<%= form_tag(customers_path, :method => "get", id: "search-form") do %>
|
||||||
|
<%= text_field_tag :search, params[:search], placeholder: "Search Customers" %>
|
||||||
|
<%= submit_tag "Search" %>
|
||||||
|
<% end %>
|
||||||
|
<%= button_to "New Customer", new_customer_path, method: :get%>
|
||||||
|
<%= button_to "Sync", qbo_sync_path, method: :get%>
|
||||||
2
app/views/customers/_sidebar.html.erb
Normal file
2
app/views/customers/_sidebar.html.erb
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<h3>Customers</h3>
|
||||||
|
<%= render :partial => 'customers/search' %>
|
||||||
3
app/views/customers/edit.html.erb
Normal file
3
app/views/customers/edit.html.erb
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<h1>Edit Customer</h1>
|
||||||
|
<br/>
|
||||||
|
<%= render :partial => 'customers/form' %>
|
||||||
1
app/views/customers/filter_estimates_by_customer.js.erb
Normal file
1
app/views/customers/filter_estimates_by_customer.js.erb
Normal file
@@ -0,0 +1 @@
|
|||||||
|
$('select#issue_qbo_estimate_id').html('<%= j content_tag(:option,'',:value=>"")+options_from_collection_for_select(@filtered_estimates, :id, :doc_number) %>');
|
||||||
1
app/views/customers/filter_vehicles_by_customer.js.erb
Normal file
1
app/views/customers/filter_vehicles_by_customer.js.erb
Normal file
@@ -0,0 +1 @@
|
|||||||
|
$('select#issue_vehicles_id').html('<%= j content_tag(:option,'',:value=>"")+options_from_collection_for_select(@filtered_vehicles, :id, :to_s) %>');
|
||||||
@@ -1,20 +1,24 @@
|
|||||||
<h1>Customers</h1>
|
<h2>Customers <span style="float:right"> <%= render :partial => 'customers/search' %> </span> </h2>
|
||||||
<br/>
|
<% if @customers.present? %>
|
||||||
<% @customers.each do |c| %>
|
<br/>
|
||||||
<div class="row">
|
<% @customers.each do |c| %>
|
||||||
<div class="span6 columns">
|
<div class="row">
|
||||||
<fieldset>
|
<div class="span6 columns">
|
||||||
<% @customer = c %>
|
<%= link_to c, customer_path(c.id) %>
|
||||||
<%= render :partial => 'customers/details' %>
|
</div>
|
||||||
<%= button_to "Show", customer_path(c.id), method: :get %>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<% end %>
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<br/>
|
<p>Matching <%= @customers.count %> Customers </p>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<%= will_paginate @customers %>
|
<%= 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>
|
||||||
|
<%= render :partial => 'qbo/stats' %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
3
app/views/customers/new.html.erb
Normal file
3
app/views/customers/new.html.erb
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<h2>New Customer</h2>
|
||||||
|
<br/>
|
||||||
|
<%= render :partial => 'customers/form' %>
|
||||||
@@ -1,5 +1,24 @@
|
|||||||
<h1>Customer Detail</h1>
|
<h2>Customer #<%= @customer.id %> - <%= @customer.name %> </h2>
|
||||||
<br/>
|
<br/>
|
||||||
<%= render :partial => 'customers/details' %>
|
|
||||||
<br/>
|
<div class="subject">
|
||||||
<%= render :partial => 'vehicles/list' %>
|
<div><h3>Details:</h3></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="attributes">
|
||||||
|
|
||||||
|
<div class="splitcontent">
|
||||||
|
<div class="splitcontentleft">
|
||||||
|
<%= render :partial => 'customers/details', locals: {customer: @customer} %>
|
||||||
|
</div>
|
||||||
|
<div class="splitcontentleft">
|
||||||
|
<h4>Vehicles:</h4>
|
||||||
|
<%= render :partial => 'vehicles/list' %>
|
||||||
|
<%= button_to "New Vehicle", new_customer_vehicle_path(@customer), method: :get %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<h2>Issues:</h2>
|
||||||
|
<%= render :partial => 'issues/list_simple', locals: {issues: @issues} %>
|
||||||
|
</div>
|
||||||
|
|||||||
109
app/views/customers/view.html.erb
Normal file
109
app/views/customers/view.html.erb
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<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) %>
|
||||||
|
This customer link expires in <%= distance_of_time_in_words(Time.now, @token.expires_at) %>
|
||||||
|
</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_full_width_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}" %>
|
||||||
29
app/views/issues/_list_simple.html.erb
Normal file
29
app/views/issues/_list_simple.html.erb
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<% if issues && issues.any? %>
|
||||||
|
<%= form_tag({}) do %>
|
||||||
|
<table class="list issues">
|
||||||
|
<thead><tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th><%=l(:field_project)%></th>
|
||||||
|
<th><%=l(:field_tracker)%></th>
|
||||||
|
<th><%=l(:field_subject)%></th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<% for issue in issues %>
|
||||||
|
<tr id="issue-<%= h(issue.id) %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= issue.css_classes %>">
|
||||||
|
<td class="id">
|
||||||
|
<%= check_box_tag("ids[]", issue.id, false, :style => 'display:none;', :id => nil) %>
|
||||||
|
<%= link_to(issue.id, issue_path(issue)) %>
|
||||||
|
</td>
|
||||||
|
<td class="project"><%= link_to_project(issue.project) %></td>
|
||||||
|
<td class="tracker"><%= issue.tracker %></td>
|
||||||
|
<td class="subject">
|
||||||
|
<%= link_to(issue.subject.truncate(60), issue_path(issue)) %> (<%= issue.status %>)
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<% end %>
|
||||||
|
<% else %>
|
||||||
|
<p class="nodata"><%= l(:label_no_data) %></p>
|
||||||
|
<% end %>
|
||||||
42
app/views/payments/_form.html.erb
Normal file
42
app/views/payments/_form.html.erb
Normal 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>
|
||||||
3
app/views/payments/new.html.erb
Normal file
3
app/views/payments/new.html.erb
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<h1>New Payment</h1>
|
||||||
|
<br/>
|
||||||
|
<%= render :partial => 'payments/form' %>
|
||||||
35
app/views/qbo/_issues_show_details.html.erb
Normal file
35
app/views/qbo/_issues_show_details.html.erb
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<div class="splitcontent">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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_notes attribute">
|
||||||
|
<div class="label"><span>Notes</span>:</div>
|
||||||
|
<div class="value"><%=notes%></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
1
app/views/qbo/_last_sync.html.erb
Normal file
1
app/views/qbo/_last_sync.html.erb
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<b>Last Sync: </b> <%= Qbo.last_sync if Qbo.exists? %>
|
||||||
@@ -10,10 +10,19 @@ The above copyright notice and this permission notice shall be included in all c
|
|||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
<!-- somewhere in your document include the Javascript -->
|
||||||
|
<script type="text/javascript" src="https://appcenter.intuit.com/Content/IA/intuit.ipp.anywhere.js"></script>
|
||||||
|
|
||||||
|
<!-- configure the Intuit object: 'grantUrl' is a URL in your application which kicks off the flow, see below -->
|
||||||
|
<script>
|
||||||
|
intuit.ipp.anywhere.setup({menuProxy: '/path/to/blue-dot', grantUrl: '<%= qbo_authenticate_url %>'});
|
||||||
|
</script>
|
||||||
|
|
||||||
<table >
|
<table >
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>OAuth Consumer Key</th>
|
<th>Intuit QBO OAuth Consumer Key</th>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" style="width:350px" id="settingsOAuthConsumerKey"
|
<input type="text" style="width:350px" id="settingsOAuthConsumerKey"
|
||||||
value="<%= settings['settingsOAuthConsumerKey'] %>"
|
value="<%= settings['settingsOAuthConsumerKey'] %>"
|
||||||
@@ -22,21 +31,22 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>OAuth Consumer Secret</th>
|
<th>Intuit QBO OAuth Consumer Secret</th>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" style="width:350px" id="settingsOAuthConsumerSecret"
|
<input type="text" style="width:350px" id="settingsOAuthConsumerSecret"
|
||||||
value="<%= settings['settingsOAuthConsumerSecret'] %>"
|
value="<%= settings['settingsOAuthConsumerSecret'] %>"
|
||||||
name="settings[settingsOAuthConsumerSecret]" >
|
name="settings[settingsOAuthConsumerSecret]" >
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
</tbody>
|
<tr>
|
||||||
</table>
|
<th>Intuit QBO Webhook Token</th>
|
||||||
|
<td>
|
||||||
<br/>
|
<input type="text" style="width:350px" id="settingsWebhookToken"
|
||||||
|
value="<%= settings['settingsWebhookToken'] %>"
|
||||||
<table>
|
name="settings[settingsWebhookToken]" >
|
||||||
<tbody>
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Token Expires At</th>
|
<th>Token Expires At</th>
|
||||||
@@ -54,5 +64,36 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|||||||
<br/>
|
<br/>
|
||||||
Note: You need to authenticate after saving your key and secret above
|
Note: You need to authenticate after saving your key and secret above
|
||||||
<br/>
|
<br/>
|
||||||
|
<br/>
|
||||||
<%= link_to "Authenticate", qbo_authenticate_path, :method => :get %>
|
|
||||||
|
<!-- 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>
|
||||||
|
|||||||
1
app/views/qbo/_stats.html.erb
Normal file
1
app/views/qbo/_stats.html.erb
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<%= Customer.count %> Customers - <%= render :partial => 'qbo/last_sync' %>
|
||||||
@@ -12,44 +12,31 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h1> Redmine Quickbooks</h1>
|
<h1> Redmine Quickbooks</h1>
|
||||||
<%= form_for @qbo do |f|%>
|
|
||||||
<div>
|
|
||||||
<%= f.label "Customer Count:"+@qbo_customer_count.to_s%>
|
|
||||||
<br/>
|
|
||||||
<%= f.select :qbo_customer_id, QboCustomer.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/>
|
||||||
<br/>
|
|
||||||
<%= link_to "Sync", qbo_sync_path %>
|
<div>
|
||||||
|
<b>Last Sync: </b> <%= Qbo.last_sync if Qbo.exists? %>
|
||||||
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,45 +1,37 @@
|
|||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Customer</th>
|
<th>Customer</th>
|
||||||
<td><%= @customer %></td>
|
<td><%= link_to vehicle.customer.name, customer_path(vehicle.customer) %></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Vehicle</th>
|
<th>Vehicle</th>
|
||||||
<td><%= @vehicle.to_s %></td>
|
<td><%= vehicle.to_s %></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>VIN</th>
|
<th>VIN</th>
|
||||||
<td><%= @vehicle.vin %></td>
|
<td><%= @vin[0] if @vin %><b><%=@vin[1] if @vin%></b></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
|
||||||
<th>Style</th>
|
|
||||||
<td><%= @vehicle.style %></td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<th>Drive</th>
|
|
||||||
<td><%= @vehicle.drive %></td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<th>Doors</th>
|
|
||||||
<td><%= @vehicle.doors %></td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Notes</th>
|
<th>Notes</th>
|
||||||
<td><%= @vehicle.notes %></td>
|
<td><%= vehicle.notes %></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Issues</th>
|
||||||
|
<td><%= vehicle.issues.count %></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td/>
|
<td/>
|
||||||
<td>
|
<td>
|
||||||
<%= button_to "Edit", edit_vehicle_path(@vehicle), method: :get%>
|
|
||||||
<%= button_to "Delete", @vehicle, method: :delete, data: {confirm: "You sure?"} %>
|
<%= button_to "Edit", edit_vehicle_path(vehicle), method: :get%>
|
||||||
|
<%= button_to "Delete", vehicle, method: :delete, data: {confirm: "You sure?"} %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
<%= @f.collection_select :vehicle_id, @customer.vehicles.order(:year), :id, :vin, include_blank: true, :selected => @vehicle%>
|
|
||||||
Partial Test
|
|
||||||
@@ -4,44 +4,45 @@
|
|||||||
|
|
||||||
<%= form_for @vehicle do |f| %>
|
<%= form_for @vehicle do |f| %>
|
||||||
<div class="clearfix">
|
<div class="clearfix">
|
||||||
Customer:
|
Customer:
|
||||||
<div class="input">
|
<div class="input">
|
||||||
<%= f.collection_select :qbo_customer_id, QboCustomer.order(:name), :id, :name, include_blank: true, :selected => @customer, :required => true%>
|
<%= f.autocomplete_field :customer, autocomplete_customer_name_customers_path, :value => @customer.name, :update_elements => {:id => '#customer_id', :value => '#issue_customer'}, :required => true %>
|
||||||
|
<%= f.hidden_field :customer_id, :id => "customer_id", :value => @customer.id %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="clearfix">
|
<div class="clearfix">
|
||||||
Year:
|
Year:
|
||||||
<div class="input">
|
<div class="input">
|
||||||
<%= f.text_field :year, :required => true %>
|
<%= f.number_field :year %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="clearfix">
|
<div class="clearfix">
|
||||||
Make:
|
Make:
|
||||||
<div class="input">
|
<div class="input">
|
||||||
<%= f.text_field :make, :required => true %>
|
<%= f.text_field :make %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="clearfix">
|
<div class="clearfix">
|
||||||
Model:
|
Model:
|
||||||
<div class="input">
|
<div class="input">
|
||||||
<%= f.text_field :model, :required => true %>
|
<%= f.text_field :model %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="clearfix">
|
<div class="clearfix">
|
||||||
VIN:
|
VIN:
|
||||||
<div class="input">
|
<div class="input">
|
||||||
<%= f.text_field :vin %>
|
<%= f.text_field :vin , :autofocus => true %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="clearfix">
|
<div class="clearfix">
|
||||||
Notes:
|
Notes:
|
||||||
<div class="input">
|
<div class="input">
|
||||||
<%= f.text_area :notes, :rows => 6, :class => 'wiki-edit', :accesskey => accesskey(:edit), :cols => 60%>
|
<%= f.text_area :notes, :cols => 60, :rows => 10, :no_label => true %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,28 @@
|
|||||||
<% @vehicles.each do |vehicle| %>
|
<% if @vehicles.present? %>
|
||||||
<div class="row">
|
|
||||||
<div class="span6 columns">
|
<% @vehicles.each do |vehicle| %>
|
||||||
<fieldset>
|
<div class="row">
|
||||||
<% @customer = vehicle.qbo_customer.name if vehicle.qbo_customer %>
|
<div>
|
||||||
<% @vehicle = vehicle %>
|
<b><%= link_to "##{vehicle.id}", vehicle_path(vehicle) %> </b>
|
||||||
<%= render :partial => 'vehicles/details' %>
|
</div>
|
||||||
</fieldset>
|
|
||||||
|
<div>
|
||||||
|
<%= vehicle.to_s %>
|
||||||
|
<br/>
|
||||||
|
<%= vehicle.customer %>
|
||||||
|
<br/>
|
||||||
|
<%= vehicle.vin %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<br/>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<%= will_paginate @vehicles %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p>Matching <%= @vehicles.count %> Vehicles </p>
|
||||||
|
|
||||||
|
<% else %>
|
||||||
|
<p>There are no vehicles containing the term(s) <%= params[:search] %>.</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="actions">
|
|
||||||
<%= will_paginate @vehicles %>
|
|
||||||
<%= button_to "New", new_vehicle_path, method: :get %>
|
|
||||||
</div>
|
|
||||||
|
|||||||
4
app/views/vehicles/_search.html.erb
Normal file
4
app/views/vehicles/_search.html.erb
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<%= 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 %>
|
||||||
1
app/views/vehicles/_vehicle.html.erb
Normal file
1
app/views/vehicles/_vehicle.html.erb
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<option value="<%= vehicle.id %>"><%= vehicle.to_s.titleize %></option>
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
<h1>Customer Vehicles</h1>
|
<h2>Customer Vehicles <span style="float:right"> <%= render :partial => 'vehicles/search' %> </span> </h2>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<%= render :partial => 'vehicles/list' %>
|
<%= render :partial => 'vehicles/list' %>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<h1>New Customer Vehicle</h1>
|
<h2>New Customer Vehicle</h2>
|
||||||
<br/>
|
<br/>
|
||||||
<%= render :partial => 'vehicles/form' %>
|
<%= render :partial => 'vehicles/form' %>
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
<h1>Customer Vehicle</h1>
|
<h2>Vehicle #<%=@vehicle.id%> <span style="float:right"> <%= render :partial => 'customers/search' %> </span> </h2>
|
||||||
<br/>
|
<br/>
|
||||||
<%= render :partial => 'vehicles/details' %>
|
|
||||||
|
<div style="text-align: left; width:90%;">
|
||||||
|
<%= render :partial => 'vehicles/details', locals: {vehicle: @vehicle} %>
|
||||||
|
|
||||||
|
<%= render :partial => 'issues/list_simple', locals: {issues: @vehicle.issues} %>
|
||||||
|
</div>
|
||||||
|
|||||||
18
app/workers/email_worker.rb
Normal file
18
app/workers/email_worker.rb
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2017 rick barrette
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
# This sidekiq worker class will handle emailing weekly time reports
|
||||||
|
class EmailWorker
|
||||||
|
include Sidekiq::Worker
|
||||||
|
|
||||||
|
def perform()
|
||||||
|
# email something
|
||||||
|
end
|
||||||
|
end
|
||||||
23
assets/javascripts/application.js
Normal file
23
assets/javascripts/application.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
$(function() {
|
||||||
|
$("input#issue_customer_id").on("change", function() {
|
||||||
|
$.ajax({
|
||||||
|
url: "/filter_vehicles_by_customer",
|
||||||
|
type: "GET",
|
||||||
|
data: { selected_customer: $("input#issue_customer_id").val() }
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "/filter_estimates_by_customer",
|
||||||
|
type: "GET",
|
||||||
|
data: { selected_customer: $("input#issue_customer_id").val() }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("input#project_customer_id").on("change", function() {
|
||||||
|
$.ajax({
|
||||||
|
url: "/filter_vehicles_by_customer",
|
||||||
|
type: "GET",
|
||||||
|
data: { selected_customer: $("input#project_customer_id").val() }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
1
assets/javascripts/autocomplete-rails.js
Normal file
1
assets/javascripts/autocomplete-rails.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
!function(t){t.fn.railsAutocomplete=function(e){var a=function(){this.railsAutoCompleter||(this.railsAutoCompleter=new t.railsAutocomplete(this))};if(void 0!==t.fn.on){if(!e)return;return t(document).on("focus",e,a)}return this.live("focus",a)},t.railsAutocomplete=function(t){var e=t;this.init(e)},t.railsAutocomplete.options={showNoMatches:!0,noMatchesLabel:"no existing match"},t.railsAutocomplete.fn=t.railsAutocomplete.prototype={railsAutocomplete:"0.0.1"},t.railsAutocomplete.fn.extend=t.railsAutocomplete.extend=t.extend,t.railsAutocomplete.fn.extend({init:function(e){function a(t){return t.split(e.delimiter)}function i(t){return a(t).pop().replace(/^\s+/,"")}e.delimiter=t(e).attr("data-delimiter")||null,e.min_length=t(e).attr("data-min-length")||t(e).attr("min-length")||2,e.append_to=t(e).attr("data-append-to")||null,e.autoFocus=t(e).attr("data-auto-focus")||!1,t(e).autocomplete({appendTo:e.append_to,autoFocus:e.autoFocus,delay:t(e).attr("delay")||0,source:function(a,r){var n=this.element[0],o={term:i(a.term)};t(e).attr("data-autocomplete-fields")&&t.each(t.parseJSON(t(e).attr("data-autocomplete-fields")),function(e,a){o[e]=t(a).val()}),t.getJSON(t(e).attr("data-autocomplete"),o,function(){var a={};t.extend(a,t.railsAutocomplete.options),t.each(a,function(i,r){if(a.hasOwnProperty(i)){var n=t(e).attr("data-"+i);a[i]=n?n:r}}),0==arguments[0].length&&t.inArray(a.showNoMatches,[!0,"true"])>=0&&(arguments[0]=[],arguments[0][0]={id:"",label:a.noMatchesLabel}),t(arguments[0]).each(function(a,i){var r={};r[i.id]=i,t(e).data(r)}),r.apply(null,arguments),t(n).trigger("railsAutocomplete.source",arguments)})},change:function(e,a){if(t(this).is("[data-id-element]")&&""!==t(t(this).attr("data-id-element")).val()&&(t(t(this).attr("data-id-element")).val(a.item?a.item.id:"").trigger("change"),t(this).attr("data-update-elements"))){var i=t.parseJSON(t(this).attr("data-update-elements")),r=a.item?t(this).data(a.item.id.toString()):{};if(i&&""===t(i.id).val())return;for(var n in i){var o=t(i[n]);o.is(":checkbox")?null!=r[n]&&o.prop("checked",r[n]):o.val(a.item?r[n]:"").trigger("change")}}},search:function(){var t=i(this.value);return t.length<e.min_length?!1:void 0},focus:function(){return!1},select:function(i,r){if(r.item.value=r.item.value.toString(),-1!=r.item.value.toLowerCase().indexOf("no match")||-1!=r.item.value.toLowerCase().indexOf("too many results"))return t(this).trigger("railsAutocomplete.noMatch",r),!1;var n=a(this.value);if(n.pop(),n.push(r.item.value),null!=e.delimiter)n.push(""),this.value=n.join(e.delimiter);else if(this.value=n.join(""),t(this).attr("data-id-element")&&t(t(this).attr("data-id-element")).val(r.item.id).trigger("change"),t(this).attr("data-update-elements")){var o=r.item,l=-1!=r.item.value.indexOf("Create New")?!0:!1,u=t.parseJSON(t(this).attr("data-update-elements"));for(var s in u)"checkbox"===t(u[s]).attr("type")?o[s]===!0||1===o[s]?t(u[s]).attr("checked","checked"):t(u[s]).removeAttr("checked"):l&&o[s]&&-1==o[s].indexOf("Create New")||!l?t(u[s]).val(o[s]).trigger("change"):t(u[s]).val("").trigger("change")}var c=this.value;return t(this).bind("keyup.clearId",function(){t.trim(t(this).val())!=t.trim(c)&&(t(t(this).attr("data-id-element")).val("").trigger("change"),t(this).unbind("keyup.clearId"))}),t(e).trigger("railsAutocomplete.select",r),!1}}),t(e).trigger("railsAutocomplete.init")}}),t(document).ready(function(){t("input[data-autocomplete]").railsAutocomplete("input[data-autocomplete]")})}(jQuery);
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
Edmunds::Api.configure do |config|
|
|
||||||
config.api_key = '2dheutzvhxs28dzukx5tgu47'
|
|
||||||
end
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2017 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -11,11 +11,14 @@
|
|||||||
# English strings go here for Rails i18n
|
# English strings go here for Rails i18n
|
||||||
en:
|
en:
|
||||||
# my_label: "My label"
|
# my_label: "My label"
|
||||||
field_qbo_customer: "Customer"
|
field_customer: "Customer"
|
||||||
field_qbo_item: "Item"
|
field_qbo_item: "Item"
|
||||||
field_qbo_employee: "Employee"
|
field_qbo_employee: "Employee"
|
||||||
field_qbo_invoice: "Invoice"
|
field_qbo_invoice: "Invoice"
|
||||||
field_qbo_estimate: "Estimate"
|
field_qbo_estimate: "Estimate"
|
||||||
field_vehicles: "Vehicle"
|
field_vehicles: "Vehicle"
|
||||||
|
field_vehicle: "Vehicle"
|
||||||
field_vin: "VIN"
|
field_vin: "VIN"
|
||||||
field_notes: "Notes"
|
field_notes: "Notes"
|
||||||
|
field_qbo_billed: "Billed"
|
||||||
|
label_week: "Week"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2017 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -8,14 +8,42 @@
|
|||||||
#
|
#
|
||||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
# Plugin's routes
|
# Main Quickbooks landing page
|
||||||
# See: http://guides.rubyonrails.org/routing.html
|
|
||||||
#
|
|
||||||
get 'qbo', :to=> 'qbo#index'
|
get 'qbo', :to=> 'qbo#index'
|
||||||
|
|
||||||
|
#authentication
|
||||||
get 'qbo/authenticate', :to => 'qbo#authenticate'
|
get 'qbo/authenticate', :to => 'qbo#authenticate'
|
||||||
get 'qbo/oauth_callback', :to => 'qbo#oauth_callback'
|
get 'qbo/oauth_callback', :to => 'qbo#oauth_callback'
|
||||||
|
|
||||||
|
#manual sync
|
||||||
get 'qbo/sync', :to => 'qbo#sync'
|
get 'qbo/sync', :to => 'qbo#sync'
|
||||||
|
|
||||||
|
# Estimate & Invoice PDF
|
||||||
get 'qbo/estimate/:id', :to => 'estimate#show', as: :estimate
|
get 'qbo/estimate/:id', :to => 'estimate#show', as: :estimate
|
||||||
get 'qbo/invoice/:id', :to => 'invoice#show', as: :invoice
|
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'
|
||||||
|
|
||||||
|
#java script routes
|
||||||
|
get 'filter_vehicles_by_customer' => 'customers#filter_vehicles_by_customer'
|
||||||
|
get 'filter_estimates_by_customer' => 'customers#filter_estimates_by_customer'
|
||||||
|
get 'filter_invoices_by_customer' => 'customers#filter_invoices_by_customer'
|
||||||
|
|
||||||
|
# Nest Vehicles under customers
|
||||||
|
resources :customers do
|
||||||
|
resources :vehicles
|
||||||
|
get :autocomplete_customer_name, :on => :collection
|
||||||
|
end
|
||||||
|
|
||||||
|
#allow for just vehicles too
|
||||||
resources :vehicles
|
resources :vehicles
|
||||||
resources :customers
|
|
||||||
|
|||||||
17
db/migrate/019_qbocustomers_to_customers.rb
Normal file
17
db/migrate/019_qbocustomers_to_customers.rb
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#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 QbocustomersToCustomers< ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
rename_table :qbo_customers, :customers
|
||||||
|
rename_column :issues, :qbo_customer_id, :customer_id
|
||||||
|
rename_column :vehicles, :qbo_customer_id, :customer_id
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -8,6 +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.
|
#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 VehicleHelper
|
class UpdateQbosTimeStamp < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :qbos, :last_sync, :datetime
|
||||||
|
end
|
||||||
end
|
end
|
||||||
27
db/migrate/021_add_issues_qbo_invoices.rb
Normal file
27
db/migrate/021_add_issues_qbo_invoices.rb
Normal 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
|
||||||
@@ -8,11 +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.
|
#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 QboHelper
|
class UpdateIssuesRemoveInvoice < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
def qbo_customer_dropdown
|
remove_reference :issues, :qbo_invoice
|
||||||
select = context[:form].select :qbo_customer_id, QboCustomers.all.pluck(:name, :id), :selected => selected, include_blank: true
|
end
|
||||||
return "<p>#{select}</p>"
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
19
db/migrate/023_create_customer_tokens.rb
Normal file
19
db/migrate/023_create_customer_tokens.rb
Normal 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
|
||||||
16
db/migrate/024_update_invoices_and_estimates.rb
Normal file
16
db/migrate/024_update_invoices_and_estimates.rb
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2017 rick barrette
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
class UpdateInvoicesAndEstimates < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_reference :qbo_invoices, :customer, index: true
|
||||||
|
add_reference :qbo_estimates, :customer, index: true
|
||||||
|
end
|
||||||
|
end
|
||||||
16
db/migrate/025_update_projects.rb
Normal file
16
db/migrate/025_update_projects.rb
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2017 rick barrette
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
class UpdateProjects < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_reference :projects, :customer, index: true
|
||||||
|
add_reference :projects, :vehicle, index: true
|
||||||
|
end
|
||||||
|
end
|
||||||
26
db/migrate/026_create_line_items.rb
Normal file
26
db/migrate/026_create_line_items.rb
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#The License
|
||||||
|
#
|
||||||
|
#Copyright (c) 2018 Rick Barrette - All Rights Reserved
|
||||||
|
#
|
||||||
|
#Unauthorized copying of this software and associated documentation files (the "Software"), via any medium is strictly prohibited.
|
||||||
|
#
|
||||||
|
#Proprietary and confidential
|
||||||
|
#
|
||||||
|
#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 CreateLineItems < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :line_items do |t|
|
||||||
|
t.integer :item_id
|
||||||
|
t.float :amount
|
||||||
|
t.string :description
|
||||||
|
t.float :unit_price
|
||||||
|
t.float :quantity
|
||||||
|
t.boolean :billed
|
||||||
|
end
|
||||||
|
|
||||||
|
add_reference :line_items, :issues, index: true
|
||||||
|
end
|
||||||
|
end
|
||||||
15
db/migrate/027_add_customers_phone_number.rb
Normal file
15
db/migrate/027_add_customers_phone_number.rb
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2019 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 AddCustomersPhoneNumber < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :customers, :phone_number, :string
|
||||||
|
end
|
||||||
|
end
|
||||||
15
db/migrate/028_add_customers_mobile_phone_number.rb
Normal file
15
db/migrate/028_add_customers_mobile_phone_number.rb
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2019 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 AddCustomersMobilePhoneNumber < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :customers, :mobile_phone_number, :string
|
||||||
|
end
|
||||||
|
end
|
||||||
17
db/migrate/029_update_qbos_types.rb
Normal file
17
db/migrate/029_update_qbos_types.rb
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2020 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 UpdateQbosTypes < ActiveRecord::Migration
|
||||||
|
|
||||||
|
def change
|
||||||
|
change_column :qbos, :qb_token, :text
|
||||||
|
change_column :qbos, :qb_secret, :text
|
||||||
|
end
|
||||||
|
end
|
||||||
17
db/migrate/030_update_qbos_token.rb
Normal file
17
db/migrate/030_update_qbos_token.rb
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2020 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 UpdateQbosToken < ActiveRecord::Migration
|
||||||
|
|
||||||
|
def change
|
||||||
|
add_column :qbos, :token, :text
|
||||||
|
add_column :qbos, :expire, :date
|
||||||
|
end
|
||||||
|
end
|
||||||
36
init.rb
36
init.rb
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2020 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -15,38 +15,52 @@ Redmine::Plugin.register :redmine_qbo do
|
|||||||
require_dependency 'issues_save_hook_listener'
|
require_dependency 'issues_save_hook_listener'
|
||||||
require_dependency 'issues_show_hook_listener'
|
require_dependency 'issues_show_hook_listener'
|
||||||
require_dependency 'users_show_hook_listener'
|
require_dependency 'users_show_hook_listener'
|
||||||
|
require_dependency 'header_footer_hook_listener'
|
||||||
|
require_dependency 'projects_form_hook_listener'
|
||||||
|
require_dependency 'view_hook_listener'
|
||||||
|
|
||||||
# Patches to the Redmine core. Will not work in development mode
|
# Patches to the Redmine core. Will not work in development mode
|
||||||
require_dependency 'issue_patch'
|
require_dependency 'issue_patch'
|
||||||
|
require_dependency 'project_patch'
|
||||||
require_dependency 'user_patch'
|
require_dependency 'user_patch'
|
||||||
require_dependency 'query_patch'
|
require_dependency 'query_patch'
|
||||||
|
require_dependency 'time_entry_query_patch'
|
||||||
require_dependency 'pdf_patch'
|
require_dependency 'pdf_patch'
|
||||||
|
require_dependency 'attachments_controller_patch'
|
||||||
|
|
||||||
name 'Redmine Quickbooks Online plugin'
|
name 'Redmine Quickbooks Online plugin'
|
||||||
author 'Rick Barrette'
|
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'
|
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.5'
|
version '0.8.0'
|
||||||
url 'https://github.com/rickbarrette/redmine_qbo'
|
url 'https://github.com/rickbarrette/redmine_qbo'
|
||||||
author_url 'http://rickbarrette.org'
|
author_url 'http://rickbarrette.org'
|
||||||
settings :default => {'empty' => true}, :partial => 'qbo/settings'
|
settings :default => {'empty' => true}, :partial => 'qbo/settings'
|
||||||
|
|
||||||
# Add safe attributes
|
# Add safe attributes
|
||||||
Issue.safe_attributes 'qbo_customer_id'
|
Issue.safe_attributes 'customer_id'
|
||||||
Issue.safe_attributes 'qbo_item_id'
|
Issue.safe_attributes 'qbo_item_id'
|
||||||
Issue.safe_attributes 'qbo_estimate_id'
|
Issue.safe_attributes 'qbo_estimate_id'
|
||||||
Issue.safe_attributes 'qbo_invoice_id'
|
Issue.safe_attributes 'qbo_invoice_id'
|
||||||
Issue.safe_attributes 'vehicles_id'
|
Issue.safe_attributes 'vehicles_id'
|
||||||
User.safe_attributes 'qbo_employee_id'
|
User.safe_attributes 'qbo_employee_id'
|
||||||
TimeEntry.safe_attributes 'qbo_billed'
|
TimeEntry.safe_attributes 'qbo_billed'
|
||||||
|
Project.safe_attributes 'customer_id'
|
||||||
|
Project.safe_attributes 'vehicle_id'
|
||||||
|
|
||||||
# We are playing in the sandbox
|
# We are playing in the sandbox
|
||||||
#Quickbooks.sandbox_mode = true
|
#Quickbooks.sandbox_mode = true
|
||||||
|
|
||||||
# set per_page globally
|
|
||||||
WillPaginate.per_page = 10
|
|
||||||
|
|
||||||
|
# set per_page globally
|
||||||
|
WillPaginate.per_page = 20
|
||||||
|
|
||||||
|
permission :view_customers, :customers => :index, :public => false
|
||||||
|
permission :add_customers, :customers => :new, :public => false
|
||||||
|
permission :view_payments, :payments => :index, :public => false
|
||||||
|
permission :add_payments, :payments => :new, :public => false
|
||||||
|
permission :view_vehicles, :payments => :new, :public => false
|
||||||
|
|
||||||
# Register QBO top menu item
|
# Register QBO top menu item
|
||||||
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 :top_menu, :vehicles, { :controller => :vehicles, :action => :index }, :caption => 'Vehicles', :if => Proc.new { User.current.logged? }
|
||||||
menu :top_menu, :customers, { :controller => :customers, :action => :index }, :caption => 'Customers', :if => Proc.new { User.current.logged? }
|
|
||||||
end
|
end
|
||||||
|
|||||||
38
lib/attachments_controller_patch.rb
Normal file
38
lib/attachments_controller_patch.rb
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2017 rick barrette
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
require_dependency 'attachments_controller'
|
||||||
|
|
||||||
|
module AttachmentsControllerPatch
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
skip_before_action :read_authorize
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
module InstanceMethods
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add module to AttachmentsController
|
||||||
|
AttachmentsController.send(:include, AttachmentsControllerPatch)
|
||||||
19
lib/header_footer_hook_listener.rb
Normal file
19
lib/header_footer_hook_listener.rb
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2017 rick barrette
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
class 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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2020 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -22,10 +22,12 @@ module IssuePatch
|
|||||||
# Same as typing in the class
|
# Same as typing in the class
|
||||||
base.class_eval do
|
base.class_eval do
|
||||||
unloadable # Send unloadable so it will not be unloaded in development
|
unloadable # Send unloadable so it will not be unloaded in development
|
||||||
belongs_to :qbo_customer, primary_key: :id
|
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_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
|
belongs_to :vehicle, primary_key: :id
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -36,7 +38,72 @@ module IssuePatch
|
|||||||
end
|
end
|
||||||
|
|
||||||
module InstanceMethods
|
module InstanceMethods
|
||||||
|
|
||||||
|
# Create billable time entries
|
||||||
|
def bill_time
|
||||||
|
|
||||||
|
# Check to see if we have everything we need to bill the customer
|
||||||
|
#return unless status.is_closed?
|
||||||
|
return if assigned_to.nil?
|
||||||
|
return unless Qbo.first
|
||||||
|
return unless customer
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
item_service = Qbo.get_base(:item)
|
||||||
|
time_entry = Quickbooks::Model::TimeActivity.new
|
||||||
|
|
||||||
|
# Lets total up each activity before billing.
|
||||||
|
# This will simpify the invoicing with a single billable time entry per time activity
|
||||||
|
h = Hash.new(0)
|
||||||
|
spent_time.each do |entry|
|
||||||
|
h[entry.activity.name] += entry.hours
|
||||||
|
# update time entries billed status
|
||||||
|
entry.qbo_billed = true
|
||||||
|
entry.save
|
||||||
|
end
|
||||||
|
|
||||||
|
# Now letes upload our totals for each activity as their own billable time entry
|
||||||
|
h.each do |key, val|
|
||||||
|
|
||||||
|
# Convert float spent time to hours and minutes
|
||||||
|
hours = val.to_i
|
||||||
|
minutesDecimal = (( val - hours) * 60)
|
||||||
|
minutes = minutesDecimal.to_i
|
||||||
|
|
||||||
|
# Lets match the activity to an qbo item
|
||||||
|
item = item_service.query("SELECT * FROM Item WHERE Name = '#{key}' ").first
|
||||||
|
next if item.nil?
|
||||||
|
|
||||||
|
# Create the new billable time entry and upload it
|
||||||
|
time_entry.description = "#{tracker} ##{id}: #{subject} #{"(Partial @ #{done_ratio}%)" if not closed?}"
|
||||||
|
# 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 + 1.month, :issue_id => id)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2017 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -10,39 +10,51 @@
|
|||||||
|
|
||||||
class IssuesFormHookListener < Redmine::Hook::ViewListener
|
class IssuesFormHookListener < Redmine::Hook::ViewListener
|
||||||
|
|
||||||
|
# Load the javascript
|
||||||
|
def view_layouts_base_html_head(context = {})
|
||||||
|
js = javascript_include_tag 'application', :plugin => 'redmine_qbo'
|
||||||
|
js += javascript_include_tag 'autocomplete-rails', :plugin => 'redmine_qbo'
|
||||||
|
return js
|
||||||
|
end
|
||||||
|
|
||||||
# Edit Issue Form
|
# Edit Issue Form
|
||||||
# Show a dropdown for quickbooks contacts
|
# Show a dropdown for quickbooks contacts
|
||||||
def view_issues_form_details_bottom(context={})
|
def view_issues_form_details_bottom(context={})
|
||||||
f = context[:form]
|
f = context[:form]
|
||||||
|
|
||||||
|
#check project level customer/vehicle ownership first
|
||||||
|
if context[:project]
|
||||||
|
selected_customer = context[:project].customer ? context[:project].customer.id : nil
|
||||||
|
selected_vehicle = context[:project].vehicle ? context[:project].vehicle.id : nil
|
||||||
|
end
|
||||||
|
|
||||||
# Check to see if there is a quickbooks user attached to the issue
|
# Check to see if there is a quickbooks user attached to the issue
|
||||||
selected_customer = context[:issue].qbo_customer ? context[:issue].qbo_customer.id : nil
|
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_estimate = context[:issue].qbo_estimate ? context[:issue].qbo_estimate.id : nil
|
||||||
|
selected_vehicle = context[:issue].vehicles_id ? context[:issue].vehicles_id : nil
|
||||||
|
|
||||||
customer = QboCustomer.find_by_id(selected_customer) if selected_customer
|
# Load customer information
|
||||||
vehicle = context[:issue].vehicles_id
|
customer = Customer.find_by_id(selected_customer) if selected_customer
|
||||||
|
search_customer = f.autocomplete_field :customer, autocomplete_customer_name_customers_path, :selected => selected_customer, :update_elements => {:id => '#issue_customer_id', :value => '#issue_customer'}
|
||||||
|
customer_id = f.hidden_field :customer_id, :id => "issue_customer_id"
|
||||||
|
|
||||||
# Generate the drop down list of quickbooks customers
|
if context[:issue].customer
|
||||||
select_customer = f.select :qbo_customer_id, QboCustomer.all.pluck(:name, :id).sort, :selected => selected_customer, include_blank: true
|
if customer.vehicles
|
||||||
|
vehicles = customer.vehicles.pluck(:name, :id)
|
||||||
# Generate the drop down list of quickbooks items
|
else
|
||||||
select_item = f.select :qbo_item_id, QboItem.all.pluck(:name, :id).sort, :selected => selected_item, include_blank: true
|
vehicles = [nil].compact
|
||||||
|
end
|
||||||
|
estimates = customer.qbo_estimates.pluck(:doc_number, :id).sort! {|x, y| y <=> x}
|
||||||
|
else
|
||||||
|
vehicles = [nil].compact
|
||||||
|
estimates = [nil].compact
|
||||||
|
end
|
||||||
|
|
||||||
# Generate the drop down list of quickbooks invoices
|
# Generate the drop down list of quickbooks extimates
|
||||||
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_estimate = f.select :qbo_estimate_id, estimates, :selected => selected_estimate, include_blank: true
|
||||||
|
|
||||||
# Generate the drop down list of quickbooks extimates
|
vehicle = f.select :vehicles_id, vehicles, :selected => selected_vehicle, include_blank: true
|
||||||
select_estimate = f.select :qbo_estimate_id, QboEstimate.all.pluck(:doc_number, :id).sort! {|x, y| y <=> x}, :selected => selected_estimate, include_blank: true
|
|
||||||
|
|
||||||
#@estimates_link = link_to qbo_update_estimates_path
|
return "<p><label for=\"issue_customer\">Customer</label>#{search_customer} #{customer_id}</p> <p>#{select_estimate}</p> <p>#{vehicle}</p>"
|
||||||
|
|
||||||
#render_on :view_issues_form_details_bottom, :partial => 'hooks/redmine_qbo/vehicles/dropdown'
|
|
||||||
vehicles = customer.vehicles.pluck(:name, :id).sort if customer
|
|
||||||
vehicles = Vehicle.all if not vehicles
|
|
||||||
vehicle = f.select :vehicles_id, vehicles, include_blank: true, :selected => vehicle
|
|
||||||
|
|
||||||
return "<p>#{select_customer}</p> <p>#{select_item}</p> <p>#{select_invoice}</p> <p>#{select_estimate}</p> <p>#{vehicle}</p>"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2017 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -10,97 +10,10 @@
|
|||||||
|
|
||||||
class IssuesSaveHookListener < Redmine::Hook::ViewListener
|
class IssuesSaveHookListener < Redmine::Hook::ViewListener
|
||||||
|
|
||||||
#Before Issue Saved
|
# Called After Issue Saved
|
||||||
def controller_issues_edit_before_save(context={})
|
|
||||||
issue = context[:issue]
|
|
||||||
|
|
||||||
# Check to see if we have registered with QBO
|
|
||||||
if Qbo.first && issue.qbo_customer && issue.qbo_item
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# Get QBO Services
|
|
||||||
item_service = QboItem.get_base.service
|
|
||||||
estimate_base = QboEstimate.get_base
|
|
||||||
|
|
||||||
# Create the estimate
|
|
||||||
estimate = estimate_base.qr_model(:estimate)
|
|
||||||
estimate.customer_id = issue.qbo_customer_id
|
|
||||||
estimate.txn_date = Date.today
|
|
||||||
|
|
||||||
# Create the line item for labor
|
|
||||||
item = item_service.fetch_by_id(issue.qbo_item_id)
|
|
||||||
|
|
||||||
line_item = Quickbooks::Model::InvoiceLineItem.new
|
|
||||||
line_item.amount = item.unit_price * issue.estimated_hours
|
|
||||||
line_item.description = issue.subject
|
|
||||||
|
|
||||||
line_item.sales_item! do |detail|
|
|
||||||
detail.unit_price = item.unit_price
|
|
||||||
detail.quantity = issue.estimated_hours
|
|
||||||
detail.item_id = issue.qbo_item_id
|
|
||||||
end
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# Called After Issue Saved
|
|
||||||
def controller_issues_edit_after_save(context={})
|
def controller_issues_edit_after_save(context={})
|
||||||
issue = context[:issue]
|
issue = context[:issue]
|
||||||
employee_id = issue.assigned_to.qbo_employee_id
|
issue.bill_time if issue.status.is_closed?
|
||||||
|
end
|
||||||
|
|
||||||
# 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.qbo_customer && issue.qbo_item && employee_id && issue.status.is_closed?
|
|
||||||
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.qbo_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
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2017 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
@@ -10,21 +10,15 @@
|
|||||||
|
|
||||||
class IssuesShowHookListener < Redmine::Hook::ViewListener
|
class IssuesShowHookListener < Redmine::Hook::ViewListener
|
||||||
|
|
||||||
# Additional context fields
|
|
||||||
# :issue => the issue this is edited
|
|
||||||
# :f => the form object to create additional fields
|
|
||||||
#render_on :view_issues_show_details_bottom, :partial => 'hooks/redmine_qbo/_view_issues_show_details_bottom.html.erb'
|
|
||||||
|
|
||||||
# View Issue
|
# View Issue
|
||||||
# Display the quickbooks contact in the issue
|
# Display the quickbooks contact in the issue
|
||||||
def view_issues_show_details_bottom(context={})
|
def view_issues_show_details_bottom(context={})
|
||||||
issue = context[:issue]
|
issue = context[:issue]
|
||||||
|
|
||||||
# Check to see if there is a quickbooks user attached to the issue
|
|
||||||
customer = issue.qbo_customer ? issue.qbo_customer.name : nil
|
|
||||||
|
|
||||||
# Check to see if there is a quickbooks item attached to the issue
|
# Check to see if there is a quickbooks user attached to the issue
|
||||||
item = issue.qbo_item ? issue.qbo_item.name : nil
|
if issue.customer
|
||||||
|
customer = link_to issue.customer.name, "#{Redmine::Utils::relative_url_root}/customers/#{issue.customer.id}"
|
||||||
|
end
|
||||||
|
|
||||||
# Estimate Number
|
# Estimate Number
|
||||||
if issue.qbo_estimate
|
if issue.qbo_estimate
|
||||||
@@ -33,59 +27,43 @@ class IssuesShowHookListener < Redmine::Hook::ViewListener
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Invoice Number
|
# Invoice Number
|
||||||
if issue.qbo_invoice
|
invoice_link = ""
|
||||||
invoice = issue.qbo_invoice.doc_number
|
if issue.qbo_invoice_ids
|
||||||
invoice_link = link_to invoice, "#{Redmine::Utils::relative_url_root}/qbo/invoice/#{issue.qbo_invoice.id}", :target => "_blank"
|
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 + " "
|
||||||
|
invoice_link = invoice_link.html_safe
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
if issue.vehicles_id
|
v = Vehicle.find(issue.vehicles_id)
|
||||||
v = Vehicle.find_by_id(issue.vehicles_id)
|
|
||||||
vehicle = link_to v.to_s, "#{Redmine::Utils::relative_url_root}/vehicles/#{v.id}"
|
vehicle = link_to v.to_s, "#{Redmine::Utils::relative_url_root}/vehicles/#{v.id}"
|
||||||
vin = v.vin
|
vin = v.vin
|
||||||
notes = v.notes
|
notes = v.notes
|
||||||
|
rescue
|
||||||
|
#do nothing
|
||||||
end
|
end
|
||||||
|
|
||||||
return "
|
split_vin = vin.scan(/.{1,9}/) if vin
|
||||||
<div class=\"attributes\">
|
|
||||||
|
|
||||||
<div class=\"qbo_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\">
|
context[:controller].send(:render_to_string, {
|
||||||
<div class=\"label\"><span>Estimate</span>:</div>
|
:partial => 'qbo/issues_show_details',
|
||||||
<div class=\"value\">#{estimate_link}</div>
|
locals: {
|
||||||
</div>
|
customer: customer,
|
||||||
|
estimate_link: estimate_link,
|
||||||
<div class=\"qbo_invoice_id attribute\">
|
invoice_link: invoice_link,
|
||||||
<div class=\"label\"><span>Invoice</span>:</div>
|
vehicle: vehicle,
|
||||||
<div class=\"value\">#{invoice_link}</div>
|
split_vin: split_vin,
|
||||||
</div>
|
notes: notes
|
||||||
|
}
|
||||||
<br/>
|
})
|
||||||
|
end
|
||||||
|
|
||||||
<div class=\"vehicle attribute\">
|
def view_issues_show_description_bottom(context={})
|
||||||
<div class=\"label\"><span>Vehicle</span>:</div>
|
bill_button = button_to "Bill Time", "#{Redmine::Utils::relative_url_root}/qbo/bill/#{context[:issue].id}", method: :get if User.current.admin?
|
||||||
<div class=\"value\">#{vehicle}</div>
|
share_button = button_to "Share", "#{Redmine::Utils::relative_url_root}/customers/view/#{context[:issue].share_token.token}", method: :get if User.current.logged?
|
||||||
</div>
|
return "<br/> #{bill_button} #{share_button}"
|
||||||
|
|
||||||
<div class=\"vehicle_vin attribute\">
|
|
||||||
<div class=\"label\"><span>VIN</span>:</div>
|
|
||||||
<div class=\"value\">#{vin}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class=\"vehicle_notes attribute\">
|
|
||||||
<div class=\"label\"><span>Notes</span>:</div>
|
|
||||||
<div class=\"value\">#{notes}</div>
|
|
||||||
</div>
|
|
||||||
</div>"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -37,10 +37,11 @@ module IssuesPdfHelperPatch
|
|||||||
pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}")
|
pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}")
|
||||||
pdf.ln
|
pdf.ln
|
||||||
|
|
||||||
|
customer = issue.customer.name if issue.customer
|
||||||
left = []
|
left = []
|
||||||
left << [l(:field_status), issue.status]
|
left << [l(:field_status), issue.status]
|
||||||
left << [l(:field_priority), issue.priority]
|
left << [l(:field_priority), issue.priority]
|
||||||
left << [l(:field_qbo_customer), issue.qbo_customer.name]
|
left << [l(:field_customer), customer]
|
||||||
left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id')
|
left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id')
|
||||||
#left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id')
|
#left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id')
|
||||||
#left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id')
|
#left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id')
|
||||||
@@ -50,8 +51,8 @@ module IssuesPdfHelperPatch
|
|||||||
vin = v ? v.vin : nil
|
vin = v ? v.vin : nil
|
||||||
notes = v ? v.notes : nil
|
notes = v ? v.notes : nil
|
||||||
left << [l(:field_vehicles), vehicle]
|
left << [l(:field_vehicles), vehicle]
|
||||||
left << [l(:field_vin), vin]
|
left << [l(:field_vin), vin ? vin.gsub(/(.{9})/, '\1 ') : nil]
|
||||||
left << [l(:field_notes), notes]
|
#left << [l(:field_notes), notes]
|
||||||
|
|
||||||
right = []
|
right = []
|
||||||
right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date')
|
right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date')
|
||||||
@@ -59,7 +60,7 @@ module IssuesPdfHelperPatch
|
|||||||
right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio')
|
right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio')
|
||||||
right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours')
|
right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours')
|
||||||
right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project)
|
right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project)
|
||||||
#right << [l(:field_notes), notes]
|
right << [l(:field_notes), notes]
|
||||||
|
|
||||||
rows = left.size > right.size ? left.size : right.size
|
rows = left.size > right.size ? left.size : right.size
|
||||||
while left.size < rows
|
while left.size < rows
|
||||||
|
|||||||
40
lib/project_patch.rb
Normal file
40
lib/project_patch.rb
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2017 rick barrette
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
require_dependency 'project'
|
||||||
|
|
||||||
|
# Patches Redmine's Projects dynamically.
|
||||||
|
# Adds a relationships
|
||||||
|
module ProjectPatch
|
||||||
|
|
||||||
|
def self.included(base) # :nodoc:
|
||||||
|
base.extend(ClassMethods)
|
||||||
|
|
||||||
|
base.send(:include, InstanceMethods)
|
||||||
|
|
||||||
|
# Same as typing in the class
|
||||||
|
base.class_eval do
|
||||||
|
unloadable # Send unloadable so it will not be unloaded in development
|
||||||
|
belongs_to :customer, primary_key: :id
|
||||||
|
belongs_to :vehicle, primary_key: :id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
module InstanceMethods
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add module to Project
|
||||||
|
Project.send(:include, ProjectPatch)
|
||||||
36
lib/projects_form_hook_listener.rb
Normal file
36
lib/projects_form_hook_listener.rb
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2017 rick barrette
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
class ProjectsFormHookListener < Redmine::Hook::ViewListener
|
||||||
|
|
||||||
|
# Edit Project Form
|
||||||
|
def view_projects_form(context={})
|
||||||
|
f = context[:form]
|
||||||
|
|
||||||
|
# Check to see if there is a quickbooks user attached to the issue
|
||||||
|
selected_customer = context[:project].customer ? context[:project].customer : nil
|
||||||
|
selected_vehicle = context[:project].vehicle_id ? context[:project].vehicle_id : nil
|
||||||
|
|
||||||
|
# Load customer information
|
||||||
|
customer = Customer.find_by_id(selected_customer) if selected_customer
|
||||||
|
search_customer = f.autocomplete_field :customer, autocomplete_customer_name_customers_path, :selected => selected_customer, :update_elements => {:id => '#project_customer_id', :value => '#project_customer'}
|
||||||
|
customer_id = f.hidden_field :customer_id, :id => "project_customer_id"
|
||||||
|
|
||||||
|
if context[:project].customer
|
||||||
|
vehicles = customer.vehicles.pluck(:name, :id).sort!
|
||||||
|
else
|
||||||
|
vehicles = [nil].compact
|
||||||
|
end
|
||||||
|
|
||||||
|
vehicle = f.select :vehicle_id, vehicles, :selected => selected_vehicle, include_blank: true
|
||||||
|
|
||||||
|
return "<p><label for=\"project_customer\">Customer</label>#{search_customer} #{customer_id}</p> <p>#{vehicle}</p>"
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2017 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#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:
|
||||||
#
|
#
|
||||||
@@ -35,7 +35,8 @@ module QueryPatch
|
|||||||
def available_columns_with_qbo
|
def available_columns_with_qbo
|
||||||
unless @available_columns
|
unless @available_columns
|
||||||
@available_columns = available_columns_without_qbo
|
@available_columns = available_columns_without_qbo
|
||||||
@available_columns << QueryColumn.new(:qbo_customer, :sortable => "#{QboCustomer.table_name}.name", :groupable => true, :caption => :field_qbo_customer)
|
@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}.qbo_billed", :groupable => true, :caption => :field_qbo_billed)
|
||||||
end
|
end
|
||||||
@available_columns
|
@available_columns
|
||||||
end
|
end
|
||||||
@@ -44,14 +45,23 @@ module QueryPatch
|
|||||||
unless @available_filters
|
unless @available_filters
|
||||||
@available_filters = available_filters_without_qbo
|
@available_filters = available_filters_without_qbo
|
||||||
|
|
||||||
qbo_filters = {
|
#qbo_filters = {
|
||||||
"customer" => {
|
# :customer => {
|
||||||
:name => l(:field_qbo_customer),
|
# :id => l(:field_customer),
|
||||||
:type => :text,
|
# :type => :integer,
|
||||||
:order => @available_filters.size + 1},
|
# :order => @available_filters.size + 1},
|
||||||
}
|
#}
|
||||||
|
|
||||||
@available_filters.merge!(qbo_filters)
|
#qbo_filters = {
|
||||||
|
# "customer_id" => {
|
||||||
|
# :id => :customer_id,
|
||||||
|
# :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
|
end
|
||||||
@available_filters
|
@available_filters
|
||||||
end
|
end
|
||||||
|
|||||||
71
lib/time_entry_query_patch.rb
Normal file
71
lib/time_entry_query_patch.rb
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#The MIT License (MIT)
|
||||||
|
#
|
||||||
|
#Copyright (c) 2017 rick barrette
|
||||||
|
#
|
||||||
|
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2017 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#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:
|
||||||
#
|
#
|
||||||
@@ -36,4 +36,4 @@ module UserPatch
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Add module to Issue
|
# Add module to Issue
|
||||||
User.send(:include, UserPatch)
|
User.send(:include, UserPatch)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2016 rick barrette
|
#Copyright (c) 2017 rick barrette
|
||||||
#
|
#
|
||||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
#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:
|
||||||
#
|
#
|
||||||
@@ -14,7 +14,7 @@ class UsersShowHookListener < Redmine::Hook::ViewListener
|
|||||||
def view_users_form(context={})
|
def view_users_form(context={})
|
||||||
|
|
||||||
# Update the users
|
# Update the users
|
||||||
QboEmployee.update_all
|
#QboEmployee.update_all
|
||||||
|
|
||||||
# Check to see if there is a quickbooks user attached to the issue
|
# Check to see if there is a quickbooks user attached to the issue
|
||||||
@selected = context[:user].qbo_employee.id if context[:user].qbo_employee
|
@selected = context[:user].qbo_employee.id if context[:user].qbo_employee
|
||||||
|
|||||||
3
lib/view_hook_listener.rb
Normal file
3
lib/view_hook_listener.rb
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
class ViewHookListener < Redmine::Hook::ViewListener
|
||||||
|
render_on :view_layouts_base_sidebar, :partial => "customers/sidebar"
|
||||||
|
end
|
||||||
9
test/unit/customer_token_test.rb
Normal file
9
test/unit/customer_token_test.rb
Normal 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
|
||||||
Reference in New Issue
Block a user