brisk-bills 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. data/CHANGELOG +6 -1
  2. data/TODO.txt +60 -38
  3. data/app/controllers/admin/employees_controller.rb +1 -1
  4. data/app/controllers/admin/invoices_controller.rb +38 -2
  5. data/app/controllers/admin/payments_controller.rb +13 -6
  6. data/app/helpers/admin/activity_tax_field_helper.rb +1 -1
  7. data/app/helpers/admin/activity_type_field_helper.rb +2 -2
  8. data/app/helpers/admin/payments_helper.rb +8 -2
  9. data/app/helpers/application_helper.rb +9 -2
  10. data/app/model_views/invoices_with_total.rb +5 -0
  11. data/app/models/activity.rb +7 -3
  12. data/app/models/activity/labor.rb +1 -1
  13. data/app/models/activity/labor/slimtimer.rb +2 -0
  14. data/app/models/client.rb +93 -3
  15. data/app/models/client_representative.rb +9 -1
  16. data/app/models/employee.rb +21 -1
  17. data/app/models/employee/slimtimer.rb +11 -0
  18. data/app/models/invoice.rb +93 -129
  19. data/app/models/invoice_payment.rb +54 -0
  20. data/app/models/payment.rb +25 -48
  21. data/config/boot.rb +1 -1
  22. data/db/migrate/029_invoices_with_totals_view_adjustment.rb +29 -0
  23. data/db/schema.rb +1 -1
  24. data/lib/brisk-bills.rb +1 -1
  25. data/lib/tasks/create_last_months_invoices.rake +8 -3
  26. data/public/javascripts/prototype.js +1573 -1019
  27. data/public/javascripts/prototype.js-1.6.0.3 +4320 -0
  28. data/test/test_unit_factory_helper.rb +16 -9
  29. data/test/unit/client_test.rb +298 -2
  30. data/test/unit/invoice_payment_test.rb +313 -3
  31. data/test/unit/invoice_test.rb +49 -36
  32. data/test/unit/payment_test.rb +35 -31
  33. data/vendor/plugins/active_scaffold_full_refresh/lib/active_scaffold_full_refresh.rb +4 -2
  34. metadata +69 -33
data/CHANGELOG CHANGED
@@ -1,6 +1,11 @@
1
1
  == Change Log
2
2
 
3
- === Release 1.0.0 (Feb 09, 2010)
3
+ === Release 0.7.0 (May 03, 2010)
4
+ - Much improved invoice to payment mapping control.
5
+ - Ability to unpublish more than one invoice, and to edit/delete older, already-published invoices
6
+ - Full control of payment to invoice mappings in the invoice/payment models, via InvoicePayment's
7
+
8
+ === Release 0.6.0 (May 03, 2010)
4
9
  - First release to public
5
10
  - gem distributable
6
11
 
data/TODO.txt CHANGED
@@ -1,61 +1,85 @@
1
- *I want to delete that old ashbritt invoice ... I should be able to !
2
- * THen figure out if perhaps we can disable the auto-assign on create
3
- * Remove create_invoice_payments from create/destory
1
+ * start using 0.7 pre on mario
2
+
3
+ * Assign version numbers to the invoice based on the number of times an invoice is published...
4
+ * I don't know if we should/need-to adjust the invoice pdfs out here just yet ....
5
+ * Might as well put this in migration 29 while we're publishing anyways
6
+
7
+ * (Install the rails-debugger on your laptop)
8
+
9
+ Now for the final Controller Adjustments:
10
+ * Invoice_id should be settable on the labor/materials/proposals/etc controllers. (though not the activities controller..not yet at least)
11
+ * Invoice create/update Activity assignments will likely need to be adjusted...
12
+ * Be sure to adjust any invoice_create tasks as well...
4
13
 
5
- * We need to go through and see where client balances are being handled, and make sure its using invoiced_amount - payment_amount
14
+ * Keep in mind - we may need/want to update InvoicePayments and not just delete/create them (say an allocaed amount changes from $4.00 -> $3.00 on the same invoice/payment
6
15
  * let's start by adding a detail that shows what payments are currently allocated..
7
- * Don't forget to Add the right show fields too (in the payments controller)
16
+ * We don't need to get sart about allocating, unless there's 0 allocated payments whcih are currently assigned...
17
+ * Show a warning if someone tries to commit a payment w/o allocating the full amount...
18
+ * Don't forget to Add the right show-action fields too (in the payments controller)
19
+ * There's should be a way in the invoices to show which payments were applied to the invoice... (perhaps on the edit detail - just a listing?)
20
+ * Maybe this should be editable.... perhaps only if is_published?
8
21
  * THen we can add an edit...
22
+ * Remove that restriction on unpublishing old invoices...
9
23
  * And then we need to add a complemntary field in the invoices view? Nah - but show which payments are currently assigned as a read-only...
10
- * Then we can allow multiple invoices to be unpublished...
11
24
 
12
- * I guess I unpublish and delete it?
13
- * We should put proper transactions in place here too while we're in here...
14
- * No - let's get the damn payments applying with the interface quickbooks uses
15
- * We should probably allow payment updates, just do a destroy_invoice_payments and create_invoice_payments when that happens. if its the newest payment for a client.
16
- * Make sure the payments_test adds a test for this...
17
-
18
- * Add Existing Client Representatives should show them in alaphabetical order
25
+ * Things might be a little awkward with out activity assignment if people are going back and editing old stuff... we should really have an activity selector...
19
26
 
20
27
  * We should add an unpublished Open Invoices controller ...
21
28
 
29
+ * I'd like to see a payment identifier listed in the payments list ... (check no/cc#)
30
+
31
+ * Payment:::is_allocated should probably be added to a view and then list controller...
32
+ * Code is there - but no view supports it...
33
+
22
34
  * We should put tax before cost in the activities with prices controller...
23
35
 
24
- * I want to delete that old ashbritt invoice ... I should be able to !
25
- * Here's what I'm thinking:
26
- * We remove create_invoice_payments from create/destory
27
- * We add an unallocated_amount to the payments controller / view
28
- * We need to go through and see where client balances are being handled, and make sure its using invoiced_amount - payment_amount
29
- * let's start by adding a detail that shows what payments are currently allocated..
30
- * THen we can add an edit...
31
- * And then we need to add a complemntary field in the invoices view? Nah - but show which payments are currently assigned as a read-only...
32
- * Then we can allow multiple invoices to be unpublished...
36
+ * WHen Assigning employees to labr_rates - the form stays open between updates and shouldnt...
37
+
38
+ * Move_to_invoice function should allow us to Move_to '(Unassigned)', and ideally that would be a radio group
33
39
 
34
- * I guess I unpublish and delete it?
35
- * We should put proper transactions in place here too while we're in here...
36
- * No - let's get the damn payments applying with the interface quickbooks uses
37
- * We should probably allow payment updates, just do a destroy_invoice_payments and create_invoice_payments when that happens. if its the newest payment for a client.
38
- * Make sure the payments_test adds a test for this...
40
+ * Start using 0.8-pre on mario...
39
41
 
40
- * Add better transactions throughout!
42
+ * Publish !!!!!
43
+ ---------------
44
+ * all delete confirms should go through the modal box
45
+
46
+ * Probably - we should prompt on invoice update, when appropriate, whether to email the client.
47
+ * Probably too, publish should be a row-action and not a form control
48
+ * Probably - when prompted to delete an invoice, we should give the option to also remove the activities (or preserve them and leave them unsassigned)
41
49
 
42
50
  * Translate the time into the local zone for the user, probably each user/client should have a zone associtation in their account.
43
51
  * Its already stored in UTC everywhere...
44
52
  * We need to factor this into invoices too, I think
45
53
 
46
54
  * Public interface
47
- * Showing the Client eventlogs as an unedutable nested list might be nice... Hell, employee eventlogs too....
48
- * CC payments handling - not a plugin
49
55
 
56
+ * Showing the Client eventlogs as an unedutable nested list might be nice... Hell, employee eventlogs too....
57
+
58
+ * Publish !!!!!
59
+ ---------------
60
+ * CC payments handling built-in- not a plugin
61
+
62
+ * Slimtimer-less time entry
63
+
64
+ * Revise the site-settings interfaces...
65
+
66
+ * Plugin interface...
67
+
68
+ * Publish 0.9 !!!!!
50
69
  ---------------
51
- Plugin interface using engines?
52
70
  ---------------
53
- LONGER-TERM: (Needs re-prioritization...)
71
+ LONGER-TERM Features: (Needs re-prioritization...)
54
72
 
55
- Feature:
73
+ * We should audit for proper transaction support, and test!
56
74
 
57
- * I'd like some tests cases written for the view objects, make sure they work just like the non-views... ...
58
- * Boy this is getting hard, I think it has something to do wioth the transactions... Oddly, the tests work on their own, outside of the rake test
75
+ * All of our read_attribute stuff, isn't "Caching"the newest values. (I think - write a test for this ...)
76
+ * Use a find_invoice_with_totals
77
+ * then update an invoice
78
+ * then run an invoice.amunt(true)
79
+ * tune run an invice.amount .... This this treturn the prior amount value?
80
+ * THis has to be fixed/ested for all read attributes...
81
+
82
+ * Drop the VIEW hacks and de-normalize the payments/invoices tables. Just 'cache' the irksome values in here
59
83
 
60
84
  * Better searches by using the search sql fields...
61
85
 
@@ -69,9 +93,7 @@ BUG: We should probably write a plugin that better displays delete errors in the
69
93
  * Create two invoices - both unpublished, try to delete the oldest one
70
94
 
71
95
  * Use polymorphic relationship for sub activities
72
- * Maybe ..
73
-
74
- * all delete confirms should go through the modal box - make this a plugin
96
+ * I really don't like the way we're uing dont_validate_type_associations. ..
75
97
 
76
98
  * The ability to create an activity from the activities & invoices/activities list would be sweet
77
99
  * With a little pop-up that asks for the activity type?
@@ -27,6 +27,6 @@ class Admin::EmployeesController < ApplicationController
27
27
  def conditions_for_collection
28
28
  ['is_active = ?', true]
29
29
  end
30
-
30
+
31
31
  handle_extensions
32
32
  end
@@ -65,10 +65,46 @@ class Admin::InvoicesController < ApplicationController
65
65
  (klass == Activity) ? Admin::ActivitiesWithPricesController : super
66
66
  end
67
67
 
68
+ # We ovverride the defaut behavior here only so that we can use this method as a hook to detyermine the invoice.activity_type_ids
69
+ # Before they've been updated by the form. We reference @activity_type_ids_before in before_update_save to determine whether we
70
+ # need to update the activity associations for this invoice.
71
+ def update_record_from_params(*args)
72
+ @activity_type_ids_before = @record.activity_type_ids.dup if @record
73
+ super
74
+ end
75
+
76
+ def before_update_save(invoice)
77
+ super
78
+
79
+ invoice.activities = invoice.recommended_activities if (
80
+ invoice.new_record? or (
81
+ !invoice.is_published and
82
+ # Unfortunately, there's no easy way to accomplish a invoice.activity_types.changed?, so - this will
83
+ # effectively ascertain that answer by comparing the params against the activity_type_ids
84
+ (invoice.issued_on_changed? || (@activity_type_ids_before != invoice.activity_type_ids))
85
+ )
86
+ )
87
+
88
+ invoice.payment_assignments.clear if !invoice.is_published and invoice.is_published_changed?
89
+
90
+ # We're going to need this in the after_update_save... Note it now.
91
+ @invoice_changes = invoice.changes.keys.collect(&:to_sym)
92
+ @invoice_new_record = invoice.new_record?
93
+ end
94
+
95
+ alias before_create_save before_update_save
96
+
68
97
  def after_update_save(invoice)
69
98
  super
70
-
71
- if successful? and invoice.is_published # TODO: And only if is_published has changed?...
99
+
100
+ if successful? and invoice.is_published and (@invoice_new_record || @invoice_changes.include?(:is_published))
101
+ # Unfortunately we have to assign payments using the quick_create and not by concat'ing on the
102
+ # invoice AssociationProxy. THis is due to a bug in my InvoicesWithTotal wrapper that I think is related
103
+ # to the association proxy not resettting its owenr id on an id change (as is the case of a create)
104
+ invoice.client.recommend_payment_assignments_for(invoice.amount).each do |ip|
105
+ InvoicePayment.quick_create! invoice.id, ip.payment_id, ip.amount
106
+ end
107
+
72
108
  define_invoice invoice
73
109
 
74
110
  attachments = [
@@ -6,7 +6,7 @@ class Admin::PaymentsController < ApplicationController
6
6
  active_scaffold :payment do |config|
7
7
  config.label = "Payments"
8
8
 
9
- config.columns = [:paid_on, :client, :payment_method, :payment_method_identifier, :amount, :invoice_assignment, :unallocated_amount, :created_at, :updated_at]
9
+ config.columns = [:paid_on, :client, :payment_method, :payment_method_identifier, :amount, :invoice_assignments, :amount_unallocated, :created_at, :updated_at]
10
10
 
11
11
  config.columns[:client].form_ui = :select
12
12
  config.columns[:client].sort_by :sql => 'clients.company_name'
@@ -17,8 +17,8 @@ class Admin::PaymentsController < ApplicationController
17
17
  config.columns[:payment_method_identifier].description = 'Last four card digits, check number...'
18
18
  config.columns[:payment_method_identifier].label = 'Method Identifier'
19
19
 
20
- config.columns[:unallocated_amount].label = 'Unallocated'
21
- config.columns[:invoice_assignment].label = 'Posted To'
20
+ config.columns[:amount_unallocated].label = 'Unallocated'
21
+ config.columns[:invoice_assignments].label = 'Posted To'
22
22
 
23
23
  config.columns[:amount].sort_by :sql => 'amount_in_cents'
24
24
 
@@ -30,9 +30,9 @@ class Admin::PaymentsController < ApplicationController
30
30
  :client,
31
31
  :payment_method,
32
32
  :payment_method_identifier,
33
- :amount,
34
- # :unallocated_amount,
35
- # :invoice_assignment
33
+ :amount
34
+ # :amount_unallocated,
35
+ # :invoice_assignments
36
36
  ]
37
37
 
38
38
  config.update.link = nil
@@ -40,6 +40,13 @@ class Admin::PaymentsController < ApplicationController
40
40
  # observe_active_scaffold_form_fields :fields => %w(client amount), :action => :on_invoice_assignment_observation
41
41
  end
42
42
 
43
+ def before_update_save(payment)
44
+ payment.invoice_assignments = payment.client.recommend_invoice_assignments_for payment.amount
45
+ end
46
+
47
+ alias before_create_save before_update_save
48
+
49
+
43
50
  def on_invoice_assignment_observation
44
51
  record_id = (/^[\d]+$/.match(params[:record_id])) ? params[:record_id].to_i : nil
45
52
 
@@ -1,7 +1,7 @@
1
1
  module Admin::ActivityTaxFieldHelper
2
2
 
3
3
  def tax_column(record)
4
- h_money (record.tax) ? record.tax : 0.0
4
+ h_money (record.tax) ? record.tax : Money.new(0)
5
5
  end
6
6
 
7
7
  def apply_tax_form_column(record, input_name)
@@ -22,11 +22,11 @@ module Admin::ActivityTypeFieldHelper
22
22
  end
23
23
 
24
24
  def cost_column(record)
25
- h_money (record.cost) ? record.cost : 0.0
25
+ h_money (record.cost) ? record.cost : Money.new(0)
26
26
  end
27
27
 
28
28
  def tax_column(record)
29
- h_money (record.tax) ? record.tax : 0.0
29
+ h_money (record.tax) ? record.tax : Money.new(0)
30
30
  end
31
31
 
32
32
  def cost_form_column(record,input_name)
@@ -13,8 +13,14 @@ module Admin::PaymentsHelper
13
13
  )
14
14
  end
15
15
 
16
- def unallocated_amount_form_column(record, input_name)
17
- '<span class="active-scaffold_detail_value" id="record_unallocated_amount_%s">%s</span>' % [record.id, '(Enter a Payment Amount)']
16
+ def amount_unallocated_form_column(record, input_name)
17
+ '<span class="active-scaffold_detail_value" id="record_amount_unallocated_%s">%s</span>' % [record.id, '(Enter a Payment Amount)']
18
+ end
19
+
20
+ def invoice_assignments_column(record)
21
+ record.invoice_assignments.collect{|ia|
22
+ '%s to Invoice %d' % [ia.amount.format, ia.invoice.id ]
23
+ }.join ', '
18
24
  end
19
25
 
20
26
  def invoice_assignment_form_column(record, input_name)
@@ -2,9 +2,9 @@
2
2
  module ApplicationHelper
3
3
 
4
4
  def h_money(amount, inverse_polarity = false)
5
- '<span class="%s">$%s</span>' % [
5
+ '<span class="%s">%s</span>' % [
6
6
  ( (inverse_polarity ? (amount > 0 ) : (amount < 0 )) ? 'money_negative':'money_positive' ),
7
- amount.to_s.gsub(/(\d)(?=\d{3}+(\.\d*)?$)/, '\1,')
7
+ amount.format
8
8
  ] unless amount.nil?
9
9
  end
10
10
 
@@ -24,4 +24,11 @@ module ApplicationHelper
24
24
  @javascripts = ['scriptaculous.js?load=effects', 'modalbox.js','briskbills-quick-helpers.js']
25
25
  @stylesheets = ['modalbox.css']
26
26
  end
27
+
28
+ # This fixes a javascript bug in active_scaffold 1.2RC1. If the controller id starts with a number, prototype
29
+ # pukes during delete and create when called in a sublist on recent firefox/safari's
30
+ def controller_id
31
+ @controller_id ||= 'as_' + super
32
+ end
33
+
27
34
  end
@@ -22,12 +22,17 @@ class InvoicesWithTotal < Invoice
22
22
  def invoice_assign_and_save!(inv)
23
23
  invoice_columns = Invoice.columns.collect{|c| c.name}-['id']
24
24
 
25
+ inv.activity_ids = activity_ids
26
+ inv.payment_assignments = payment_assignments
25
27
  inv.activity_type_ids = activity_type_ids
26
28
 
27
29
  attributes.reject{|k,v| true unless invoice_columns.include? k }.each{ |k,v| inv.send "#{k}=", v }
28
30
 
29
31
  inv.save!
30
32
 
33
+ # For the case of a create! this assign the id to the current view
34
+ self.id ||= inv.id
35
+
31
36
  inv.errors.each { |attr,msg| errors.add attr, msg }
32
37
 
33
38
  inv
@@ -30,7 +30,7 @@ class Activity < ActiveRecord::Base
30
30
  activity_type_sym = (activity_type.nil? or activity_type.empty?) ? nil : activity_type.to_sym
31
31
 
32
32
  unless dont_validate_type_associations or !self.class.reflections.has_key?(activity_type_sym)
33
- type_association = instance_variable_get("@#{activity_type}")
33
+ type_association = self.send activity_type_sym
34
34
 
35
35
  if type_association.nil?
36
36
  errors.add activity_type_sym, 'missing'
@@ -68,9 +68,13 @@ class Activity < ActiveRecord::Base
68
68
  end
69
69
 
70
70
  def validate_on_update
71
- errors.add_to_base "Activity can't be adjusted once its invoice is published" if is_published? and changed_attributes.length > 0
71
+ errors.add_to_base "Activity can't be adjusted once its invoice is published" if (
72
+ # If we're published, and someone's trying to change us ....
73
+ is_published? and changed_attributes.length > 0 and
74
+ # *But* this change isn't the case of an invoice association from nil to (not nil) [this case is cool]:
75
+ !(changed_attributes.length == 1 and changed_attributes.keys.include? "invoice_id" and invoice_id_change[0].nil?)
76
+ )
72
77
  end
73
- # /No updates/destroys
74
78
 
75
79
  def sub_activity
76
80
  send activity_type unless activity_type.nil?
@@ -79,7 +79,7 @@ class Activity::Labor < ActiveRecord::Base
79
79
  '%.2f' % activity.cost.to_f,
80
80
  item_name,
81
81
  occurred_on.strftime('%m/%d/%y'),
82
- comments.tr("\r\n", '')
82
+ comments.try(:tr, "\r\n", '')
83
83
  ]
84
84
  end
85
85
 
@@ -1,3 +1,5 @@
1
+ require "#{BRISKBILLS_ROOT}/lib/utilities.rb"
2
+
1
3
  class Activity::Labor < ActiveRecord::Base
2
4
  has_one :slimtimer_time_entry, :foreign_key => :activity_labor_id, :class_name => "::SlimtimerTimeEntry"
3
5
  end
@@ -23,9 +23,11 @@ class Client < ActiveRecord::Base
23
23
  def uninvoiced_activities_balance( force_reload = false )
24
24
  (attribute_present? :uninvoiced_activities_balance_in_cents and !force_reload) ?
25
25
  Money.new(read_attribute(:uninvoiced_activities_balance_in_cents).to_i) :
26
- (Activity.sum( Invoice::ACTIVITY_TOTAL_SQL, :conditions => ['client_id = ? AND is_published = ? AND invoice_id IS NULL',id, true] ) or 0.0)
26
+ (Activity.sum( Invoice::ACTIVITY_TOTAL_SQL, :conditions => ['client_id = ? AND is_published = ? AND invoice_id IS NULL',id, true] ) or 0)
27
27
  end
28
28
 
29
+ # THis is the client's outstanding balance. This value is calculated based off the total invoices amount - total payments amount. And is
30
+ # Not determined based on invoice/payment assignments
29
31
  def balance( force_reload = false )
30
32
  Money.new(
31
33
  (attribute_present? :balance_in_cents and !force_reload) ?
@@ -77,8 +79,8 @@ class Client < ActiveRecord::Base
77
79
  def mailing_address
78
80
  ret = []
79
81
 
80
- %w(name address1 address2).each do |f|
81
- val = send(f.to_sym) and ( ret <<val if val.length )
82
+ %w( name address1 address2 ).each do |f|
83
+ val = send(f.to_sym) and ( ret << val if val.length )
82
84
  end
83
85
 
84
86
  ret << '%s%s %s' % [
@@ -89,5 +91,93 @@ class Client < ActiveRecord::Base
89
91
 
90
92
  ret
91
93
  end
94
+
95
+ # Returns an array of unsaved InvoicePayment objects, with unset payment_ids, and 'recommended' amounts.
96
+ # If the provided amount exactly equals an outstanding invoice's amount, we return a InvoicePayment for the oldest such matching invoice.
97
+ # Otherwise, we start applying the amount to invoices in ascending order by issued_date.
98
+ def recommend_invoice_assignments_for(amount)
99
+ amount = amount.to_money
100
+
101
+ invs = unpaid_invoices(
102
+ :all,
103
+ # Using this order forces the closest-amount match to be above anything else, followed by date sorting
104
+ :order => '(amount_outstanding_in_cents = %d) DESC, issued_on ASC, created_at ASC' % amount.cents
105
+ )
106
+
107
+ unassigned_outstanding = invs.inject(Money.new(0)){|total, inv| total + inv.amount_outstanding}
108
+
109
+ invs.collect{ |inv|
110
+ if amount > 0 and unassigned_outstanding > 0
111
+ assignment = (amount >= inv.amount_outstanding) ?
112
+ inv.amount_outstanding :
113
+ amount
114
+
115
+ unassigned_outstanding -= assignment
116
+ amount -= assignment
117
+
118
+ InvoicePayment.new :invoice => inv, :amount => assignment if assignment > 0
119
+ end
120
+ }.compact
121
+ end
122
+
123
+ # Returns an array of unsaved InvoicePayment objects, with unset invoice_ids, and 'recommended' amounts.
124
+ # If the provided amount exactly equals an payment's unalloated amount, we return a InvoicePayment for the oldest such matching payment.
125
+ # Otherwise, we start applying the amount to payments in ascending order by issued_date.
126
+ def recommend_payment_assignments_for(amount, verbose_inclusion = false)
127
+ amount = amount.to_money
128
+
129
+ pymnts = unassigned_payments(
130
+ :all,
131
+ # Using this order forces the closest-amount match to be above anything else, followed by date sorting
132
+ :order => '(amount_unallocated_in_cents = %d) DESC, paid_on ASC, created_at ASC' % amount.cents
133
+ )
134
+
135
+ current_client_balance = pymnts.inject(Money.new(0)){|total, pmnt| total - pmnt.amount_unallocated}
136
+
137
+ pymnts.collect{ |unallocated_pmnt|
138
+ if amount > 0 or current_client_balance < 0
139
+ assignment = (unallocated_pmnt.amount_unallocated > amount) ?
140
+ amount :
141
+ unallocated_pmnt.amount_unallocated
142
+
143
+ current_client_balance += assignment
144
+ amount -= assignment
145
+
146
+ InvoicePayment.new :payment => unallocated_pmnt, :amount => assignment if assignment > 0
147
+ end
148
+ }.compact
149
+ end
150
+
151
+ # Returns all the client's invoices for which the allocated payments is less than the invoice amount. Perhaps this should be a has_many,
152
+ # But since we're using the find_with_totals that would get complicated...
153
+ def unpaid_invoices( how_many = :all, options = {} )
154
+ Invoice.find_with_totals(
155
+ how_many,
156
+ {:conditions => [
157
+ [
158
+ 'client_id = ?',
159
+ 'is_published = ?',
160
+ 'IF(activities_total.total_in_cents IS NULL, 0,activities_total.total_in_cents) - '+
161
+ 'IF(invoices_total.total_in_cents IS NULL, 0,invoices_total.total_in_cents) > ?'
162
+ ].join(' AND '),
163
+ id, true, 0
164
+ ]}.merge(options.reject{|k,v| k == :conditions})
165
+ )
166
+ end
167
+
168
+ # Returns all the client's payments for which the invoice allocation is less than the payment amount. Perhaps this should be a has_many,
169
+ # But since we're using the find_with_totals that would get complicated...
170
+ def unassigned_payments( how_many = :all, options = {} )
171
+ Payment.find_with_totals(
172
+ how_many,
173
+ {:conditions => [
174
+ [
175
+ 'client_id = ?',
176
+ '(payments.amount_in_cents - IF(payments_total.amount_allocated_in_cents IS NULL, 0, payments_total.amount_allocated_in_cents) ) > ?'
177
+ ].join(' AND '),
178
+ id, 0
179
+ ]}.merge(options.reject{|k,v| k == :conditions})
180
+ )
181
+ end
92
182
 
93
183
  end