eboshi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +28 -0
  3. data/Rakefile +8 -0
  4. data/app/assets/config/eboshi_manifest.js +1 -0
  5. data/app/assets/stylesheets/eboshi/application.css +15 -0
  6. data/app/controllers/eboshi/adjustments_controller.rb +77 -0
  7. data/app/controllers/eboshi/application_controller.rb +62 -0
  8. data/app/controllers/eboshi/assignments_controller.rb +31 -0
  9. data/app/controllers/eboshi/budgets_controller.rb +43 -0
  10. data/app/controllers/eboshi/calendar_controller.rb +12 -0
  11. data/app/controllers/eboshi/clients_controller.rb +41 -0
  12. data/app/controllers/eboshi/invoices_controller.rb +69 -0
  13. data/app/controllers/eboshi/line_items_controller.rb +28 -0
  14. data/app/controllers/eboshi/payments_controller.rb +31 -0
  15. data/app/controllers/eboshi/user_sessions_controller.rb +25 -0
  16. data/app/controllers/eboshi/users_controller.rb +41 -0
  17. data/app/controllers/eboshi/works_controller.rb +83 -0
  18. data/app/helpers/eboshi/application_helper.rb +15 -0
  19. data/app/helpers/eboshi/calendar_helper.rb +80 -0
  20. data/app/helpers/eboshi/shallow_route_helper.rb +68 -0
  21. data/app/jobs/eboshi/application_job.rb +4 -0
  22. data/app/mailers/eboshi/application_mailer.rb +6 -0
  23. data/app/models/eboshi/adjustment.rb +37 -0
  24. data/app/models/eboshi/application_record.rb +5 -0
  25. data/app/models/eboshi/assignment.rb +20 -0
  26. data/app/models/eboshi/budget.rb +33 -0
  27. data/app/models/eboshi/client.rb +85 -0
  28. data/app/models/eboshi/invoice.rb +87 -0
  29. data/app/models/eboshi/line_item.rb +94 -0
  30. data/app/models/eboshi/payment.rb +20 -0
  31. data/app/models/eboshi/user.rb +128 -0
  32. data/app/models/eboshi/user_session.rb +6 -0
  33. data/app/models/eboshi/work.rb +66 -0
  34. data/app/views/eboshi/adjustments/_adjustment.html.slim +15 -0
  35. data/app/views/eboshi/adjustments/_form.html.haml +27 -0
  36. data/app/views/eboshi/adjustments/edit.html.haml +7 -0
  37. data/app/views/eboshi/adjustments/new.html.haml +8 -0
  38. data/app/views/eboshi/assignments/new.html.haml +18 -0
  39. data/app/views/eboshi/budgets/form.html.haml +22 -0
  40. data/app/views/eboshi/calendar/index.html.slim +32 -0
  41. data/app/views/eboshi/clients/_form.html.haml +34 -0
  42. data/app/views/eboshi/clients/edit.html.haml +4 -0
  43. data/app/views/eboshi/clients/index.html.haml +19 -0
  44. data/app/views/eboshi/clients/new.html.haml +4 -0
  45. data/app/views/eboshi/invoices/_form.html.slim +68 -0
  46. data/app/views/eboshi/invoices/_full.html.haml +76 -0
  47. data/app/views/eboshi/invoices/_invoice.html.haml +2 -0
  48. data/app/views/eboshi/invoices/_mini.html.haml +21 -0
  49. data/app/views/eboshi/invoices/edit.html.haml +14 -0
  50. data/app/views/eboshi/invoices/index.html.haml +46 -0
  51. data/app/views/eboshi/invoices/new.html.haml +11 -0
  52. data/app/views/eboshi/invoices/show.pdf.haml +118 -0
  53. data/app/views/eboshi/layouts/application.html.haml +120 -0
  54. data/app/views/eboshi/payments/_form.html.haml +10 -0
  55. data/app/views/eboshi/payments/new.html.haml +9 -0
  56. data/app/views/eboshi/user_sessions/new.html.haml +19 -0
  57. data/app/views/eboshi/users/_form.html.haml +65 -0
  58. data/app/views/eboshi/users/edit.html.haml +16 -0
  59. data/app/views/eboshi/users/index.html.haml +11 -0
  60. data/app/views/eboshi/users/new.html.haml +13 -0
  61. data/app/views/eboshi/users/show.html.haml +39 -0
  62. data/app/views/eboshi/works/_form.html.slim +25 -0
  63. data/app/views/eboshi/works/_work.html.slim +27 -0
  64. data/app/views/eboshi/works/edit.html.haml +9 -0
  65. data/app/views/eboshi/works/new.html.haml +8 -0
  66. data/app/views/layouts/eboshi/application.html.haml +120 -0
  67. data/config/routes.rb +35 -0
  68. data/lib/eboshi/engine.rb +15 -0
  69. data/lib/eboshi/version.rb +3 -0
  70. data/lib/eboshi.rb +6 -0
  71. data/lib/tasks/eboshi_tasks.rake +4 -0
  72. metadata +253 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9357ca29543c5d56860ecc82f6adc51d0e35f3a9b75357f03af175209767a4bc
4
+ data.tar.gz: 9d327178f35dd138e215cf9a4bc3446231db82578809d33513b686ffc3b7e1d3
5
+ SHA512:
6
+ metadata.gz: f0b4b4d52a165e511aac881851a55dfb3dee206c49877d8914529932cd4a5c9ff3c605edcfdff7f3579b4f4e70ec75a43eb285b42435e7d90937f28243f20a1f
7
+ data.tar.gz: a21c9d6cf4d5812dcb1ce781912b19813b9df7d72f45cab8347a2633417deaa489b1f7a2e4d1bf24bea39c6248f8aca7eceb01b23670d7fd4b1fdde5c9f823db
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # Eboshi
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem "eboshi"
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install eboshi
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/eboshi .css
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,77 @@
1
+ module Eboshi
2
+ class AdjustmentsController < ApplicationController
3
+ before_action :get_client
4
+ before_action :authorized?
5
+
6
+ before_action :filter_date, only: [:create, :update]
7
+ before_action :filter_user, only: [:create, :update]
8
+
9
+ def new
10
+ @adjustment = @client.adjustments.build
11
+ end
12
+
13
+ def create
14
+ @adjustment = @client.adjustments.build params[:adjustment]
15
+ if @adjustment.save
16
+ flash[:notice] = "Adjustment successfully created."
17
+ redirect_to invoices_path(@client)
18
+ else
19
+ render :new
20
+ end
21
+ end
22
+
23
+ def edit
24
+ get_adjustment
25
+ end
26
+
27
+ def update
28
+ get_adjustment
29
+ if @adjustment.update params[:adjustment]
30
+ flash[:notice] = "Successfully updated Adjustment."
31
+ respond_to do |wants|
32
+ wants.html { redirect_to invoices_path(@client) }
33
+ wants.js { head :ok }
34
+ end
35
+ else
36
+ render :edit
37
+ end
38
+ end
39
+
40
+ def destroy
41
+ get_adjustment
42
+ @adjustment.destroy
43
+ respond_to do |wants|
44
+ wants.html { redirect_to invoices_path(@client) }
45
+ wants.js { render json: @adjustment.invoice_total }
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def filter_date
52
+ a = params[:adjustment]
53
+ if a.delete(:no_date) == "1"
54
+ a[:start] = nil
55
+ a.delete_if { |key, _value| key =~ /^start\(.i\)$/ }
56
+ end
57
+ end
58
+
59
+ def filter_user
60
+ a = params[:adjustment]
61
+ a[:user_id] = current_user.id if a[:user_id].blank?
62
+ a[:user_id] = nil if a.delete(:no_user) == "1"
63
+ end
64
+
65
+ def get_adjustment
66
+ @adjustment ||= Adjustment.find params[:id]
67
+ end
68
+
69
+ def get_client
70
+ @client = params[:client_id] ? Client.find(params[:client_id]) : get_adjustment.client
71
+ end
72
+
73
+ def authorized?
74
+ current_user.authorized? @client
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,62 @@
1
+ module Eboshi
2
+ class ApplicationController < ActionController::Base
3
+ helper_method :current_user_session, :current_user
4
+
5
+ helper :all
6
+ include ShallowRouteHelper
7
+
8
+ before_action :activate_authlogic
9
+ before_action :require_user
10
+
11
+ private
12
+
13
+ def current_user_session
14
+ @current_user_session ||= UserSession.find
15
+ end
16
+
17
+ def current_user
18
+ @current_user ||= current_user_session && current_user_session.user
19
+ end
20
+
21
+ def require_admin
22
+ unless current_user.admin?
23
+ head :forbidden
24
+ return false
25
+ end
26
+ end
27
+
28
+ def require_user
29
+ unless current_user
30
+ store_location
31
+ flash[:notice] = "You must be logged in to access this page"
32
+ redirect_to login_path
33
+ return false
34
+ end
35
+ end
36
+
37
+ def require_no_user
38
+ if current_user
39
+ flash[:notice] = "You must be logged out to access this page"
40
+ redirect_to '/'
41
+ return false
42
+ end
43
+ end
44
+
45
+ def store_location
46
+ session[:return_to] = request.fullpath
47
+ end
48
+
49
+ def redirect_to_back_or_default(default)
50
+ redirect_to(session[:return_to] || default)
51
+ session[:return_to] = nil
52
+ end
53
+
54
+ def redirect_to_last_client_or_back_or_default
55
+ if current_user.last_client
56
+ redirect_to invoices_path(current_user.last_client)
57
+ else
58
+ redirect_to_back_or_default '/'
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,31 @@
1
+ module Eboshi
2
+ class AssignmentsController < ApplicationController
3
+ def new
4
+ @client = current_user.clients.find params[:client_id]
5
+ @assignment = @client.assignments.build user: current_user
6
+ end
7
+
8
+ def create
9
+ @client = current_user.clients.find params[:client_id]
10
+ user = User.find_by_id(params[:assignment][:user_id]) || User.find_by_email(params[:assignment][:email])
11
+ if user
12
+ @client.users << user
13
+ redirect_to invoices_path(@client), notice: "Successfully created!"
14
+ else
15
+ redirect_to({ action: :new }, alert: "A user with that email address does not exist!")
16
+ end
17
+ end
18
+
19
+ def destroy
20
+ @assignment = Assignment.find params[:id]
21
+ raise ActiveRecord::RecordNotFound unless current_user.clients.include? @assignment.client
22
+ @assignment.destroy
23
+ if @assignment.user == current_user
24
+ redirect_to "/"
25
+ else
26
+ redirect_back fallback_location: "/"
27
+ end
28
+ end
29
+ end
30
+ end
31
+
@@ -0,0 +1,43 @@
1
+ module Eboshi
2
+ class BudgetsController < ApplicationController
3
+ before_action :get_client, :authorized?
4
+
5
+ def new
6
+ @budget = @client.budgets.build
7
+ render :form
8
+ end
9
+
10
+ def create
11
+ @budget = @client.budgets.build(params[:budget])
12
+ @budget.save!
13
+ redirect_to [@client, :invoices], notice: "Budget created"
14
+ end
15
+
16
+ def edit
17
+ @budget = @client.budgets.find(params[:id])
18
+ render :form
19
+ end
20
+
21
+ def update
22
+ @budget = @client.budgets.find(params[:id])
23
+ @budget.update!(params[:budget])
24
+ redirect_to [@client, :invoices], notice: "Budget updated"
25
+ end
26
+
27
+ def destroy
28
+ @client.budgets.destroy(params[:id])
29
+ redirect_to [@client, :invoices], notice: "Budget destroy"
30
+ end
31
+
32
+ private
33
+
34
+ def get_client
35
+ @client = Client.find(params[:client_id])
36
+ end
37
+
38
+ def authorized?
39
+ current_user.authorized?(@client)
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,12 @@
1
+ module Eboshi
2
+ class CalendarController < ApplicationController
3
+ def index
4
+ @date = begin
5
+ Date.parse("#{params[:year]}/#{params[:month]}/1")
6
+ rescue
7
+ Date.today
8
+ end
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,41 @@
1
+ module Eboshi
2
+ class ClientsController < ApplicationController
3
+ def index
4
+ @clients = current_user.clients
5
+ end
6
+
7
+ def new
8
+ @client = Client.new
9
+ end
10
+
11
+ def edit
12
+ @client = current_user.clients.find(params[:id])
13
+ end
14
+
15
+ def create
16
+ @client = Client.new(params[:client])
17
+ if @client.save
18
+ @client.users << current_user
19
+ redirect_to clients_path, notice: "Client successfully created."
20
+ else
21
+ render :new
22
+ end
23
+ end
24
+
25
+ def update
26
+ @client = current_user.clients.find(params[:id])
27
+ if @client.update(params[:client])
28
+ redirect_to clients_path, notice: "Client successfully updated."
29
+ else
30
+ render :edit
31
+ end
32
+ end
33
+
34
+ def destroy
35
+ @client = current_user.clients.find params[:id]
36
+ @client.destroy
37
+ redirect_to clients_path
38
+ end
39
+ end
40
+ end
41
+
@@ -0,0 +1,69 @@
1
+ module Eboshi
2
+ class InvoicesController < ApplicationController
3
+ before_action :get_client, :authorized?
4
+
5
+ def index
6
+ @invoices = @client.invoices_with_unbilled
7
+ current_user.update_attribute(:last_client, @client)
8
+ respond_to do |wants|
9
+ wants.html
10
+ wants.js { render @client.invoices.paid }
11
+ end
12
+ end
13
+
14
+ def show
15
+ @invoice = @client.invoices.find(params[:id])
16
+ respond_to do |wants|
17
+ wants.html { render partial: params.fetch(:type, "full"), locals: { invoice: @invoice } }
18
+ wants.pdf do
19
+ filename = "#{current_user.business_name_or_name.parameterize}_invoice-\##{@invoice.id}.pdf"
20
+ render pdf: filename, show_as_html: params[:debug].present?
21
+ end
22
+ end
23
+ end
24
+
25
+ def new
26
+ @invoice = @client.build_invoice_from_unbilled(params[:line_item_ids])
27
+ end
28
+
29
+ def create
30
+ @invoice = @client.invoices.build
31
+ @invoice.attributes = params[:invoice]
32
+ if @invoice.save
33
+ redirect_to invoices_path(@client), notice: "Invoice successfully created."
34
+ else
35
+ render :new
36
+ end
37
+ end
38
+
39
+ def edit
40
+ @invoice = @client.invoices.find(params[:id])
41
+ end
42
+
43
+ def update
44
+ @invoice = @client.invoices.find(params[:id])
45
+ if @invoice.update(params[:invoice])
46
+ redirect_to invoices_path(@client), notice: "Invoice successfully updated."
47
+ else
48
+ render :edit
49
+ end
50
+ end
51
+
52
+ def destroy
53
+ @invoice = @client.invoices.find(params[:id])
54
+ @invoice.destroy
55
+ redirect_to invoices_path(@client)
56
+ end
57
+
58
+ private
59
+
60
+ def get_client
61
+ @client = Client.includes(:assignments).find(params[:client_id])
62
+ end
63
+
64
+ def authorized?
65
+ current_user.authorized?(@client)
66
+ end
67
+ end
68
+ end
69
+
@@ -0,0 +1,28 @@
1
+ module Eboshi
2
+ class LineItemsController < ApplicationController
3
+ before_action :get_client
4
+ before_action :authorized?
5
+
6
+ def update
7
+ if line_item.timestamp < params[:line_item][:timestamp].to_i
8
+ line_item.update(params[:line_item])
9
+ end
10
+ render json: {}, status: :ok
11
+ end
12
+
13
+ private
14
+
15
+ def line_item
16
+ @line_item ||= LineItem.find(params[:id])
17
+ end
18
+
19
+ def get_client
20
+ @client ||= (line_item.try(:client) || Client.find(params[:client_id]))
21
+ end
22
+
23
+ def authorized?
24
+ current_user.authorized? @client
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,31 @@
1
+ module Eboshi
2
+ class PaymentsController < ApplicationController
3
+ before_action :get_invoice
4
+ before_action :authorized?
5
+
6
+ def new
7
+ @payment = @invoice.payments.build(params[:payment] || { total: @invoice.balance })
8
+ end
9
+
10
+ def create
11
+ @payment = @invoice.payments.build params[:payment]
12
+ if @payment.save
13
+ flash[:notice] = "Payment successfully created."
14
+ redirect_to invoices_path(@invoice.client)
15
+ else
16
+ render :new
17
+ end
18
+ end
19
+
20
+ protected
21
+
22
+ def get_invoice
23
+ @invoice = Invoice.find params[:invoice_id]
24
+ end
25
+
26
+ def authorized?
27
+ current_user.authorized? @invoice
28
+ end
29
+ end
30
+ end
31
+
@@ -0,0 +1,25 @@
1
+ module Eboshi
2
+ class UserSessionsController < ApplicationController
3
+ before_action :require_no_user, only: [:new, :create]
4
+ before_action :require_user, only: :destroy
5
+
6
+ def new
7
+ @user_session = UserSession.new
8
+ end
9
+
10
+ def create
11
+ @user_session = UserSession.new(params[:user_session].to_h)
12
+ if @user_session.save
13
+ flash[:notice] = "Login successful!"
14
+ redirect_to_last_client_or_back_or_default
15
+ else
16
+ render "new"
17
+ end
18
+ end
19
+
20
+ def destroy
21
+ current_user_session.destroy
22
+ redirect_to login_path, notice: "Logout successful!"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,41 @@
1
+ module Eboshi
2
+ class UsersController < ApplicationController
3
+ def index
4
+ require_admin
5
+ @users = User.all
6
+ end
7
+
8
+ def show
9
+ @user = User.find params[:id]
10
+ end
11
+
12
+ def new
13
+ @user = User.new
14
+ end
15
+
16
+ def create
17
+ @user = User.new params[:user]
18
+ if @user.save
19
+ flash[:notice] = "Account registered!"
20
+ redirect_to @user
21
+ else
22
+ render :new
23
+ end
24
+ end
25
+
26
+ def edit
27
+ @user = User.find params[:id]
28
+ end
29
+
30
+ def update
31
+ @user = User.find params[:id]
32
+ if @user.update params[:user]
33
+ flash[:notice] = "Account updated!"
34
+ redirect_to_last_client_or_back_or_default
35
+ else
36
+ render :edit
37
+ end
38
+ end
39
+ end
40
+ end
41
+
@@ -0,0 +1,83 @@
1
+ module Eboshi
2
+ class WorksController < ApplicationController
3
+ before_action :get_client
4
+ before_action :authorized?
5
+
6
+ def new
7
+ @work = @client.works.build user: current_user
8
+ end
9
+
10
+ def create
11
+ @work = @client.works.build params[:work].merge(user: current_user)
12
+ if @work.save
13
+ flash[:notice] = "Successfully created Work."
14
+ redirect_to invoices_path(@client)
15
+ else
16
+ render :new
17
+ end
18
+ end
19
+
20
+ def edit
21
+ get_work
22
+ end
23
+
24
+ def update
25
+ get_work
26
+ if @work.update params[:work]
27
+ flash[:notice] = "Successfully updated Work."
28
+ respond_to do |wants|
29
+ wants.html { redirect_to invoices_path(@client) }
30
+ wants.js { head :ok }
31
+ end
32
+ else
33
+ render :edit
34
+ end
35
+ end
36
+
37
+ def destroy
38
+ get_work
39
+ @work.destroy
40
+ invoice = @work.invoice || @client.build_invoice_from_unbilled
41
+ redirect_to invoices_path(@client), status: :see_other
42
+ end
43
+
44
+ def clock_in
45
+ @work = @client.clock_in current_user
46
+ redirect_to invoices_path(@client), status: :see_other
47
+ end
48
+
49
+ def clock_out
50
+ get_work
51
+ @work.clock_out
52
+ redirect_to invoices_path(@client), status: :see_other
53
+ end
54
+
55
+ def merge
56
+ @work = Work.merge_from_ids params[:line_item_ids]
57
+ @invoice = @work.invoice || @client.build_invoice_from_unbilled
58
+ render @work
59
+ end
60
+
61
+ def convert
62
+ get_work
63
+ @work.to_adjustment!
64
+ flash[:notice] = "Time item converted to adjustment"
65
+ redirect_to invoices_path(@client)
66
+ end
67
+
68
+ private
69
+
70
+ def get_work
71
+ @work ||= Work.find params[:id]
72
+ end
73
+
74
+ def get_client
75
+ @client = params[:client_id] ? Client.find(params[:client_id]) : get_work.client
76
+ end
77
+
78
+ def authorized?
79
+ current_user.authorized? @client
80
+ end
81
+ end
82
+ end
83
+
@@ -0,0 +1,15 @@
1
+ module Eboshi
2
+ module ApplicationHelper
3
+ def number_to_per_hour(value)
4
+ return nil unless value
5
+ precision = (value.round == value ? 0 : 2)
6
+ number_to_currency(value, precision: precision) + "/hr"
7
+ end
8
+
9
+ def currency_or_empty(value)
10
+ return '--' unless value.nonzero?
11
+ number_to_currency value
12
+ end
13
+ end
14
+ end
15
+