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.
- 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
|