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,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
|