gitquickbooks 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ sudo: false
3
+ rvm:
4
+ - 2.2.3
5
+ script: bundle exec rake spec
6
+
7
+ addons:
8
+ code_climate:
9
+ repo_token: c2d82eb9f0e428387f401349a88ddcc21e9bbff3a91d0d2ec700c3783691da08
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "codeclimate-test-reporter", group: :test, require: nil
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Russell Osborne
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,59 @@
1
+ # GitQuickBooks
2
+
3
+ [![Build Status](https://travis-ci.org/rposborne/gitquickbooks.svg)](https://travis-ci.org/rposborne/gitquickbooks)
4
+ [![Code Climate](https://codeclimate.com/github/rposborne/gitquickbooks/badges/gpa.svg)](https://codeclimate.com/github/rposborne/gitquickbooks)
5
+ [![Test Coverage](https://codeclimate.com/github/rposborne/gitquickbooks/badges/coverage.svg)](https://codeclimate.com/github/rposborne/gitquickbooks/coverage)
6
+
7
+ This code links git wakatime per commit data to quickbooks online.
8
+ It will automatically manage API keys and storing of wakatime data locally via
9
+ GitWakatime gem.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'gitquickbooks'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install gitquickbooks
24
+
25
+ ## Usage
26
+
27
+ TODO: Write usage instructions here
28
+
29
+ ## Contributing
30
+
31
+ 1. Fork it ( https://github.com/[my-github-username]/gitquickbooks/fork )
32
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
33
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
34
+ 4. Push to the branch (`git push origin my-new-feature`)
35
+ 5. Create a new Pull Request
36
+
37
+
38
+ # Loop through TimeServices
39
+ ``` ruby
40
+ @entires = @time_service.query("Select * from TimeActivity where BillableStatus = 'Billable' and CustomerRef = '167'", :per_page => 50)
41
+
42
+ def update_entry(entry)
43
+ if entry.employee_ref
44
+ entry.hourly_rate = 75
45
+ name = entry.employee_ref.name
46
+ else #sam
47
+ entry.hourly_rate = 50
48
+ name = entry.vendor_ref.name
49
+ end
50
+ puts "#{entry.billable_status} #{entry.txn_date} #{entry.hourly_rate} for #{name}: #{entry.description}"
51
+ @time_service.update(entry)
52
+ end
53
+ entry = @entires.entries.first
54
+
55
+ update_entry(entry)
56
+ @entires.entries.each do |time_entry|
57
+ update_entry(time_entry)
58
+ end
59
+ ```
@@ -0,0 +1,19 @@
1
+ require 'bundler/gem_tasks'
2
+ begin
3
+ Bundler.setup(:default, :development)
4
+ rescue Bundler::BundlerError => e
5
+ $stderr.puts e.message
6
+ $stderr.puts 'Run `bundle install` to install missing gems'
7
+ exit e.status_code
8
+ end
9
+ require 'rake'
10
+
11
+ begin
12
+ require 'rspec/core/rake_task'
13
+
14
+ RSpec::Core::RakeTask.new(:spec)
15
+
16
+ task default: :spec
17
+ rescue LoadError
18
+ # no rspec available
19
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ require 'thor'
3
+
4
+ require_relative '../lib/gitquickbooks'
5
+
6
+ GitQuickBooks::Cli.start
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'gitquickbooks/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'gitquickbooks'
8
+ spec.version = GitQuickBooks::VERSION
9
+ spec.authors = ['Russell Osborne']
10
+ spec.email = ['russell@burningpony.com']
11
+ spec.summary = 'Bill Away'
12
+ spec.description = 'Bill Away to the clients'
13
+ spec.homepage = ''
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_runtime_dependency 'oauth-plugin', '>= 0.0.1'
22
+ spec.add_runtime_dependency 'dotenv', '>= 0.0.1'
23
+ spec.add_runtime_dependency 'thor', '>= 0.0.1'
24
+ spec.add_runtime_dependency 'launchy', '>= 0.0.1'
25
+ spec.add_runtime_dependency 'quickbooks-ruby', '>= 0.0.1'
26
+ spec.add_runtime_dependency 'gitwakatime', '>= 0.3.0'
27
+ spec.add_runtime_dependency 'awesome_print', '>= 0.0.1'
28
+ spec.add_development_dependency('bundler', ['>= 0'])
29
+ spec.add_development_dependency 'rake'
30
+ spec.add_development_dependency 'pry'
31
+ spec.add_development_dependency 'rspec'
32
+ end
@@ -0,0 +1,14 @@
1
+ require 'dotenv'
2
+ Dotenv.load
3
+
4
+ require 'thor'
5
+ require 'gitquickbooks/version'
6
+ require 'gitquickbooks/api'
7
+ require 'gitquickbooks/cli'
8
+ require 'gitquickbooks/cache'
9
+ require 'gitquickbooks/commit_msg_cleaner'
10
+ require 'quickbooks-ruby'
11
+ require 'gitwakatime'
12
+ # Yup it's a module :)
13
+ module GitQuickBooks
14
+ end
@@ -0,0 +1,47 @@
1
+ require 'oauth'
2
+ require 'launchy'
3
+
4
+ module GitQuickBooks
5
+ ##
6
+ # Expose and authorize primary QB account
7
+ class Api
8
+ attr_reader :realm,
9
+ :key,
10
+ :secret,
11
+ :access_token,
12
+ :callback_url,
13
+ :authorize_url
14
+
15
+ def initialize
16
+ @key = ENV['QB_KEY']
17
+ @secret = ENV['QB_SECRET']
18
+ @realm = ENV['COMPANY_ID']
19
+ @callback_url = 'https://localhost/oob'
20
+ @qb_oauth = build_oauth
21
+ @access_token = GitQuickBooks::Cache.new.fetch('key') do
22
+ setup_auth
23
+ end
24
+ end
25
+
26
+ def build_oauth
27
+ OAuth::Consumer.new(
28
+ @key,
29
+ @secret,
30
+ site: 'https://oauth.intuit.com',
31
+ request_token_path: '/oauth/v1/get_request_token',
32
+ authorize_url: 'https://appcenter.intuit.com/Connect/Begin',
33
+ access_token_path: '/oauth/v1/get_access_token'
34
+ )
35
+ end
36
+
37
+ def setup_auth
38
+ @request_token ||= @qb_oauth.get_request_token(oauth_callback: @callback_url)
39
+ @authorize_url ||= @request_token.authorize_url(oauth_callback: @callback_url)
40
+ end
41
+
42
+ def authorize(oauth)
43
+ @access_token = @request_token.get_access_token(oauth_verifier: oauth)
44
+ GitQuickBooks::Cache.new.write('key', @access_token)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,35 @@
1
+ module GitQuickBooks
2
+ ##
3
+ # Setup Marshalling cache
4
+ class Cache
5
+ # attr_accesor :base_path
6
+ def load(name)
7
+ Marshal.load(IO.read("tmp/#{name}"))
8
+ end
9
+
10
+ def present?(name)
11
+ File.file?("tmp/#{name}")
12
+ end
13
+
14
+ def write(name, data)
15
+ File.open("tmp/#{name}", 'w+') do |f|
16
+ f.write(Marshal.dump(data))
17
+ end
18
+ end
19
+
20
+ def delete(name)
21
+ File.delete("tmp/#{name}")
22
+ end
23
+
24
+ def fetch(name, &block)
25
+ if !present?(name)
26
+ @data = block.call
27
+ write(name, @data)
28
+ @data
29
+ else
30
+ puts "#{name} extracted from cache".red
31
+ @data = GitQuickBooks::Cache.new.load(name)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,161 @@
1
+ require 'active_support/core_ext/date/calculations'
2
+ require 'active_support/core_ext/date_and_time/calculations'
3
+ require 'active_support/core_ext/integer/time'
4
+ require 'active_support/core_ext/time'
5
+ require 'awesome_print'
6
+
7
+ module GitQuickBooks
8
+ # Provides two CLI actions init and tally
9
+ class Cli < Thor
10
+ include Thor::Actions
11
+
12
+ desc 'authorize', 'Login'
13
+
14
+ def authorize
15
+ api = GitQuickBooks::Api.new
16
+
17
+ say 'To use Quickbooks, you need to grant access to the app.'
18
+ say 'Press enter to launch your web browser and grant access.'
19
+
20
+ Launchy.open api.authorize_url
21
+
22
+ oauth_verifier = ask 'Now, copy the PIN below and press enter:'
23
+
24
+ api.authorize(oauth_verifier)
25
+ end
26
+
27
+ desc 'companies', 'List Company Names and IDs'
28
+ def companies
29
+ api = GitQuickBooks::Api.new
30
+
31
+ @customer_service = Quickbooks::Service::Customer.new
32
+ @customer_service.access_token = api.access_token
33
+ @customer_service.company_id = api.realm
34
+
35
+ # Called without args you get the first page of results
36
+ @customer_service.query_in_batches(nil, per_page: 20) do |batch|
37
+ batch.each do |customer|
38
+ puts "#{customer.id} : #{customer.display_name || customer.method}"
39
+ end
40
+ end
41
+ end
42
+
43
+ desc 'process', 'Produce time spend for each commit and file in each commit'
44
+ method_option :customer, aliases: '-c', type: 'numeric', required: true
45
+ method_option :file, aliases: '-f', default: '.'
46
+
47
+ method_option :start_on, aliases: '-s', default: Date.today.beginning_of_month.to_s
48
+ method_option :end_on, aliases: '-e'
49
+ method_option :output, aliases: '-o', default: 'text'
50
+ method_option :flush, default: false, type: 'boolean'
51
+ method_option :add_to_qb, default: false
52
+ method_option :rate, default: 75.0
53
+
54
+ def process
55
+ path = File.expand_path(options.file)
56
+ start_date = Date.parse(options.start_on)
57
+ end_date = Date.today
58
+
59
+ # == Setup QB
60
+ GitWakaTime.config.setup_local_db
61
+ api = GitQuickBooks::Api.new
62
+ @time_service = Quickbooks::Service::TimeActivity.new
63
+ @time_service.access_token = api.access_token
64
+ @time_service.company_id = api.realm
65
+
66
+ @customer_service = Quickbooks::Service::Customer.new
67
+ @customer_service.access_token = api.access_token
68
+ @customer_service.company_id = api.realm
69
+
70
+ customer = @customer_service.fetch_by_id(options.customer)
71
+
72
+ puts "running for #{customer.display_name} for #{start_date} #{end_date}".blue
73
+
74
+ return unless customer
75
+ @total_time = 0
76
+
77
+ controller = GitWakaTime::Controller.new(path: path, date: start_date)
78
+ @timer = controller.timer
79
+
80
+ @slips = @timer.map do |date, commits|
81
+ next unless date >= start_date
82
+ next unless date <= end_date
83
+ commits = commits.reject { |c| c.message.to_s.downcase.delete(' ').include?('nocharge') }
84
+
85
+ total_time = commits.map(&:time_in_seconds).compact.reduce(&:+) || 0
86
+
87
+ # has a local time flaw
88
+ recorded_time = GitWakaTime::Heartbeat.where(
89
+ 'DATE(time) >= ? and DATE(time) <= ? ', date.to_time.utc.to_date, date.to_time.utc.to_date
90
+ ).where(project: controller.project).sum(:duration) || 0
91
+
92
+ msgs = commits.map(&:message)
93
+ message = CommitMsgCleaner.new(msgs).call
94
+
95
+ @total_time += total_time
96
+
97
+ hours = (total_time / 60.0 / 60).floor
98
+ minutes = (total_time / 60.0).remainder(60).ceil
99
+
100
+ GitWakaTime::Log.new format(
101
+ '%-100s %-30s %-30s %30s'.purple,
102
+ date,
103
+ "Recorded #{ChronicDuration.output recorded_time}",
104
+ "Commited #{ChronicDuration.output total_time}",
105
+ seconds_to_money(total_time, rate: options.rate)
106
+ )
107
+
108
+ message.split("\n").each do |msg_line|
109
+ GitWakaTime::Log.new format(
110
+ '%-120s'.green,
111
+ msg_line
112
+ )
113
+ end
114
+
115
+ # commits.each do |commit|
116
+ # GitWakaTime::Log.new format(
117
+ # '%-120s %-30s %30s'.green,
118
+ # commit.message.split("\n").first,
119
+ # "Total #{ChronicDuration.output commit.time_in_seconds.to_f}",
120
+ # seconds_to_money(commit.time_in_seconds, rate: options.rate)
121
+ # )
122
+ # end
123
+
124
+ slip = Quickbooks::Model::TimeActivity.new
125
+ slip.txn_date = date
126
+ slip.customer_id = options.customer
127
+ slip.name_of = 'Employee'
128
+ slip.employee_id = 196
129
+
130
+ slip.billable_status = 'Billable' # or NotBillable
131
+ slip.taxable = false
132
+ slip.hours = hours
133
+ slip.hourly_rate = options.rate
134
+ slip.minutes = minutes
135
+ slip.description = message
136
+
137
+ slip
138
+ end
139
+
140
+ GitWakaTime::Log.new format(
141
+ '%-120s %-30s %30s'.red,
142
+ "#{@slips.size} slips",
143
+ "Total #{ChronicDuration.output @total_time}",
144
+ seconds_to_money(@total_time, rate: options.rate)
145
+ )
146
+
147
+ return unless options.add_to_qb
148
+ # Send slips to Quickbooks Online
149
+ @slips.compact.each do |slip|
150
+ next unless ((slip.hours * 60) + slip.minutes) > 0
151
+ puts 'Saving Slip'
152
+ @time_service.create(slip)
153
+ end
154
+ end
155
+ no_commands do
156
+ def seconds_to_money(seconds, rate: 75.0)
157
+ ((seconds.to_f / 3600.0) * rate.to_f).round(2)
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,45 @@
1
+ module GitQuickBooks
2
+ class CommitMsgCleaner
3
+ def initialize(msgs)
4
+ @msgs = msgs
5
+ end
6
+
7
+ def remove_blanks
8
+ @msgs.reject(&:blank?).compact
9
+ end
10
+
11
+ def remove_former_commits
12
+ @msgs.reject { |m| m =~ /Former-commit-id/ }
13
+ end
14
+
15
+ def remove_trail_period
16
+ @msgs.map do |msg|
17
+ # [ci-skip]
18
+ msg.strip.chomp('.')
19
+ end
20
+ end
21
+
22
+ def remove_square_brackets
23
+ @msgs.map do |msg|
24
+ # [ci-skip]
25
+ msg.gsub(/\[.*\]/, '').strip
26
+ end
27
+ end
28
+
29
+ def capitolize_first_word
30
+ @msgs.map do |msg|
31
+ msg[0] = msg[0].to_s.capitalize
32
+ msg
33
+ end
34
+ end
35
+
36
+ def call
37
+ @msgs = remove_blanks
38
+ @msgs = remove_trail_period
39
+ @msgs = remove_former_commits
40
+ @msgs = capitolize_first_word
41
+ @msgs = remove_square_brackets
42
+ @msgs.join("\n")
43
+ end
44
+ end
45
+ end