brisk-bills 0.6.0 → 0.7.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 (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