eboshi 0.1.0

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