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.
- checksums.yaml +7 -0
- data/README.md +28 -0
- data/Rakefile +8 -0
- data/app/assets/config/eboshi_manifest.js +1 -0
- data/app/assets/stylesheets/eboshi/application.css +15 -0
- data/app/controllers/eboshi/adjustments_controller.rb +77 -0
- data/app/controllers/eboshi/application_controller.rb +62 -0
- data/app/controllers/eboshi/assignments_controller.rb +31 -0
- data/app/controllers/eboshi/budgets_controller.rb +43 -0
- data/app/controllers/eboshi/calendar_controller.rb +12 -0
- data/app/controllers/eboshi/clients_controller.rb +41 -0
- data/app/controllers/eboshi/invoices_controller.rb +69 -0
- data/app/controllers/eboshi/line_items_controller.rb +28 -0
- data/app/controllers/eboshi/payments_controller.rb +31 -0
- data/app/controllers/eboshi/user_sessions_controller.rb +25 -0
- data/app/controllers/eboshi/users_controller.rb +41 -0
- data/app/controllers/eboshi/works_controller.rb +83 -0
- data/app/helpers/eboshi/application_helper.rb +15 -0
- data/app/helpers/eboshi/calendar_helper.rb +80 -0
- data/app/helpers/eboshi/shallow_route_helper.rb +68 -0
- data/app/jobs/eboshi/application_job.rb +4 -0
- data/app/mailers/eboshi/application_mailer.rb +6 -0
- data/app/models/eboshi/adjustment.rb +37 -0
- data/app/models/eboshi/application_record.rb +5 -0
- data/app/models/eboshi/assignment.rb +20 -0
- data/app/models/eboshi/budget.rb +33 -0
- data/app/models/eboshi/client.rb +85 -0
- data/app/models/eboshi/invoice.rb +87 -0
- data/app/models/eboshi/line_item.rb +94 -0
- data/app/models/eboshi/payment.rb +20 -0
- data/app/models/eboshi/user.rb +128 -0
- data/app/models/eboshi/user_session.rb +6 -0
- data/app/models/eboshi/work.rb +66 -0
- data/app/views/eboshi/adjustments/_adjustment.html.slim +15 -0
- data/app/views/eboshi/adjustments/_form.html.haml +27 -0
- data/app/views/eboshi/adjustments/edit.html.haml +7 -0
- data/app/views/eboshi/adjustments/new.html.haml +8 -0
- data/app/views/eboshi/assignments/new.html.haml +18 -0
- data/app/views/eboshi/budgets/form.html.haml +22 -0
- data/app/views/eboshi/calendar/index.html.slim +32 -0
- data/app/views/eboshi/clients/_form.html.haml +34 -0
- data/app/views/eboshi/clients/edit.html.haml +4 -0
- data/app/views/eboshi/clients/index.html.haml +19 -0
- data/app/views/eboshi/clients/new.html.haml +4 -0
- data/app/views/eboshi/invoices/_form.html.slim +68 -0
- data/app/views/eboshi/invoices/_full.html.haml +76 -0
- data/app/views/eboshi/invoices/_invoice.html.haml +2 -0
- data/app/views/eboshi/invoices/_mini.html.haml +21 -0
- data/app/views/eboshi/invoices/edit.html.haml +14 -0
- data/app/views/eboshi/invoices/index.html.haml +46 -0
- data/app/views/eboshi/invoices/new.html.haml +11 -0
- data/app/views/eboshi/invoices/show.pdf.haml +118 -0
- data/app/views/eboshi/layouts/application.html.haml +120 -0
- data/app/views/eboshi/payments/_form.html.haml +10 -0
- data/app/views/eboshi/payments/new.html.haml +9 -0
- data/app/views/eboshi/user_sessions/new.html.haml +19 -0
- data/app/views/eboshi/users/_form.html.haml +65 -0
- data/app/views/eboshi/users/edit.html.haml +16 -0
- data/app/views/eboshi/users/index.html.haml +11 -0
- data/app/views/eboshi/users/new.html.haml +13 -0
- data/app/views/eboshi/users/show.html.haml +39 -0
- data/app/views/eboshi/works/_form.html.slim +25 -0
- data/app/views/eboshi/works/_work.html.slim +27 -0
- data/app/views/eboshi/works/edit.html.haml +9 -0
- data/app/views/eboshi/works/new.html.haml +8 -0
- data/app/views/layouts/eboshi/application.html.haml +120 -0
- data/config/routes.rb +35 -0
- data/lib/eboshi/engine.rb +15 -0
- data/lib/eboshi/version.rb +3 -0
- data/lib/eboshi.rb +6 -0
- data/lib/tasks/eboshi_tasks.rake +4 -0
- metadata +253 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Eboshi
|
4
|
+
module CalendarHelper
|
5
|
+
def calendar(options = {}, &block)
|
6
|
+
defaults = {
|
7
|
+
year: Date.today.year,
|
8
|
+
month: Date.today.month,
|
9
|
+
previous_month_text: nil,
|
10
|
+
next_month_text: nil
|
11
|
+
}
|
12
|
+
options.reverse_merge! defaults
|
13
|
+
|
14
|
+
first = Date.civil(options[:year], options[:month], 1)
|
15
|
+
last = first.end_of_month
|
16
|
+
|
17
|
+
build first, last, options, &block
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def build(first, last, _options, &block)
|
23
|
+
content_tag :tbody do
|
24
|
+
content_tag :tr do
|
25
|
+
concat last_month_days(first) # start out the week with last month's days
|
26
|
+
concat this_month_days(first, last, &block)
|
27
|
+
concat next_month_days(last) # finish out the week with next month's days
|
28
|
+
"".html_safe
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def last_month_days(first)
|
34
|
+
first.beginning_of_week(:sunday).upto(first - 1.day).map do |d|
|
35
|
+
content_tag :td, class: "otherMonth" do
|
36
|
+
concat d.day
|
37
|
+
end
|
38
|
+
end.join("\n").html_safe
|
39
|
+
end
|
40
|
+
|
41
|
+
def this_month_days(first, last, &block)
|
42
|
+
first.upto(last).map do |date|
|
43
|
+
out = content_tag :td, id: "day_#{date.day}", class: "#{date.today? && "today"}" do
|
44
|
+
date.mday.to_s.html_safe + capture(date, &block).to_s.html_safe
|
45
|
+
end
|
46
|
+
unless date == last || !date.saturday?
|
47
|
+
out << raw("</tr><tr>")
|
48
|
+
end
|
49
|
+
out.html_safe
|
50
|
+
end.join("\n").html_safe
|
51
|
+
end
|
52
|
+
|
53
|
+
def next_month_days(last)
|
54
|
+
unless last.saturday?
|
55
|
+
(last + 1).upto(last.end_of_week(:sunday)) do |d|
|
56
|
+
content_tag :td, class: "otherMonth" do
|
57
|
+
concat d.day
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
module CalendarHelper
|
65
|
+
def link_to_previous_month(date, options = {})
|
66
|
+
link_to_month(:prev, date, options)
|
67
|
+
end
|
68
|
+
|
69
|
+
def link_to_next_month(date, options = {})
|
70
|
+
link_to_month(:next, date, options)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def link_to_month(direction, date, options = {})
|
76
|
+
date += (direction == :next ? 1 : -1).month
|
77
|
+
link_to direction, "/calendar/#{date.year}/#{date.month}", options
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Eboshi
|
2
|
+
module ShallowRouteHelper
|
3
|
+
def invoices_path(client, options = {})
|
4
|
+
client_invoices_path(client, options)
|
5
|
+
end
|
6
|
+
|
7
|
+
def invoice_path(invoice, options = {})
|
8
|
+
client_invoice_path(invoice.client, invoice, options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def new_invoice_path(client, options = {})
|
12
|
+
new_client_invoice_path(client, options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def edit_invoice_path(invoice, options = {})
|
16
|
+
edit_client_invoice_path(invoice.client, invoice, options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def payments_path(invoice, options = {})
|
20
|
+
client_invoice_payments_path(invoice.client, invoice, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def new_payment_path(invoice, options = {})
|
24
|
+
new_client_invoice_payment_path(invoice.client, invoice, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def new_adjustment_path(adjustment, options = {})
|
28
|
+
new_client_adjustment_path(adjustment.client, adjustment, options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def edit_adjustment_path(adjustment, options = {})
|
32
|
+
edit_client_adjustment_path(adjustment.client, adjustment, options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def work_path(work, options = {})
|
36
|
+
client_work_path(work.client, work, options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def edit_work_path(work, options = {})
|
40
|
+
edit_client_work_path(work.client, work, options)
|
41
|
+
end
|
42
|
+
|
43
|
+
def merge_works_path(client, options = {})
|
44
|
+
merge_client_works_path(client, options)
|
45
|
+
end
|
46
|
+
|
47
|
+
def convert_work_path(work, options = {})
|
48
|
+
convert_client_work_path(work.client, work, options)
|
49
|
+
end
|
50
|
+
|
51
|
+
def line_item_path(work, options = {})
|
52
|
+
client_line_item_path(work.client, work, options)
|
53
|
+
end
|
54
|
+
|
55
|
+
def assignments_path(client, options = {})
|
56
|
+
client_assignments_path(client, options)
|
57
|
+
end
|
58
|
+
|
59
|
+
def assignment_path(assignment, options = {})
|
60
|
+
client_assignment_path(assignment.client, assignment, options)
|
61
|
+
end
|
62
|
+
|
63
|
+
def new_assignment_path(client, options = {})
|
64
|
+
new_client_assignment_path(client, options)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# == Schema Information
|
2
|
+
# Schema version: 20081222015211
|
3
|
+
#
|
4
|
+
# Table name: line_items
|
5
|
+
#
|
6
|
+
# id :integer(4) not null, primary key
|
7
|
+
# client_id :integer(4)
|
8
|
+
# user_id :integer(4)
|
9
|
+
# start :datetime
|
10
|
+
# finish :datetime
|
11
|
+
# rate :decimal(10, 2)
|
12
|
+
# notes :text
|
13
|
+
# created_at :datetime
|
14
|
+
# updated_at :datetime
|
15
|
+
# invoice_id :integer(4)
|
16
|
+
# type :string(255)
|
17
|
+
#
|
18
|
+
|
19
|
+
module Eboshi
|
20
|
+
class Adjustment < LineItem
|
21
|
+
def total
|
22
|
+
rate
|
23
|
+
end
|
24
|
+
|
25
|
+
def total=(value)
|
26
|
+
self.rate = value
|
27
|
+
end
|
28
|
+
|
29
|
+
def no_user
|
30
|
+
user.nil? && !new_record?
|
31
|
+
end
|
32
|
+
|
33
|
+
def no_date
|
34
|
+
start.nil? && !new_record?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# == Schema Information
|
2
|
+
# Schema version: 20090412030221
|
3
|
+
#
|
4
|
+
# Table name: assignments
|
5
|
+
#
|
6
|
+
# id :integer(4) not null, primary key
|
7
|
+
# user_id :integer(4)
|
8
|
+
# client_id :integer(4)
|
9
|
+
# created_at :datetime
|
10
|
+
# updated_at :datetime
|
11
|
+
#
|
12
|
+
|
13
|
+
module Eboshi
|
14
|
+
class Assignment < ApplicationRecord
|
15
|
+
self.table_name = "assignments"
|
16
|
+
|
17
|
+
belongs_to :user, required: false
|
18
|
+
belongs_to :client, required: false
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Eboshi
|
2
|
+
class Budget < ApplicationRecord
|
3
|
+
self.table_name = "budgets"
|
4
|
+
|
5
|
+
belongs_to :client, touch: true, required: false
|
6
|
+
has_many :invoices
|
7
|
+
|
8
|
+
validates_presence_of :total
|
9
|
+
|
10
|
+
attr_accessor :unbilled_invoice
|
11
|
+
|
12
|
+
def used
|
13
|
+
invoices.to_a.sum(&:total) + unbilled_invoice.try(:total).to_f
|
14
|
+
end
|
15
|
+
|
16
|
+
def remaining
|
17
|
+
total - used
|
18
|
+
end
|
19
|
+
|
20
|
+
def name_and_total
|
21
|
+
[name, total.to_s].join(" - $")
|
22
|
+
end
|
23
|
+
|
24
|
+
def under?
|
25
|
+
used < total
|
26
|
+
end
|
27
|
+
|
28
|
+
def over?
|
29
|
+
used > total
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# == Schema Information
|
2
|
+
# Schema version: 20081222015211
|
3
|
+
#
|
4
|
+
# Table name: clients
|
5
|
+
#
|
6
|
+
# id :integer(4) not null, primary key
|
7
|
+
# name :string(255)
|
8
|
+
# address :string(255)
|
9
|
+
# city :string(255)
|
10
|
+
# state :string(255)
|
11
|
+
# zip :string(255)
|
12
|
+
# country :string(255)
|
13
|
+
# email :string(255)
|
14
|
+
# contact :string(255)
|
15
|
+
# phone :string(255)
|
16
|
+
# created_at :datetime
|
17
|
+
# updated_at :datetime
|
18
|
+
#
|
19
|
+
|
20
|
+
module Eboshi
|
21
|
+
class Client < ApplicationRecord
|
22
|
+
self.table_name = "clients"
|
23
|
+
|
24
|
+
default_scope -> { order(:name) }
|
25
|
+
|
26
|
+
has_many :line_items, dependent: :destroy
|
27
|
+
has_many :works
|
28
|
+
has_many :adjustments
|
29
|
+
has_many :invoices, -> { includes(:line_items, :payments).order(date: :desc) }, dependent: :destroy
|
30
|
+
has_many :payments, through: :invoices
|
31
|
+
has_many :budgets
|
32
|
+
has_many :assignments, dependent: :destroy
|
33
|
+
has_many :users, through: :assignments
|
34
|
+
|
35
|
+
validates_presence_of :name
|
36
|
+
|
37
|
+
def build_invoice_from_unbilled(line_items_ids = nil)
|
38
|
+
lis = line_items_ids ? LineItem.find(line_items_ids) : line_items.unbilled
|
39
|
+
budget = budgets.order(:created_at).last
|
40
|
+
invoice = invoices.build line_items: lis, budget: budget
|
41
|
+
budget.unbilled_invoice = invoice if budget
|
42
|
+
invoice
|
43
|
+
end
|
44
|
+
|
45
|
+
def invoices_with_unbilled
|
46
|
+
new_invoices = invoices.to_a
|
47
|
+
new_invoices.unshift build_invoice_from_unbilled
|
48
|
+
new_invoices
|
49
|
+
end
|
50
|
+
|
51
|
+
def balance
|
52
|
+
credits - debits
|
53
|
+
end
|
54
|
+
|
55
|
+
def unbilled_balance
|
56
|
+
return 0.0 if line_items.empty?
|
57
|
+
line_items.to_a.select(&:unbilled?).sum(&:total)
|
58
|
+
end
|
59
|
+
|
60
|
+
def overdue_balance
|
61
|
+
balance - unbilled_balance
|
62
|
+
end
|
63
|
+
|
64
|
+
def credits
|
65
|
+
line_items.to_a.sum(&:total)
|
66
|
+
end
|
67
|
+
|
68
|
+
def debits
|
69
|
+
payments.to_a.sum(&:total)
|
70
|
+
end
|
71
|
+
|
72
|
+
def clock_in(user)
|
73
|
+
Work.new.tap do |line_item|
|
74
|
+
now = Time.now
|
75
|
+
line_item.attributes = { start: now, finish: now, user: user, rate: user.default_rate_for(self) }
|
76
|
+
line_items << line_item
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def default_project_name
|
81
|
+
invoices.last.try(:project_name)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# == Schema Information
|
2
|
+
# Schema version: 20081222015211
|
3
|
+
#
|
4
|
+
# Table name: invoices
|
5
|
+
#
|
6
|
+
# id :integer(4) not null, primary key
|
7
|
+
# client_id :integer(4)
|
8
|
+
# date :datetime
|
9
|
+
# project_name :string(255)
|
10
|
+
#
|
11
|
+
|
12
|
+
module Eboshi
|
13
|
+
class Invoice < ApplicationRecord
|
14
|
+
self.table_name = "invoices"
|
15
|
+
|
16
|
+
belongs_to :client, touch: true, required: false
|
17
|
+
has_many :line_items, dependent: :nullify
|
18
|
+
has_many :payments, -> { order(created_at: :desc) }, dependent: :destroy
|
19
|
+
belongs_to :budget, touch: true, required: false
|
20
|
+
|
21
|
+
validates_presence_of :client, :date
|
22
|
+
|
23
|
+
default_value_for(:date) { Time.zone.today.midnight }
|
24
|
+
|
25
|
+
def self.unpaid
|
26
|
+
order(date: :desc).reject(&:paid?)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.paid
|
30
|
+
order(date: :desc).select(&:paid?)
|
31
|
+
end
|
32
|
+
|
33
|
+
def works
|
34
|
+
line_items.where(type: "Eboshi::Work")
|
35
|
+
end
|
36
|
+
|
37
|
+
def adjustments
|
38
|
+
line_items.where(type: "Eboshi::Adjustment")
|
39
|
+
end
|
40
|
+
|
41
|
+
def total
|
42
|
+
line_items.to_a.sum(&:total)
|
43
|
+
end
|
44
|
+
|
45
|
+
def total_for_user(user)
|
46
|
+
line_items.to_a.select { |li| li.user == user }.sum(&:total)
|
47
|
+
end
|
48
|
+
|
49
|
+
def total=(value)
|
50
|
+
difference = value.to_f - total
|
51
|
+
return total if difference.abs < 0.01
|
52
|
+
line_items << Adjustment.new(total: difference, client_id: client_id)
|
53
|
+
end
|
54
|
+
|
55
|
+
def balance
|
56
|
+
total - payments.to_a.sum(&:total)
|
57
|
+
end
|
58
|
+
|
59
|
+
def status
|
60
|
+
return 'unbilled' if new_record?
|
61
|
+
paid? ? 'paid' : 'unpaid'
|
62
|
+
end
|
63
|
+
|
64
|
+
def paid?
|
65
|
+
!line_items.empty? && !payments.empty? && balance == 0
|
66
|
+
end
|
67
|
+
|
68
|
+
def total_hours
|
69
|
+
line_items.to_a.sum(&:hours)
|
70
|
+
end
|
71
|
+
|
72
|
+
def total_hours_for_user(user)
|
73
|
+
line_items.to_a.select { |li| li.user == user }.sum(&:hours)
|
74
|
+
end
|
75
|
+
|
76
|
+
def consistant_rate
|
77
|
+
return true if works.empty?
|
78
|
+
rates = works.collect(&:rate).uniq
|
79
|
+
rates.length == 1 ? rates.first : false
|
80
|
+
end
|
81
|
+
|
82
|
+
def users
|
83
|
+
line_items.collect(&:user).uniq.sort_by { |user| user ? user.name : "" }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# == Schema Information
|
2
|
+
# Schema version: 20081222015211
|
3
|
+
#
|
4
|
+
# Table name: line_items
|
5
|
+
#
|
6
|
+
# id :integer(4) not null, primary key
|
7
|
+
# client_id :integer(4)
|
8
|
+
# user_id :integer(4)
|
9
|
+
# start :datetime
|
10
|
+
# finish :datetime
|
11
|
+
# rate :decimal(10, 2)
|
12
|
+
# notes :text
|
13
|
+
# created_at :datetime
|
14
|
+
# updated_at :datetime
|
15
|
+
# invoice_id :integer(4)
|
16
|
+
# type :string(255)
|
17
|
+
#
|
18
|
+
|
19
|
+
module Eboshi
|
20
|
+
class LineItem < ApplicationRecord
|
21
|
+
self.table_name = "line_items"
|
22
|
+
|
23
|
+
include Comparable
|
24
|
+
|
25
|
+
belongs_to :client, touch: true, required: false
|
26
|
+
belongs_to :user, required: false
|
27
|
+
belongs_to :invoice, touch: true, required: false
|
28
|
+
|
29
|
+
validates_presence_of :client, :rate
|
30
|
+
|
31
|
+
def self.unbilled
|
32
|
+
where(invoice_id: nil).order(start: :desc)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.on_date(date)
|
36
|
+
start = date.beginning_of_day
|
37
|
+
finish = date.end_of_day
|
38
|
+
where(start: start..finish)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.on_week(date)
|
42
|
+
start = date.beginning_of_week(:sunday).beginning_of_day
|
43
|
+
finish = date.end_of_week(:sunday).end_of_day
|
44
|
+
where(start: start..finish)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.on_month(date)
|
48
|
+
start = date.beginning_of_month.beginning_of_day
|
49
|
+
finish = date.end_of_month.end_of_day
|
50
|
+
where(start: start..finish)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.on_year(date)
|
54
|
+
start = date.beginning_of_year.beginning_of_day
|
55
|
+
finish = date.end_of_year.end_of_day
|
56
|
+
where(start: start..finish)
|
57
|
+
end
|
58
|
+
|
59
|
+
def total
|
60
|
+
0
|
61
|
+
end
|
62
|
+
|
63
|
+
def hours
|
64
|
+
return 0 unless finish && start
|
65
|
+
seconds = BigDecimal((finish - start).to_s)
|
66
|
+
seconds / 60 / 60
|
67
|
+
end
|
68
|
+
|
69
|
+
def ==(target)
|
70
|
+
target == id
|
71
|
+
end
|
72
|
+
|
73
|
+
def unbilled?
|
74
|
+
invoice_id.nil?
|
75
|
+
end
|
76
|
+
|
77
|
+
def checked?
|
78
|
+
unbilled?
|
79
|
+
end
|
80
|
+
|
81
|
+
def user_name=(name)
|
82
|
+
self.user = User.find_by_name!(name) unless name.nil?
|
83
|
+
end
|
84
|
+
|
85
|
+
def invoice_total
|
86
|
+
invoice.try(:total) || client.unbilled_balance
|
87
|
+
end
|
88
|
+
|
89
|
+
def <=>(target)
|
90
|
+
(target.start || Time.zone.parse("0000-01-01")) <=> (start || Time.zone.parse("0000-01-01"))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# == Schema Information
|
2
|
+
# Schema version: 20081222015211
|
3
|
+
#
|
4
|
+
# Table name: payments
|
5
|
+
#
|
6
|
+
# id :integer(4) not null, primary key
|
7
|
+
# invoice_id :integer(4)
|
8
|
+
# total :decimal(10, 2)
|
9
|
+
# created_at :datetime
|
10
|
+
# updated_at :datetime
|
11
|
+
#
|
12
|
+
|
13
|
+
module Eboshi
|
14
|
+
class Payment < ApplicationRecord
|
15
|
+
self.table_name = "payments"
|
16
|
+
|
17
|
+
belongs_to :invoice, touch: true, required: false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# == Schema Information
|
2
|
+
# Schema version: 20090412030221
|
3
|
+
#
|
4
|
+
# Table name: users
|
5
|
+
#
|
6
|
+
# id :integer(4) not null, primary key
|
7
|
+
# name :string(255)
|
8
|
+
# email :string(255)
|
9
|
+
# crypted_password :string(255)
|
10
|
+
# password_salt :string(255)
|
11
|
+
# created_at :datetime
|
12
|
+
# updated_at :datetime
|
13
|
+
# rate :decimal(10, 2)
|
14
|
+
# color :string(255)
|
15
|
+
# persistence_token :string(255)
|
16
|
+
# login_count :integer(4)
|
17
|
+
# last_request_at :datetime
|
18
|
+
# last_login_at :datetime
|
19
|
+
# current_login_at :datetime
|
20
|
+
# last_login_ip :string(255)
|
21
|
+
# current_login_ip :string(255)
|
22
|
+
# last_client_id :integer(4)
|
23
|
+
# admin :boolean(1)
|
24
|
+
# logo_file_name :string(255)
|
25
|
+
# logo_content_type :string(255)
|
26
|
+
# logo_file_size :integer(4)
|
27
|
+
# logo_updated_at :datetime
|
28
|
+
# signature_file_name :string(255)
|
29
|
+
# signature_content_type :string(255)
|
30
|
+
# signature_file_size :integer(4)
|
31
|
+
# signature_updated_at :datetime
|
32
|
+
#
|
33
|
+
|
34
|
+
module Eboshi
|
35
|
+
class User < ApplicationRecord
|
36
|
+
self.table_name = "eboshi_users"
|
37
|
+
|
38
|
+
acts_as_authentic do |config|
|
39
|
+
config.crypto_provider = Authlogic::CryptoProviders::SCrypt
|
40
|
+
config.transition_from_crypto_providers = [Authlogic::CryptoProviders::Sha512]
|
41
|
+
end
|
42
|
+
|
43
|
+
validates_uniqueness_of :email, case_sensitive: false
|
44
|
+
validates_email_format_of :email
|
45
|
+
validates_presence_of :password, :on => :create
|
46
|
+
validates_confirmation_of :password, if: proc { |user| user.password.present? }
|
47
|
+
validates_length_of :password, minimum: 4, allow_blank: true, message: "must be at least five characters long"
|
48
|
+
validates_length_of :password_confirmation, minimum: 4, allow_blank: true, message: "must be at least five characters long"
|
49
|
+
|
50
|
+
belongs_to :last_client, class_name: "Client", required: false
|
51
|
+
has_many :assignments, dependent: :destroy
|
52
|
+
has_many :clients, through: :assignments
|
53
|
+
has_many :works
|
54
|
+
|
55
|
+
has_attached_file :logo,
|
56
|
+
styles: { pdf: "200x200>" },
|
57
|
+
path: ":rails_root/public/:attachment/:id/:style.:extension",
|
58
|
+
url: "/:attachment/:id/:style.:extension"
|
59
|
+
has_attached_file :signature,
|
60
|
+
styles: { pdf: "450x100>" },
|
61
|
+
path: ":rails_root/public/:attachment/:id/:style.:extension",
|
62
|
+
url: "/:attachment/:id/:style.:extension"
|
63
|
+
validates_attachment_content_type :logo, content_type: ["image/jpg", "image/jpeg", "image/png", "image/gif"]
|
64
|
+
validates_attachment_content_type :signature, content_type: ["image/jpg", "image/jpeg", "image/png", "image/gif"]
|
65
|
+
|
66
|
+
def default_rate_for(client)
|
67
|
+
last_work = client.works.complete.where(user: self).order(start: :desc).first
|
68
|
+
last_work.try(:rate) || rate
|
69
|
+
end
|
70
|
+
|
71
|
+
def related_users
|
72
|
+
clients.collect(&:users).flatten.uniq - [self]
|
73
|
+
end
|
74
|
+
|
75
|
+
def authorized?(object)
|
76
|
+
return if object.is_a?(Client) && clients.include?(object)
|
77
|
+
return if object.is_a?(Invoice) && clients.include?(object.client)
|
78
|
+
return if object.is_a?(Assignment) && object.client.users.include?(self)
|
79
|
+
raise ActiveRecord::RecordNotFound
|
80
|
+
end
|
81
|
+
|
82
|
+
def city_state_zip
|
83
|
+
return nil if city.blank? || state.blank? || zip.blank?
|
84
|
+
"#{city}, #{state} #{zip}"
|
85
|
+
end
|
86
|
+
|
87
|
+
def business_name_or_name
|
88
|
+
business_name.blank? ? name : business_name
|
89
|
+
end
|
90
|
+
|
91
|
+
def business_email_or_email
|
92
|
+
business_email.blank? ? email : business_email
|
93
|
+
end
|
94
|
+
|
95
|
+
def hours_by_date(date)
|
96
|
+
works.on_date(date).to_a.sum(&:hours)
|
97
|
+
end
|
98
|
+
|
99
|
+
def total_by_date(date)
|
100
|
+
works.on_date(date).to_a.sum(&:total)
|
101
|
+
end
|
102
|
+
|
103
|
+
def hours_by_week(date)
|
104
|
+
works.on_week(date).to_a.sum(&:hours)
|
105
|
+
end
|
106
|
+
|
107
|
+
def total_by_week(date)
|
108
|
+
works.on_week(date).to_a.sum(&:total)
|
109
|
+
end
|
110
|
+
|
111
|
+
def hours_by_month(date)
|
112
|
+
works.on_month(date).to_a.sum(&:hours)
|
113
|
+
end
|
114
|
+
|
115
|
+
def total_by_month(date)
|
116
|
+
works.on_month(date).to_a.sum(&:total)
|
117
|
+
end
|
118
|
+
|
119
|
+
def hours_by_year(date)
|
120
|
+
works.on_year(date).to_a.sum(&:hours)
|
121
|
+
end
|
122
|
+
|
123
|
+
def total_by_year(date)
|
124
|
+
works.on_year(date).to_a.sum(&:total)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|