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.
- data/CHANGELOG +6 -1
- data/TODO.txt +60 -38
- data/app/controllers/admin/employees_controller.rb +1 -1
- data/app/controllers/admin/invoices_controller.rb +38 -2
- data/app/controllers/admin/payments_controller.rb +13 -6
- data/app/helpers/admin/activity_tax_field_helper.rb +1 -1
- data/app/helpers/admin/activity_type_field_helper.rb +2 -2
- data/app/helpers/admin/payments_helper.rb +8 -2
- data/app/helpers/application_helper.rb +9 -2
- data/app/model_views/invoices_with_total.rb +5 -0
- data/app/models/activity.rb +7 -3
- data/app/models/activity/labor.rb +1 -1
- data/app/models/activity/labor/slimtimer.rb +2 -0
- data/app/models/client.rb +93 -3
- data/app/models/client_representative.rb +9 -1
- data/app/models/employee.rb +21 -1
- data/app/models/employee/slimtimer.rb +11 -0
- data/app/models/invoice.rb +93 -129
- data/app/models/invoice_payment.rb +54 -0
- data/app/models/payment.rb +25 -48
- data/config/boot.rb +1 -1
- data/db/migrate/029_invoices_with_totals_view_adjustment.rb +29 -0
- data/db/schema.rb +1 -1
- data/lib/brisk-bills.rb +1 -1
- data/lib/tasks/create_last_months_invoices.rake +8 -3
- data/public/javascripts/prototype.js +1573 -1019
- data/public/javascripts/prototype.js-1.6.0.3 +4320 -0
- data/test/test_unit_factory_helper.rb +16 -9
- data/test/unit/client_test.rb +298 -2
- data/test/unit/invoice_payment_test.rb +313 -3
- data/test/unit/invoice_test.rb +49 -36
- data/test/unit/payment_test.rb +35 -31
- data/vendor/plugins/active_scaffold_full_refresh/lib/active_scaffold_full_refresh.rb +4 -2
- metadata +69 -33
data/CHANGELOG
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
== Change Log
|
2
2
|
|
3
|
-
=== Release
|
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
|
-
*
|
2
|
-
|
3
|
-
|
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
|
-
*
|
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
|
-
*
|
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
|
-
|
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
|
-
*
|
25
|
-
|
26
|
-
|
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
|
-
|
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
|
-
*
|
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
|
-
|
73
|
+
* We should audit for proper transaction support, and test!
|
56
74
|
|
57
|
-
*
|
58
|
-
*
|
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
|
-
*
|
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?
|
@@ -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
|
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, :
|
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[:
|
21
|
-
config.columns[:
|
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
|
-
# :
|
35
|
-
# :
|
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
|
|
@@ -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 :
|
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 :
|
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
|
17
|
-
'<span class="active-scaffold_detail_value" id="
|
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"
|
5
|
+
'<span class="%s">%s</span>' % [
|
6
6
|
( (inverse_polarity ? (amount > 0 ) : (amount < 0 )) ? 'money_negative':'money_positive' ),
|
7
|
-
amount.
|
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
|
data/app/models/activity.rb
CHANGED
@@ -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 =
|
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
|
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?
|
data/app/models/client.rb
CHANGED
@@ -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
|
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
|