eboshi 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|