eboshi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +28 -0
  3. data/Rakefile +8 -0
  4. data/app/assets/config/eboshi_manifest.js +1 -0
  5. data/app/assets/stylesheets/eboshi/application.css +15 -0
  6. data/app/controllers/eboshi/adjustments_controller.rb +77 -0
  7. data/app/controllers/eboshi/application_controller.rb +62 -0
  8. data/app/controllers/eboshi/assignments_controller.rb +31 -0
  9. data/app/controllers/eboshi/budgets_controller.rb +43 -0
  10. data/app/controllers/eboshi/calendar_controller.rb +12 -0
  11. data/app/controllers/eboshi/clients_controller.rb +41 -0
  12. data/app/controllers/eboshi/invoices_controller.rb +69 -0
  13. data/app/controllers/eboshi/line_items_controller.rb +28 -0
  14. data/app/controllers/eboshi/payments_controller.rb +31 -0
  15. data/app/controllers/eboshi/user_sessions_controller.rb +25 -0
  16. data/app/controllers/eboshi/users_controller.rb +41 -0
  17. data/app/controllers/eboshi/works_controller.rb +83 -0
  18. data/app/helpers/eboshi/application_helper.rb +15 -0
  19. data/app/helpers/eboshi/calendar_helper.rb +80 -0
  20. data/app/helpers/eboshi/shallow_route_helper.rb +68 -0
  21. data/app/jobs/eboshi/application_job.rb +4 -0
  22. data/app/mailers/eboshi/application_mailer.rb +6 -0
  23. data/app/models/eboshi/adjustment.rb +37 -0
  24. data/app/models/eboshi/application_record.rb +5 -0
  25. data/app/models/eboshi/assignment.rb +20 -0
  26. data/app/models/eboshi/budget.rb +33 -0
  27. data/app/models/eboshi/client.rb +85 -0
  28. data/app/models/eboshi/invoice.rb +87 -0
  29. data/app/models/eboshi/line_item.rb +94 -0
  30. data/app/models/eboshi/payment.rb +20 -0
  31. data/app/models/eboshi/user.rb +128 -0
  32. data/app/models/eboshi/user_session.rb +6 -0
  33. data/app/models/eboshi/work.rb +66 -0
  34. data/app/views/eboshi/adjustments/_adjustment.html.slim +15 -0
  35. data/app/views/eboshi/adjustments/_form.html.haml +27 -0
  36. data/app/views/eboshi/adjustments/edit.html.haml +7 -0
  37. data/app/views/eboshi/adjustments/new.html.haml +8 -0
  38. data/app/views/eboshi/assignments/new.html.haml +18 -0
  39. data/app/views/eboshi/budgets/form.html.haml +22 -0
  40. data/app/views/eboshi/calendar/index.html.slim +32 -0
  41. data/app/views/eboshi/clients/_form.html.haml +34 -0
  42. data/app/views/eboshi/clients/edit.html.haml +4 -0
  43. data/app/views/eboshi/clients/index.html.haml +19 -0
  44. data/app/views/eboshi/clients/new.html.haml +4 -0
  45. data/app/views/eboshi/invoices/_form.html.slim +68 -0
  46. data/app/views/eboshi/invoices/_full.html.haml +76 -0
  47. data/app/views/eboshi/invoices/_invoice.html.haml +2 -0
  48. data/app/views/eboshi/invoices/_mini.html.haml +21 -0
  49. data/app/views/eboshi/invoices/edit.html.haml +14 -0
  50. data/app/views/eboshi/invoices/index.html.haml +46 -0
  51. data/app/views/eboshi/invoices/new.html.haml +11 -0
  52. data/app/views/eboshi/invoices/show.pdf.haml +118 -0
  53. data/app/views/eboshi/layouts/application.html.haml +120 -0
  54. data/app/views/eboshi/payments/_form.html.haml +10 -0
  55. data/app/views/eboshi/payments/new.html.haml +9 -0
  56. data/app/views/eboshi/user_sessions/new.html.haml +19 -0
  57. data/app/views/eboshi/users/_form.html.haml +65 -0
  58. data/app/views/eboshi/users/edit.html.haml +16 -0
  59. data/app/views/eboshi/users/index.html.haml +11 -0
  60. data/app/views/eboshi/users/new.html.haml +13 -0
  61. data/app/views/eboshi/users/show.html.haml +39 -0
  62. data/app/views/eboshi/works/_form.html.slim +25 -0
  63. data/app/views/eboshi/works/_work.html.slim +27 -0
  64. data/app/views/eboshi/works/edit.html.haml +9 -0
  65. data/app/views/eboshi/works/new.html.haml +8 -0
  66. data/app/views/layouts/eboshi/application.html.haml +120 -0
  67. data/config/routes.rb +35 -0
  68. data/lib/eboshi/engine.rb +15 -0
  69. data/lib/eboshi/version.rb +3 -0
  70. data/lib/eboshi.rb +6 -0
  71. data/lib/tasks/eboshi_tasks.rake +4 -0
  72. metadata +253 -0
@@ -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,7 @@
1
+ - content_for :title do
2
+ =h @client.name
3
+ &raquo; Editing Flat Fee
4
+
5
+ = render "form"
6
+
7
+ = link_to 'Back', invoices_path(@client)
@@ -0,0 +1,8 @@
1
+ - content_for :title do
2
+ =h @client.name
3
+ &raquo; New Flat Fee
4
+
5
+ = render "form"
6
+
7
+ = link_to 'Back', invoices_path(@client)
8
+
@@ -0,0 +1,18 @@
1
+ - content_for :title do
2
+ = @client.name
3
+ &raquo; 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,4 @@
1
+ - content_for :title do
2
+ Editing client
3
+
4
+ = render :partial => 'form', :object => @client
@@ -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,4 @@
1
+ - content_for :title do
2
+ Add Client
3
+
4
+ = render :partial => 'form', :object => @client
@@ -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
+ | &nbsp;
39
+ = form.date_field :date
40
+
41
+ p
42
+ = form.label :project_name
43
+ | &nbsp;
44
+ = form.text_field :project_name
45
+
46
+ p
47
+ = form.label :total
48
+ i (changing this number will automatically create an adjustment)
49
+ | &nbsp;$
50
+ = form.text_field :total, data: { new_invoice_target: "total" }
51
+
52
+ p
53
+ = form.check_box :include_dates
54
+ | &nbsp;
55
+ = form.label :include_dates, "Include dates?"
56
+
57
+ p
58
+ = form.check_box :include_times
59
+ | &nbsp;
60
+ = form.label :include_times, "Include times?"
61
+
62
+ p
63
+ = form.label :budget_id, "Budget"
64
+ | &nbsp;
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 &nbsp;
46
+ %th user
47
+ %th time
48
+ %th hours
49
+ %th rate
50
+ %th total
51
+ %th notes
52
+ %th &nbsp;
53
+ %tr#totals
54
+ %td{:colspan => "3"}
55
+ %td.total_hours= number_with_precision invoice.total_hours, :precision => 2
56
+ %td &nbsp;
57
+ %td.total= number_to_currency invoice.balance
58
+ %td.align_left
59
+ %b Current Balance
60
+ %td &nbsp;
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 &nbsp;
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,2 @@
1
+ = render :partial => (invoice.status == 'unbilled' ? 'full' : 'mini'), :locals => { :invoice => invoice }
2
+
@@ -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
+ &raquo; 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,11 @@
1
+ - content_for :title do
2
+ New invoice
3
+
4
+ = error_messages_for :invoice
5
+
6
+ = form_for [@client, @invoice] do |f|
7
+ = render :partial => 'form', :object => f
8
+
9
+ = link_to 'Cancel', invoices_path(@client), :class=>"button left"
10
+
11
+
@@ -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