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,66 @@
|
|
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 Work < LineItem
|
21
|
+
validates_presence_of :user, :start, :finish
|
22
|
+
|
23
|
+
def self.complete
|
24
|
+
where("start <> finish")
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.merge_from_ids(ids)
|
28
|
+
works = order(finish: :desc).find(ids)
|
29
|
+
hours = works.sum(&:hours)
|
30
|
+
notes = works.collect(&:notes).select(&:present?).join(' ')
|
31
|
+
works.first.update hours: hours, notes: notes
|
32
|
+
|
33
|
+
destroy ids[1..-1] # destroy all but the first work
|
34
|
+
|
35
|
+
works.first
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_adjustment!
|
39
|
+
self.type = "Eboshi::Adjustment"
|
40
|
+
self.rate = total
|
41
|
+
self.finish = start
|
42
|
+
save!
|
43
|
+
end
|
44
|
+
|
45
|
+
def total
|
46
|
+
(rate * hours).round(2)
|
47
|
+
end
|
48
|
+
|
49
|
+
def total=(value)
|
50
|
+
self.hours = value / rate
|
51
|
+
end
|
52
|
+
|
53
|
+
def hours=(total)
|
54
|
+
update_attribute :finish, start + total.hours
|
55
|
+
end
|
56
|
+
|
57
|
+
def clock_out
|
58
|
+
update finish: Time.now
|
59
|
+
end
|
60
|
+
|
61
|
+
def incomplete?
|
62
|
+
start >= finish
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
tr.line_item.adjustment id="line_item_#{adjustment.id}_tr" class=cycle(' even','') data-controller="line-item"
|
2
|
+
td = check_box_tag 'line_item_ids[]', adjustment.id, nil, id: "line_item_ids_#{adjustment.id}", data: { line_item_target: "checkbox" }
|
3
|
+
td.name.user_name style="color: \##{adjustment.user&.color}" = adjustment.user&.name
|
4
|
+
td.time = adjustment.start&.to_fs(:slash)
|
5
|
+
td colspan=2 align="center" : i Flat Fee
|
6
|
+
td = number_to_currency adjustment.total
|
7
|
+
td.notes
|
8
|
+
textarea id="notes_#{adjustment.id}" autocomplete="off" data-controller="live-textarea" data-live-textarea-url-value=line_item_path(adjustment)
|
9
|
+
= adjustment.notes
|
10
|
+
|
11
|
+
td
|
12
|
+
= link_to image_tag("edit.png", size: "12x13"), [:edit, adjustment.client, adjustment], title: "Edit"
|
13
|
+
= link_to image_tag("delete.png", size: "11x10"), [adjustment.client, adjustment], method: :delete, class: "delete",
|
14
|
+
data: { confirm: "Are you sure you want to delete this line item? This cannot be undone!", line_item_target: "delete" }
|
15
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
= error_messages_for :adjustment
|
2
|
+
|
3
|
+
= form_for [@client, @adjustment] do |form|
|
4
|
+
%p
|
5
|
+
%label User
|
6
|
+
= @adjustment.user ? @adjustment.user.name : current_user.name
|
7
|
+
= form.hidden_field :user_id
|
8
|
+
|
9
|
+
= form.check_box :no_user
|
10
|
+
= form.label :no_user
|
11
|
+
|
12
|
+
%p
|
13
|
+
= form.label :start, "Date"
|
14
|
+
= form.date_field :start
|
15
|
+
|
16
|
+
= form.check_box :no_date
|
17
|
+
= form.label :no_date
|
18
|
+
|
19
|
+
%p
|
20
|
+
= form.label :rate, "Amount"
|
21
|
+
==$#{form.text_field :rate}
|
22
|
+
|
23
|
+
%p
|
24
|
+
= form.label :notes
|
25
|
+
= form.text_area :notes
|
26
|
+
|
27
|
+
%p= form.submit @adjustment.new_record? ? "Create" : "Update"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
- content_for :title do
|
2
|
+
= @client.name
|
3
|
+
» Invite Collaborator
|
4
|
+
|
5
|
+
= form_for [@client, @assignment] do |f|
|
6
|
+
%h2 Existing User
|
7
|
+
%ul#existing_users
|
8
|
+
- current_user.related_users.each do |user|
|
9
|
+
%li
|
10
|
+
= f.radio_button "user_id", user.id
|
11
|
+
= label_tag "assignment_user_id_#{user.id}", h(user.name)
|
12
|
+
|
13
|
+
%li
|
14
|
+
= radio_button_tag "assignment_user_id", 0
|
15
|
+
= label_tag "assignment_user_id_0", "Other"
|
16
|
+
= text_field_tag "assignment[email]", "", placeholder: "Email"
|
17
|
+
|
18
|
+
= f.submit "Invite"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
= error_messages_for :budget
|
2
|
+
|
3
|
+
= form_for [@client, @budget] do |form|
|
4
|
+
%p
|
5
|
+
= form.label :name
|
6
|
+
= form.text_field :name
|
7
|
+
|
8
|
+
%p
|
9
|
+
= form.label :total
|
10
|
+
==$#{form.number_field :total, required: true}
|
11
|
+
|
12
|
+
%p
|
13
|
+
= form.label :created_at
|
14
|
+
= form.date_select :created_at
|
15
|
+
|
16
|
+
%p= form.submit @budget.new_record? ? "Create" : "Update"
|
17
|
+
|
18
|
+
- if @budget.persisted?
|
19
|
+
%br
|
20
|
+
%br
|
21
|
+
%p= button_to "Delete budget", [@client, @budget], method: :delete
|
22
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
= content_for :css do
|
2
|
+
= stylesheet_link_tag "calendar"
|
3
|
+
|
4
|
+
h2 Calendar
|
5
|
+
|
6
|
+
table.calendar border=0 cellspacing=0 cellpadding=0
|
7
|
+
thead
|
8
|
+
tr
|
9
|
+
th colspan=2 align="right"
|
10
|
+
= link_to_previous_month(@date, id: "prev_month")
|
11
|
+
th.monthName colspan=3
|
12
|
+
= Date::MONTHNAMES[@date.month]
|
13
|
+
= @date.year
|
14
|
+
th colspan=2 align="left"
|
15
|
+
= link_to_next_month(@date, id: "next_month")
|
16
|
+
|
17
|
+
tr.dayName
|
18
|
+
- Date::DAYNAMES.each do |name|
|
19
|
+
th scope="col"
|
20
|
+
abbr title=name = name[0..2]
|
21
|
+
|
22
|
+
= calendar year: @date.year, month: @date.month do |date|
|
23
|
+
- unless current_user.works.on_date(date).empty?
|
24
|
+
p = pluralize number_with_precision(current_user.hours_by_date(date), precision: 0), "hour"
|
25
|
+
p = number_to_currency current_user.total_by_date(date)
|
26
|
+
|
27
|
+
- unless current_user.works.on_month(@date).empty?
|
28
|
+
h2 Total
|
29
|
+
ul#calendar_summary
|
30
|
+
li = pluralize number_with_precision(current_user.hours_by_month(@date), precision: 0), 'hour'
|
31
|
+
li = number_to_currency current_user.total_by_month(@date)
|
32
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
= error_messages_for :client
|
2
|
+
|
3
|
+
= form_for @client do |f|
|
4
|
+
%p
|
5
|
+
= f.label :name, "Company Name"
|
6
|
+
= f.text_field :name
|
7
|
+
%p
|
8
|
+
= f.label :contact, "Contact Name"
|
9
|
+
= f.text_field :contact
|
10
|
+
%p
|
11
|
+
= f.label :phone
|
12
|
+
= f.text_field :phone
|
13
|
+
%p
|
14
|
+
= f.label :email
|
15
|
+
= f.text_field :email
|
16
|
+
%p
|
17
|
+
= f.label :address
|
18
|
+
= f.text_field :address
|
19
|
+
%p
|
20
|
+
= f.label :city
|
21
|
+
= f.text_field :city
|
22
|
+
%p
|
23
|
+
= f.label :state
|
24
|
+
= f.text_field :state
|
25
|
+
%p
|
26
|
+
= f.label :zip
|
27
|
+
= f.text_field :zip
|
28
|
+
%p
|
29
|
+
= f.label :country
|
30
|
+
= f.text_field :country
|
31
|
+
|
32
|
+
%p
|
33
|
+
= f.submit @client.new_record? ? "Create" : "Update", :class => "button"
|
34
|
+
= link_to 'Back', clients_path, :class => "button"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
= content_for :title do
|
2
|
+
All Clients
|
3
|
+
|
4
|
+
%table#clients(width="100%")
|
5
|
+
%col(width="35%")
|
6
|
+
%col(width="35%")
|
7
|
+
%col(width="15%")
|
8
|
+
%col(width="15%")
|
9
|
+
%tr
|
10
|
+
%th name
|
11
|
+
%th balance
|
12
|
+
|
13
|
+
- @clients.each do |client|
|
14
|
+
%tr{class: cycle('even','')}
|
15
|
+
%td= link_to client.name, [client, :invoices]
|
16
|
+
%td(align="center")= number_to_currency client.balance
|
17
|
+
%td(align="center")= link_to "Edit", [:edit, client], class: "button"
|
18
|
+
%td(align="center")= link_to "Delete", client, data: { confirm: 'Are you sure?' }, method: :delete, class: "button"
|
19
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
h2 Line items to include
|
2
|
+
|
3
|
+
table data-controller="new-invoice"
|
4
|
+
tr
|
5
|
+
th
|
6
|
+
th Date
|
7
|
+
th Agent
|
8
|
+
th Start
|
9
|
+
th Finish
|
10
|
+
th Hours
|
11
|
+
th Rate
|
12
|
+
th Total
|
13
|
+
th Notes
|
14
|
+
- @invoice.line_items.sort.each do |line_item|
|
15
|
+
tr
|
16
|
+
td = check_box_tag 'invoice[line_item_ids][]', line_item.id, true, data: { total: line_item.total, action: "new-invoice#updateTotal" }
|
17
|
+
- if line_item.is_a?(Eboshi::Work)
|
18
|
+
td = line_item.start&.to_fs(:slash)
|
19
|
+
td = line_item.user&.name
|
20
|
+
td = line_item.start&.to_fs(:time)
|
21
|
+
td = line_item.finish&.to_fs(:time)
|
22
|
+
td = number_with_precision line_item.hours, precision: 2
|
23
|
+
td = number_to_per_hour line_item.rate
|
24
|
+
|
25
|
+
- if line_item.is_a?(Eboshi::Adjustment)
|
26
|
+
td
|
27
|
+
td = line_item.user&.name
|
28
|
+
td align="center" colspan=4 Flat Fee:
|
29
|
+
|
30
|
+
td align="right"
|
31
|
+
= number_to_currency line_item.total
|
32
|
+
td = line_item.notes
|
33
|
+
|
34
|
+
h2 Details
|
35
|
+
|
36
|
+
p
|
37
|
+
= form.label :date
|
38
|
+
|
|
39
|
+
= form.date_field :date
|
40
|
+
|
41
|
+
p
|
42
|
+
= form.label :project_name
|
43
|
+
|
|
44
|
+
= form.text_field :project_name
|
45
|
+
|
46
|
+
p
|
47
|
+
= form.label :total
|
48
|
+
i (changing this number will automatically create an adjustment)
|
49
|
+
| $
|
50
|
+
= form.text_field :total, data: { new_invoice_target: "total" }
|
51
|
+
|
52
|
+
p
|
53
|
+
= form.check_box :include_dates
|
54
|
+
|
|
55
|
+
= form.label :include_dates, "Include dates?"
|
56
|
+
|
57
|
+
p
|
58
|
+
= form.check_box :include_times
|
59
|
+
|
|
60
|
+
= form.label :include_times, "Include times?"
|
61
|
+
|
62
|
+
p
|
63
|
+
= form.label :budget_id, "Budget"
|
64
|
+
|
|
65
|
+
= form.collection_select :budget_id, @client.budgets, :id, :name_and_total, include_blank: true
|
66
|
+
|
67
|
+
= form.submit @invoice.new_record? ? "Create" : "Update"
|
68
|
+
|
@@ -0,0 +1,76 @@
|
|
1
|
+
= turbo_frame_tag invoice, target: "_top" do
|
2
|
+
%table{:class => "invoice #{invoice.status}", :width=>"100%", :border=>"0", :cellpadding=>"0", :cellspacing=>"0" }
|
3
|
+
%thead{ :id => "invoice_#{invoice.id_or_new}" }
|
4
|
+
- if invoice.status == 'unbilled'
|
5
|
+
%tr
|
6
|
+
%td{ :colspan => 8 }
|
7
|
+
%h2 Building Invoice
|
8
|
+
%tr
|
9
|
+
%td{ :colspan => 8 }
|
10
|
+
= link_to 'Clock In', clock_in_path(@client), class: "button left",
|
11
|
+
data: { turbo_frame: dom_id(invoice), turbo_method: "post", turbo: true }
|
12
|
+
= link_to 'New Time Item', new_client_work_path(@client), :class => "button left"
|
13
|
+
= link_to 'New Flat Fee', new_client_adjustment_path(@client), :class => "button left"
|
14
|
+
.clear
|
15
|
+
- else
|
16
|
+
%tr
|
17
|
+
%td{ :colspan => 8 }
|
18
|
+
%h2
|
19
|
+
Invoice ##{invoice.id}
|
20
|
+
= " - #{invoice.project_name}" unless invoice.project_name.blank?
|
21
|
+
%tr
|
22
|
+
%td{ :colspan => 5 }
|
23
|
+
= link_to 'Hide Details', [invoice, type: "mini"], class: 'button',
|
24
|
+
data: { turbo_frame: dom_id(invoice), turbo: true }
|
25
|
+
= link_to 'Export PDF', invoice_path(invoice, :format => :pdf), :class=> "button"
|
26
|
+
= link_to 'Edit', edit_invoice_path(invoice), :class=>"button"
|
27
|
+
= link_to 'Delete', invoice_path(invoice), :method => :delete, :confirm => 'Are you certain you want to delete this invoice? Its line items will not be deleted and will go back in your unassigned list.', :class=>"button"
|
28
|
+
%td.total= number_to_currency invoice.total
|
29
|
+
%td
|
30
|
+
%div
|
31
|
+
Invoiced on:
|
32
|
+
= invoice.date.to_fs(:slash) if invoice.date
|
33
|
+
- unless invoice.payments.empty?
|
34
|
+
%b PAYMENTS:
|
35
|
+
- invoice.payments.each do |p|
|
36
|
+
%li= number_to_currency(p.total)+' '+p.created_at.to_fs(:slash)
|
37
|
+
- unless invoice.paid?
|
38
|
+
= number_to_currency invoice.balance
|
39
|
+
remaining
|
40
|
+
- else
|
41
|
+
%b.red UNPAID
|
42
|
+
=# button_to 'Mark as Paid', paid_invoice_path(invoice)
|
43
|
+
|
44
|
+
%tr#new_line_items
|
45
|
+
%th
|
46
|
+
%th user
|
47
|
+
%th time
|
48
|
+
%th hours
|
49
|
+
%th rate
|
50
|
+
%th total
|
51
|
+
%th notes
|
52
|
+
%th
|
53
|
+
%tr#totals
|
54
|
+
%td{:colspan => "3"}
|
55
|
+
%td.total_hours= number_with_precision invoice.total_hours, :precision => 2
|
56
|
+
%td
|
57
|
+
%td.total= number_to_currency invoice.balance
|
58
|
+
%td.align_left
|
59
|
+
%b Current Balance
|
60
|
+
%td
|
61
|
+
|
62
|
+
%tbody
|
63
|
+
= render invoice.line_items.sort
|
64
|
+
%tfoot
|
65
|
+
- invoice.users.each_with_index do |user, i|
|
66
|
+
%tr.breakdown
|
67
|
+
%td
|
68
|
+
%td= user ? h(user.name) : "<em>Ambiguous</em>".html_safe
|
69
|
+
%td
|
70
|
+
%td.total_hours= number_with_precision invoice.total_hours_for_user(user), :precision => 2
|
71
|
+
%td
|
72
|
+
%td.total= number_to_currency invoice.total_for_user(user)
|
73
|
+
- if i == 0
|
74
|
+
%td.align_left{ :rowspan => invoice.users.count, :valign => "middle" }
|
75
|
+
%b Breakdown
|
76
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
= cache invoice do
|
2
|
+
= turbo_frame_tag invoice, target: "_top" do
|
3
|
+
%div{ :class => "#{invoice.status} invoice", :id => "invoice_#{invoice.id_or_new}" }
|
4
|
+
= link_to 'Details', invoice, class: "button left",
|
5
|
+
data: { turbo_frame: dom_id(invoice), turbo: true }
|
6
|
+
|
7
|
+
%h3
|
8
|
+
= "\##{invoice.id} - "
|
9
|
+
= "#{invoice.project_name} - " unless invoice.project_name.blank?
|
10
|
+
= invoice.date.to_fs(:slash)+": "
|
11
|
+
= invoice.paid? ? number_to_currency(invoice.total) : number_to_currency(invoice.balance)
|
12
|
+
= invoice.status
|
13
|
+
%span
|
14
|
+
- unless invoice.payments.empty?
|
15
|
+
- invoice.payments.each do |p|
|
16
|
+
%span= number_to_currency(p.total)+' paid on '+p.created_at.to_fs(:slash)
|
17
|
+
= link_to 'Export PDF', invoice_path(invoice, :format => :pdf), :class=>"button right"
|
18
|
+
- unless invoice.paid?
|
19
|
+
= link_to 'Log payment', new_payment_path(invoice), :class => 'mini_invoice_make_payment button right'
|
20
|
+
.clear
|
21
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
- content_for :title do
|
2
|
+
Editing invoice
|
3
|
+
|
4
|
+
= error_messages_for :invoice
|
5
|
+
|
6
|
+
= form_for @invoice, :url => invoice_path(@invoice) do |f|
|
7
|
+
= render :partial => 'form', :object => f
|
8
|
+
|
9
|
+
= link_to 'Show', invoice_path(@invoice)
|
10
|
+
|
|
11
|
+
= link_to 'Back', invoices_path(@client)
|
12
|
+
|
|
13
|
+
= button_to 'Delete', invoice_path(@invoice), :method => :delete, :confirm => 'Are you certain you want to delete this invoice and its hours and notes?'
|
14
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
= content_for :title do
|
2
|
+
= @client.name
|
3
|
+
» Billing Details
|
4
|
+
|
5
|
+
= content_for :css do
|
6
|
+
= stylesheet_link_tag "invoices"
|
7
|
+
|
8
|
+
= content_for :actions do
|
9
|
+
%div(data-controller="invoice")
|
10
|
+
= link_to 'Create Invoice', new_invoice_path(@client), class: "button left", data: { action: "invoice#create" }
|
11
|
+
= link_to 'Merge', merge_works_path(@client), class: "button left", data: { action: "invoice#merge" }
|
12
|
+
= link_to 'New Budget', [:new, @client, :budget], class: "button left"
|
13
|
+
.clear
|
14
|
+
|
15
|
+
- @invoices.group_by(&:budget).each do |budget, invoices|
|
16
|
+
- if budget
|
17
|
+
%dl.budget
|
18
|
+
%dfn
|
19
|
+
%dd Name:
|
20
|
+
%dt= budget.name
|
21
|
+
%dfn
|
22
|
+
%dd Total:
|
23
|
+
%dt= number_to_currency budget.total
|
24
|
+
%dfn
|
25
|
+
%dd Used:
|
26
|
+
%dt= number_to_currency budget.used
|
27
|
+
- if budget.under?
|
28
|
+
%dfn
|
29
|
+
%dd Remaining:
|
30
|
+
%dt= number_to_currency budget.remaining
|
31
|
+
- if budget.over?
|
32
|
+
%dfn
|
33
|
+
%dd Over:
|
34
|
+
%dt= number_to_currency budget.remaining
|
35
|
+
%dfn
|
36
|
+
%dd= link_to "Edit Budget", [:edit, @client, budget], class: "edit-budget"
|
37
|
+
|
38
|
+
= render invoices
|
39
|
+
|
40
|
+
- else
|
41
|
+
= render invoices
|
42
|
+
|
43
|
+
%p
|
44
|
+
Total:
|
45
|
+
= @client.line_items.to_a.sum(&:total)
|
46
|
+
|
@@ -0,0 +1,118 @@
|
|
1
|
+
!!!
|
2
|
+
%html
|
3
|
+
%head
|
4
|
+
%meta{'http-equiv' => 'Content-Type', :content => 'text/html; charset=utf-8'}
|
5
|
+
%title= "#{current_user.business_name_or_name} invoice \##{@invoice.id}"
|
6
|
+
= wicked_pdf_stylesheet_link_tag "invoice"
|
7
|
+
|
8
|
+
%body
|
9
|
+
#container
|
10
|
+
#content
|
11
|
+
|
12
|
+
#return
|
13
|
+
#logo
|
14
|
+
- if current_user.logo.exists?
|
15
|
+
= image_tag current_user.logo.url(:pdf)
|
16
|
+
- else
|
17
|
+
= current_user.business_name_or_name
|
18
|
+
- unless current_user.address.blank?
|
19
|
+
%p= h current_user.address
|
20
|
+
- unless current_user.address2.blank?
|
21
|
+
%p= h current_user.address2
|
22
|
+
- if current_user.city_state_zip
|
23
|
+
%p= h current_user.city_state_zip
|
24
|
+
%p.color= mail_to current_user.business_email_or_email
|
25
|
+
.clear
|
26
|
+
|
27
|
+
#client
|
28
|
+
%h1 Invoice
|
29
|
+
%p attention:
|
30
|
+
%h2= h @invoice.client.contact
|
31
|
+
%p= h @invoice.client.address
|
32
|
+
.clear
|
33
|
+
|
34
|
+
#date
|
35
|
+
%h3="#{@invoice.date.day} #{Date::MONTHNAMES[@invoice.date.month]} #{@invoice.date.year}"
|
36
|
+
.clear
|
37
|
+
|
38
|
+
#project
|
39
|
+
#project_header
|
40
|
+
%table{:cellspacing => '0', :cellpadding => '0', :width => '100%'}
|
41
|
+
%col{:width => '49%'}
|
42
|
+
%col{:width => '2%'}
|
43
|
+
%col{:width => '49%'}
|
44
|
+
- unless @invoice.project_name.blank?
|
45
|
+
%tr
|
46
|
+
%td PROJECT
|
47
|
+
%td .
|
48
|
+
%td.right_align= h @invoice.project_name
|
49
|
+
%tr
|
50
|
+
%td INVOICE NUMBER
|
51
|
+
%td .
|
52
|
+
%td.right_align= @invoice.id
|
53
|
+
%tr
|
54
|
+
%td TERMS
|
55
|
+
%td .
|
56
|
+
%td.right_align Due on Receipt
|
57
|
+
|
58
|
+
#items
|
59
|
+
%table{:cellspacing => '0', :cellpadding => '0', :width => '100%'}
|
60
|
+
- if @invoice.include_dates
|
61
|
+
%col{:width => '25%'}
|
62
|
+
%col{:width => '*%'}
|
63
|
+
- unless @invoice.works.empty?
|
64
|
+
%col{:width => '15%'}
|
65
|
+
%col{:width => '15%'}
|
66
|
+
|
67
|
+
%thead
|
68
|
+
%tr
|
69
|
+
- if @invoice.include_dates
|
70
|
+
%th date
|
71
|
+
%th.left_align item
|
72
|
+
- unless @invoice.works.empty?
|
73
|
+
%th hours
|
74
|
+
- unless @invoice.consistant_rate
|
75
|
+
%th rate
|
76
|
+
%th cost
|
77
|
+
|
78
|
+
%tbody
|
79
|
+
- @invoice.line_items.sort.each do |line_item|
|
80
|
+
%tr{:class => cycle('line_item even','line_item')}
|
81
|
+
- if @invoice.include_dates
|
82
|
+
%td= line_item.start.to_fs(:slash) if line_item.start
|
83
|
+
- if line_item.is_a?(Eboshi::Work)
|
84
|
+
%td.li= line_item.notes
|
85
|
+
%td.center_align
|
86
|
+
- if @invoice.include_times and line_item.is_a?(Eboshi::Work)
|
87
|
+
= raw line_item.start.to_fs(:pretty_time)
|
88
|
+
= raw line_item.finish.to_fs(:pretty_time)
|
89
|
+
%br
|
90
|
+
= number_with_precision line_item.hours, :precision => 2
|
91
|
+
- unless @invoice.consistant_rate
|
92
|
+
%td.center_align= number_to_currency line_item.rate
|
93
|
+
%td.center_align= number_to_currency line_item.total
|
94
|
+
- if line_item.is_a?(Eboshi::Adjustment)
|
95
|
+
%td{:colspan => (@invoice.consistant_rate ? 1 : 2) + (@invoice.works.empty? ? 0 : 1)}
|
96
|
+
= line_item.notes
|
97
|
+
%td.center_align= number_to_currency line_item.total
|
98
|
+
|
99
|
+
%tr
|
100
|
+
%td{:colspan => @invoice.include_dates ? 2 : 1 } TOTAL
|
101
|
+
- unless @invoice.works.empty?
|
102
|
+
%td
|
103
|
+
- unless @invoice.consistant_rate
|
104
|
+
%td
|
105
|
+
%td.center_align= number_to_currency @invoice.total
|
106
|
+
|
107
|
+
- if @invoice.consistant_rate.respond_to?(:nonzero?)
|
108
|
+
#project_footer
|
109
|
+
%em== Work rated at #{number_to_per_hour @invoice.consistant_rate}.
|
110
|
+
|
111
|
+
.clear
|
112
|
+
|
113
|
+
#sig
|
114
|
+
%p sincerely,
|
115
|
+
- if current_user.signature.exists?
|
116
|
+
= image_tag current_user.signature.url(:pdf)
|
117
|
+
- else
|
118
|
+
= current_user.business_name_or_name
|