erp_invoicing 3.0.0 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/app/controllers/erp_invoicing/erp_app/organizer/bill_pay/accounts_controller.rb +98 -46
- data/app/controllers/erp_invoicing/erp_app/organizer/bill_pay/base_controller.rb +1 -1
- data/app/controllers/erp_invoicing/erp_app/shared/billing_accounts_controller.rb +111 -0
- data/app/controllers/erp_invoicing/erp_app/shared/files_controller.rb +52 -0
- data/app/controllers/erp_invoicing/erp_app/shared/invoices_controller.rb +208 -0
- data/app/controllers/erp_invoicing/sms_controller.rb +156 -0
- data/app/models/billing_account.rb +204 -0
- data/app/models/extensions/document.rb +5 -0
- data/app/models/extensions/financial_txn.rb +5 -0
- data/app/models/extensions/party.rb +10 -0
- data/app/models/invoice.rb +100 -7
- data/app/models/invoice_item.rb +35 -2
- data/app/models/invoice_payment_strategy_type.rb +5 -0
- data/app/models/invoice_payment_term.rb +4 -0
- data/app/models/invoice_payment_term_set.rb +6 -0
- data/app/models/invoice_payment_term_type.rb +3 -0
- data/app/models/payment_application.rb +6 -1
- data/app/models/recurring_payment.rb +40 -0
- data/config/routes.rb +8 -1
- data/db/data_migrations/20111121153349_create_bill_pay_organizer_application.rb +1 -1
- data/db/data_migrations/20120118181839_create_invoice_management_desktop_application.rb +26 -0
- data/db/data_migrations/20120229174322_add_billpay_widget.rb +28 -0
- data/db/migrate/20111121000000_invoicing_services.rb +170 -103
- data/db/migrate/20120228184317_add_text_to_pay_to_recurring_payments.rb +13 -0
- data/db/migrate/20120301155722_add_billing_date_to_billing_account.rb +13 -0
- data/lib/erp_invoicing/engine.rb +5 -0
- data/lib/erp_invoicing/version.rb +7 -1
- data/lib/erp_invoicing.rb +1 -0
- data/public/javascripts/erp_app/desktop/applications/invoice_management/billing_accounts_panel.js +80 -0
- data/public/javascripts/erp_app/desktop/applications/invoice_management/invoices_panel.js +48 -0
- data/public/javascripts/erp_app/desktop/applications/invoice_management/module.js +36 -0
- data/public/javascripts/erp_app/organizer/applications/bill_pay/base.js +88 -76
- data/public/javascripts/erp_app/organizer/applications/bill_pay/extensions.js +6 -97
- data/public/javascripts/erp_app/organizer/applications/bill_pay/make_payment_window.js +19 -13
- data/public/javascripts/erp_app/organizer/applications/bill_pay/payment_accounts_grid_panel.js +2 -1
- data/public/javascripts/erp_app/shared/add_invoice_window.js +220 -0
- data/public/javascripts/erp_app/shared/billing_accounts_grid_panel.js +318 -0
- data/public/javascripts/erp_app/shared/invoice_items_grid_panel.js +152 -0
- data/public/javascripts/erp_app/shared/invoices_grid_panel.js +363 -0
- data/public/javascripts/erp_app/shared/payments_grid_panel.js +74 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +9 -0
- data/spec/dummy/app/assets/stylesheets/application.css +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config/application.rb +50 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +8 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/spec.rb +27 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +12 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/factories/basic.rb +9 -0
- data/spec/models/billing_account_spec.rb +78 -0
- data/spec/models/invoice_item_spec.rb +22 -0
- data/spec/models/invoice_spec.rb +58 -0
- data/spec/models/payment_application_spec.rb +17 -0
- data/spec/spec_helper.rb +60 -0
- metadata +148 -67
- data/app/widgets/bill_pay/base.rb +0 -230
- data/app/widgets/bill_pay/helpers/controller/bill_pay_controller_helper.rb +0 -3
- data/app/widgets/bill_pay/helpers/view/bill_pay_view_helper.rb +0 -3
- data/app/widgets/bill_pay/javascript/bill_pay.js +0 -11
- data/app/widgets/bill_pay/views/_menu.html.erb +0 -63
- data/app/widgets/bill_pay/views/_payment_history_table.html.erb +0 -115
- data/app/widgets/bill_pay/views/_statement_table.html.erb +0 -117
- data/app/widgets/bill_pay/views/account_home.html.erb +0 -10
- data/app/widgets/bill_pay/views/index.html.erb +0 -4
- data/app/widgets/bill_pay/views/make_payment.html.erb +0 -24
- data/app/widgets/bill_pay/views/payment_account_forms/bank_account.html.erb +0 -28
- data/app/widgets/bill_pay/views/payment_account_forms/credit_card.html.erb +0 -48
- data/app/widgets/bill_pay/views/payment_accounts.html.erb +0 -120
- data/app/widgets/bill_pay/views/payment_history.html.erb +0 -104
- data/app/widgets/bill_pay/views/pdf/layout.pdf.erb +0 -35
- data/app/widgets/bill_pay/views/statements.html.erb +0 -4
- data/public/javascripts/erp_app/organizer/applications/bill_pay/accounts_grid_panel.js +0 -182
@@ -0,0 +1,156 @@
|
|
1
|
+
module ErpInvoicing
|
2
|
+
class SmsController < ::ActionController::Base
|
3
|
+
before_filter :allow_by_ip
|
4
|
+
|
5
|
+
# Receive SMS callback from clickatell
|
6
|
+
# Example: If you provide this URL http://www.yourdomain.com/erp_invoicing/sms/receive_response then we will do a POST or GET as follows:
|
7
|
+
# https://www.yourdomain.com/sms/receive_response?api_id=12345&from=279991235642&to=27123456789&
|
8
|
+
# timestamp=2008-08-0609:43:50&text=Hereisthe%20messagetext&charset=ISO-8859-1&udh=&moMsgId=b2aee337abd962489b123fda9c3480fa
|
9
|
+
def receive_response
|
10
|
+
message_text = params[:text]
|
11
|
+
moMsgId = params[:moMsgId]
|
12
|
+
to_number = params[:to]
|
13
|
+
from_number = params[:from]
|
14
|
+
|
15
|
+
if message_text.blank? or to_number.blank? or from_number.blank?
|
16
|
+
Rails.logger.error 'ErpInvoicing::SmsController#receive_response called with insufficient data'
|
17
|
+
render_false and return
|
18
|
+
render :json => {:success => false} and return
|
19
|
+
end
|
20
|
+
|
21
|
+
# find the comm event sent within past 10mins where that incoming message is a reponse to
|
22
|
+
cmm_evt = CommunicationEvent.find_by_sql("SELECT * FROM communication_events
|
23
|
+
JOIN phone_numbers from_phone ON from_contact_mechanism_id=from_phone.id
|
24
|
+
JOIN phone_numbers to_phone ON to_contact_mechanism_id=to_phone.id
|
25
|
+
WHERE from_contact_mechanism_type = 'PhoneNumber'
|
26
|
+
AND from_contact_mechanism_type='PhoneNumber'
|
27
|
+
AND from_phone.phone_number = '#{to_number.to_s}'
|
28
|
+
AND (to_phone.phone_number = '#{from_number.to_s}' OR to_phone.phone_number = '#{from_number[1..from_number.length]}')
|
29
|
+
AND communication_events.created_at > '#{SMS_TIME_WINDOW.minutes.ago.to_s}'
|
30
|
+
ORDER BY communication_events.created_at DESC").first
|
31
|
+
|
32
|
+
unless cmm_evt.nil?
|
33
|
+
if message_text.downcase.include?('yespay') or message_text.downcase.include?('yes pay')
|
34
|
+
billing_account = BillingAccount.find(cmm_evt.case_id)
|
35
|
+
payment_due = billing_account.payment_due
|
36
|
+
|
37
|
+
# TODO: scrape for amount and compare with payment_due on account?
|
38
|
+
|
39
|
+
if billing_account.has_outstanding_balance?
|
40
|
+
success = submit_payment(cmm_evt, billing_account) if billing_account.has_outstanding_balance?
|
41
|
+
else
|
42
|
+
Rails.logger.error 'ErpInvoicing::SmsController#receive_response called: skipping payment NO OUTSTANDING BALANCE TO PAY'
|
43
|
+
end
|
44
|
+
to_party = cmm_evt.from_party
|
45
|
+
|
46
|
+
# log communication event
|
47
|
+
new_cmm_evt = CommunicationEvent.new
|
48
|
+
new_cmm_evt.short_description = 'SMS Response'
|
49
|
+
new_cmm_evt.comm_evt_purpose_types << CommEvtPurposeType.find_by_internal_identifier('sms_response')
|
50
|
+
new_cmm_evt.to_role = RoleType.find_by_internal_identifier('application')
|
51
|
+
new_cmm_evt.to_party = to_party
|
52
|
+
new_cmm_evt.to_contact_mechanism = to_party.default_phone_number
|
53
|
+
new_cmm_evt.from_contact_mechanism = cmm_evt.to_party.billing_phone_number
|
54
|
+
new_cmm_evt.from_role = RoleType.find_by_internal_identifier('customer')
|
55
|
+
new_cmm_evt.from_party = cmm_evt.to_party
|
56
|
+
new_cmm_evt.case_id = cmm_evt.case_id
|
57
|
+
new_cmm_evt.notes = "From Number: #{from_number}, To Number: #{to_number}, Message: #{message_text}, Payment: #{success}"
|
58
|
+
new_cmm_evt.external_identifier = moMsgId
|
59
|
+
new_cmm_evt.save
|
60
|
+
|
61
|
+
if success
|
62
|
+
# send successful payment notification
|
63
|
+
clikatell = ErpTechSvcs::SmsWrapper::Clickatell.new
|
64
|
+
clikatell.send_message(from_number, SMS_SUCCESS_NOTIFICATION.gsub('payment_due', payment_due.to_s), :mo => 1, :from => to_number)
|
65
|
+
render_true and return
|
66
|
+
else
|
67
|
+
render_false and return
|
68
|
+
end
|
69
|
+
end
|
70
|
+
else
|
71
|
+
Rails.logger.error "ErpInvoicing::SmsController#receive_ ation event #{moMsgId}"
|
72
|
+
render :json => {:success => false} and return
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
def submit_payment(cmm_evt, billing_account)
|
78
|
+
party = cmm_evt.to_party
|
79
|
+
|
80
|
+
@error_message = nil
|
81
|
+
@message = nil
|
82
|
+
@payment_accounts = party.payment_accounts
|
83
|
+
|
84
|
+
root_account = BizTxnAcctRoot.find(@payment_accounts.first)
|
85
|
+
@payment_account = root_account.account
|
86
|
+
@amount = billing_account.payment_due
|
87
|
+
@payment_date = Date.today
|
88
|
+
|
89
|
+
money = Money.create(
|
90
|
+
:amount => @amount.to_f,
|
91
|
+
:description => "Clicktopay Payment Applied",
|
92
|
+
:currency => Currency.usd
|
93
|
+
)
|
94
|
+
financial_txn = FinancialTxn.new(:apply_date => @payment_date)
|
95
|
+
financial_txn.description = "Clicktopay Payment Applied"
|
96
|
+
financial_txn.money = money
|
97
|
+
financial_txn.account = root_account
|
98
|
+
financial_txn.save
|
99
|
+
|
100
|
+
PaymentApplication.create(
|
101
|
+
:financial_txn => financial_txn,
|
102
|
+
:payment_applied_to => billing_account,
|
103
|
+
:money => money
|
104
|
+
)
|
105
|
+
|
106
|
+
if financial_txn.apply_date == Date.today
|
107
|
+
case @payment_account.class.to_s
|
108
|
+
when "BankAccount"
|
109
|
+
financial_txn.txn_type = BizTxnType.ach_sale
|
110
|
+
financial_txn.save
|
111
|
+
result = @payment_account.purchase(financial_txn, ErpCommerce::Config.active_merchant_gateway_wrapper)
|
112
|
+
if !result[:payment].nil? and result[:payment].success
|
113
|
+
@authorization_code = result[:payment].authorization_code
|
114
|
+
else
|
115
|
+
@message = result[:message]
|
116
|
+
end
|
117
|
+
when "CreditCardAccount"
|
118
|
+
financial_txn.txn_type = BizTxnType.cc_sale
|
119
|
+
financial_txn.save
|
120
|
+
result = @payment_account.purchase(financial_txn, '123', ErpCommerce::Config.active_merchant_gateway_wrapper)
|
121
|
+
if !result[:payment].nil? and result[:payment].success
|
122
|
+
@authorization_code = result[:payment].authorization_code
|
123
|
+
else
|
124
|
+
@message = result[:message]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
if @error_message.nil?
|
130
|
+
Rails.logger.info 'ErpInvoicing::SmsController#submit_payment called: payment successful'
|
131
|
+
else
|
132
|
+
Rails.logger.error 'ErpInvoicing::SmsController#submit_payment called: payment failed'
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
def render_false
|
138
|
+
render :json => {:success => false}
|
139
|
+
end
|
140
|
+
|
141
|
+
def render_true
|
142
|
+
render :json => {:success => true}
|
143
|
+
end
|
144
|
+
|
145
|
+
def allow_by_ip
|
146
|
+
Rails.logger.info "ErpInvoicing::SmsController#allow_by_ip called: request from #{request.remote_ip}"
|
147
|
+
if request.remote_ip != SMS_SERVICE_IP
|
148
|
+
redirect_to '/'
|
149
|
+
flash.now[:notice] = "Access denied!"
|
150
|
+
return false
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
class BillingAccount < ActiveRecord::Base
|
2
|
+
has_relational_dynamic_attributes
|
3
|
+
acts_as_financial_txn_account
|
4
|
+
|
5
|
+
|
6
|
+
has_many :invoices, :dependent => :destroy do
|
7
|
+
def by_invoice_date
|
8
|
+
order('invoice_date desc')
|
9
|
+
end
|
10
|
+
|
11
|
+
def balance
|
12
|
+
all.sum(&:balance)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
has_many :payment_applications, :as => :payment_applied_to, :dependent => :destroy do
|
16
|
+
def successful
|
17
|
+
all.select{|item| item.financial_txn.has_captured_payment?}
|
18
|
+
end
|
19
|
+
def pending
|
20
|
+
all.select{|item| item.is_pending?}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
has_one :recurring_payment, :dependent => :destroy
|
24
|
+
|
25
|
+
def has_recurring_payment_enabled?
|
26
|
+
!self.recurring_payment.nil? and self.recurring_payment.enabled
|
27
|
+
end
|
28
|
+
|
29
|
+
def has_payments?(status)
|
30
|
+
selected_payment_applications = self.get_payment_applications(status)
|
31
|
+
!(selected_payment_applications.nil? or selected_payment_applications.empty?)
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_payment_applications(status=:all)
|
35
|
+
selected_payment_applications = case status.to_sym
|
36
|
+
when :pending
|
37
|
+
self.payment_applications.pending
|
38
|
+
when :successful
|
39
|
+
self.payment_applications.successful
|
40
|
+
when :all
|
41
|
+
self.payment_applications
|
42
|
+
end
|
43
|
+
|
44
|
+
unless self.invoices.empty?
|
45
|
+
selected_payment_applications = (selected_payment_applications | self.invoices.collect{|item| item.get_payment_applications(status)}).flatten! unless (self.invoices.collect{|item| item.get_payment_applications(status)}.empty?)
|
46
|
+
end
|
47
|
+
|
48
|
+
selected_payment_applications
|
49
|
+
end
|
50
|
+
|
51
|
+
def has_outstanding_balance?
|
52
|
+
(outstanding_balance > 0)
|
53
|
+
end
|
54
|
+
|
55
|
+
def outstanding_balance
|
56
|
+
(balance - total_pending_payments)
|
57
|
+
end
|
58
|
+
|
59
|
+
def total_pending_payments
|
60
|
+
self.payment_applications.pending.sum{|item| item.money.amount}
|
61
|
+
end
|
62
|
+
|
63
|
+
def total_payments
|
64
|
+
self.payment_applications.successful.sum{|item| item.money.amount}
|
65
|
+
end
|
66
|
+
|
67
|
+
#payment due is determined by last invoice
|
68
|
+
def payment_due
|
69
|
+
if self.calculate_balance and !self.invoices.empty?
|
70
|
+
self.invoices.by_invoice_date.last.payment_due
|
71
|
+
else
|
72
|
+
self.financial_txn_account.payment_due.amount
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def payment_due=(amount, currency=Currency.usd)
|
77
|
+
currency = Currency.usd
|
78
|
+
if amount.is_a?(Array)
|
79
|
+
currency = amount.last
|
80
|
+
amount = amount.first
|
81
|
+
end
|
82
|
+
if self.financial_txn_account.payment_due
|
83
|
+
self.financial_txn_account.payment_due.amount = amount
|
84
|
+
else
|
85
|
+
self.financial_txn_account.payment_due = Money.create(:amount => amount, :currency => currency)
|
86
|
+
end
|
87
|
+
self.financial_txn_account.payment_due.save
|
88
|
+
end
|
89
|
+
|
90
|
+
def billing_date
|
91
|
+
unless self.invoices.empty?
|
92
|
+
current_invoice.invoice_date
|
93
|
+
else
|
94
|
+
self.attributes['billing_date']
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
#override due_date for invoice.invoice_date
|
99
|
+
def due_date
|
100
|
+
unless self.invoices.empty?
|
101
|
+
current_invoice.due_date
|
102
|
+
else
|
103
|
+
self.financial_txn_account.due_date
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
#override balance_date for today if calculate_balance is set to true
|
108
|
+
def balance_date
|
109
|
+
if self.calculate_balance
|
110
|
+
Date.today
|
111
|
+
else
|
112
|
+
unless self.invoices.empty?
|
113
|
+
current_invoice.invoice_date
|
114
|
+
else
|
115
|
+
self.financial_txn_account.balance_date
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
#override balance to use invoices is calculate_balance is set to true
|
121
|
+
def balance
|
122
|
+
if self.calculate_balance
|
123
|
+
self.invoices.balance
|
124
|
+
else
|
125
|
+
(self.financial_txn_account.balance.amount - self.total_payments)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def balance=(amount, currency=Currency.usd)
|
130
|
+
if amount.is_a?(Array)
|
131
|
+
currency = amount.last
|
132
|
+
amount = amount.first
|
133
|
+
end
|
134
|
+
if self.financial_txn_account.balance
|
135
|
+
self.financial_txn_account.balance.amount = amount
|
136
|
+
else
|
137
|
+
self.financial_txn_account.balance = Money.create(:amount => amount, :currency => currency)
|
138
|
+
end
|
139
|
+
self.financial_txn_account.balance.save
|
140
|
+
end
|
141
|
+
|
142
|
+
def current_invoice
|
143
|
+
self.invoices.by_invoice_date.last
|
144
|
+
end
|
145
|
+
|
146
|
+
def send_sms_notification
|
147
|
+
primary_party = self.find_parties_by_role('primary').first
|
148
|
+
from_party = Party.find_by_description('Compass AE')
|
149
|
+
from_number = from_party.default_phone_number
|
150
|
+
to_number = primary_party.billing_phone_number
|
151
|
+
|
152
|
+
# prevent multiple sms notifications being sent within the time window
|
153
|
+
previous_cmm_evt = CommunicationEvent.find_by_sql("SELECT * FROM communication_events
|
154
|
+
JOIN phone_numbers from_phone ON from_contact_mechanism_id=from_phone.id
|
155
|
+
JOIN phone_numbers to_phone ON to_contact_mechanism_id=to_phone.id
|
156
|
+
WHERE from_contact_mechanism_type = 'PhoneNumber'
|
157
|
+
AND from_contact_mechanism_type='PhoneNumber'
|
158
|
+
AND from_phone.phone_number = '#{from_number.phone_number.to_s}'
|
159
|
+
AND (to_phone.phone_number = '#{to_number.phone_number.to_s}' OR to_phone.phone_number = '#{to_number.phone_number[1..to_number.phone_number.length]}')
|
160
|
+
AND communication_events.created_at > '#{SMS_TIME_WINDOW.minutes.ago.to_s}'
|
161
|
+
ORDER BY communication_events.created_at DESC").first
|
162
|
+
|
163
|
+
Rails.logger.info 'not sending sms notification, one has already been sent within time window' if !previous_cmm_evt.nil?
|
164
|
+
|
165
|
+
unless primary_party.billing_phone_number.nil? or !previous_cmm_evt.nil?
|
166
|
+
message = SMS_NOTIFICATION_MESSAGE.gsub('payment_due',self.payment_due.to_s)
|
167
|
+
|
168
|
+
|
169
|
+
# get cmm event purpose type
|
170
|
+
sms_purpose = CommEvtPurposeType.find_by_internal_identifier('sms_notification')
|
171
|
+
|
172
|
+
# create cmm event
|
173
|
+
cmm_evt = CommunicationEvent.new
|
174
|
+
cmm_evt.short_description = 'SMS Notification'
|
175
|
+
cmm_evt.from_role = RoleType.find_by_internal_identifier('application')
|
176
|
+
cmm_evt.from_party = from_party
|
177
|
+
cmm_evt.from_contact_mechanism = from_number
|
178
|
+
cmm_evt.comm_evt_purpose_types << sms_purpose
|
179
|
+
cmm_evt.to_contact_mechanism = to_number
|
180
|
+
cmm_evt.to_role = RoleType.find_by_internal_identifier('customer')
|
181
|
+
cmm_evt.to_party = primary_party
|
182
|
+
cmm_evt.case_id = self.id
|
183
|
+
cmm_evt.notes = "From Number: #{from_number}, To Number: #{to_number}, Message: #{message}"
|
184
|
+
|
185
|
+
clikatell = ErpTechSvcs::SmsWrapper::Clickatell.new
|
186
|
+
cmm_evt.external_identifier = clikatell.send_message(to_number.phone_number, message, :mo => 1, :from => from_number.phone_number)
|
187
|
+
|
188
|
+
unless cmm_evt.external_identifier.nil?
|
189
|
+
cmm_evt.save
|
190
|
+
return true
|
191
|
+
else
|
192
|
+
return false
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def send_email_notification
|
198
|
+
primary_party = self.find_parties_by_role('primary').first
|
199
|
+
unless primary_party.billing_email_address.nil?
|
200
|
+
#send email
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
Party.class_eval do
|
2
|
+
|
3
|
+
has_many :invoice_party_roles, :dependent => :destroy
|
4
|
+
has_many :invoices, :through => :invoice_party_roles
|
5
|
+
|
6
|
+
def billing_accounts
|
7
|
+
self.biz_txn_acct_roots.where('biz_txn_acct_type = ?', 'FinancialTxnAccount').collect(&:account).collect(&:financial_account)
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
data/app/models/invoice.rb
CHANGED
@@ -1,16 +1,109 @@
|
|
1
1
|
class Invoice < ActiveRecord::Base
|
2
|
+
acts_as_document
|
2
3
|
|
4
|
+
belongs_to :billing_account
|
3
5
|
belongs_to :invoice_type
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
belongs_to :invoice_payment_strategy_type
|
7
|
+
has_many :invoice_payment_term_sets, :dependent => :destroy
|
8
|
+
has_many :payment_applications, :as => :payment_applied_to, :dependent => :destroy do
|
9
|
+
def successful
|
10
|
+
all.select{|item| item.financial_txn.has_captured_payment?}
|
11
|
+
end
|
12
|
+
def pending
|
13
|
+
all.select{|item| item.is_pending?}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
has_many :invoice_items, :dependent => :destroy do
|
17
|
+
def by_date
|
18
|
+
order('created_at')
|
19
|
+
end
|
11
20
|
|
21
|
+
def unpaid
|
22
|
+
select{|item| item.balance > 0 }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
has_many :invoice_party_roles, :dependent => :destroy
|
26
|
+
has_many :parties, :through => :invoice_party_roles
|
27
|
+
|
12
28
|
alias :items :invoice_items
|
13
29
|
alias :type :invoice_type
|
14
30
|
alias :party_roles :invoice_party_roles
|
31
|
+
alias :payment_strategy :invoice_payment_strategy_type
|
32
|
+
|
33
|
+
def has_payments?(status)
|
34
|
+
selected_payment_applications = self.get_payment_applications(status)
|
35
|
+
!(selected_payment_applications.nil? or selected_payment_applications.empty?)
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_payment_applications(status=:all)
|
39
|
+
selected_payment_applications = case status.to_sym
|
40
|
+
when :pending
|
41
|
+
self.payment_applications.pending
|
42
|
+
when :successful
|
43
|
+
self.payment_applications.successful
|
44
|
+
when :all
|
45
|
+
self.payment_applications
|
46
|
+
end
|
47
|
+
|
48
|
+
unless self.items.empty?
|
49
|
+
selected_payment_applications = (selected_payment_applications | self.items.collect{|item| item.get_payment_applications(status)}).flatten! unless (self.items.collect{|item| item.get_payment_applications(status)}.empty?)
|
50
|
+
end
|
51
|
+
|
52
|
+
selected_payment_applications
|
53
|
+
end
|
54
|
+
|
55
|
+
def balance
|
56
|
+
(self.payment_due - self.total_payments)
|
57
|
+
end
|
58
|
+
|
59
|
+
def payment_due
|
60
|
+
self.items.all.sum(&:total_amount)
|
61
|
+
end
|
62
|
+
|
63
|
+
def total_payments
|
64
|
+
self.get_payment_applications(:successful).sum{|item| item.money.amount}
|
65
|
+
end
|
66
|
+
|
67
|
+
def transactions
|
68
|
+
transactions = []
|
69
|
+
|
70
|
+
self.items.each do |item|
|
71
|
+
transactions << {
|
72
|
+
:date => item.created_at,
|
73
|
+
:description => item.item_description,
|
74
|
+
:quantity => item.quantity,
|
75
|
+
:amount => item.amount
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
self.get_payment_applications(:successful).each do |item|
|
80
|
+
transactions << {
|
81
|
+
:date => item.financial_txn.payments.last.created_at,
|
82
|
+
:description => item.financial_txn.description,
|
83
|
+
:quantity => 1,
|
84
|
+
:amount => (0 - item.financial_txn.money.amount)
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
transactions.sort_by{|item| [item[:date]]}
|
89
|
+
end
|
90
|
+
|
91
|
+
def add_party_with_role_type(party, role_type)
|
92
|
+
self.invoice_party_roles << InvoicePartyRole.create(:party => party, :role_type => convert_role_type(role_type))
|
93
|
+
self.save
|
94
|
+
end
|
95
|
+
|
96
|
+
def find_parties_by_role_type(role_type)
|
97
|
+
self.invoice_party_roles.where('role_type_id = ?', convert_role_type(role_type).id).all.collect(&:party)
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def convert_role_type(role_type)
|
103
|
+
role_type = RoleType.iid(role_type) if role_type.is_a? String
|
104
|
+
raise "Role type does not exist" if role_type.nil?
|
105
|
+
|
106
|
+
role_type
|
107
|
+
end
|
15
108
|
|
16
109
|
end
|
data/app/models/invoice_item.rb
CHANGED
@@ -2,12 +2,45 @@ class InvoiceItem < ActiveRecord::Base
|
|
2
2
|
|
3
3
|
belongs_to :agreement
|
4
4
|
belongs_to :agreement_item_type
|
5
|
-
|
6
5
|
belongs_to :invoiceable_item, :polymorphic => true
|
7
6
|
|
8
7
|
#This line of code connects the invoice to a polymorphic payment application.
|
9
8
|
#The effect of this is to allow payments to be "applied_to" invoices
|
10
|
-
has_many :payment_applications, :as => :payment_applied_to
|
9
|
+
has_many :payment_applications, :as => :payment_applied_to, :dependent => :destroy do
|
10
|
+
def pending
|
11
|
+
all.select{|item| item.is_pending?}
|
12
|
+
end
|
13
|
+
def successful
|
14
|
+
all.select{|item| item.financial_txn.has_captured_payment?}
|
15
|
+
end
|
16
|
+
end
|
11
17
|
|
18
|
+
def has_payments?(status)
|
19
|
+
selected_payment_applications = self.get_payment_applications(status)
|
20
|
+
!(selected_payment_applications.nil? or selected_payment_applications.empty?)
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_payment_applications(status=:all)
|
24
|
+
case status.to_sym
|
25
|
+
when :pending
|
26
|
+
self.payment_applications.pending
|
27
|
+
when :successful
|
28
|
+
self.payment_applications.successful
|
29
|
+
when :all
|
30
|
+
self.payment_applications
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def total_amount
|
35
|
+
(self.amount * self.quantity)
|
36
|
+
end
|
37
|
+
|
38
|
+
def total_payments
|
39
|
+
self.get_payment_applications(:successful).sum{|item| item.money.amount}
|
40
|
+
end
|
41
|
+
|
42
|
+
def balance
|
43
|
+
self.total_amount - self.total_payments
|
44
|
+
end
|
12
45
|
|
13
46
|
end
|
@@ -1,6 +1,11 @@
|
|
1
1
|
class PaymentApplication < ActiveRecord::Base
|
2
2
|
|
3
|
-
belongs_to :
|
3
|
+
belongs_to :financial_txn, :dependent => :destroy
|
4
4
|
belongs_to :payment_applied_to, :polymorphic => true
|
5
|
+
belongs_to :money, :foreign_key => 'applied_money_amount_id', :dependent => :destroy
|
6
|
+
|
7
|
+
def is_pending?
|
8
|
+
(self.financial_txn.is_scheduled? or self.financial_txn.is_pending?) unless self.financial_txn.nil?
|
9
|
+
end
|
5
10
|
|
6
11
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class RecurringPayment < ActiveRecord::Base
|
2
|
+
belongs_to :billing_account
|
3
|
+
belongs_to :payment_account, :polymorphic => true
|
4
|
+
|
5
|
+
def schedule_payment(date)
|
6
|
+
unless self.payment_account.nil?
|
7
|
+
if self.billing_account.has_outstanding_balance?
|
8
|
+
payment_amount = self.billing_account.outstanding_balance
|
9
|
+
if payment_amount < self.pay_up_to_amount
|
10
|
+
|
11
|
+
money = Money.create(
|
12
|
+
:amount => payment_amount.to_f,
|
13
|
+
:description => "AutoPayment",
|
14
|
+
:currency => Currency.usd
|
15
|
+
)
|
16
|
+
financial_txn = FinancialTxn.create(
|
17
|
+
:apply_date => date,
|
18
|
+
:money => money
|
19
|
+
)
|
20
|
+
financial_txn.description = "AutoPayment"
|
21
|
+
financial_txn.account = self.payment_account.account_root
|
22
|
+
financial_txn.save
|
23
|
+
|
24
|
+
PaymentApplication.create(
|
25
|
+
:financial_txn => financial_txn,
|
26
|
+
:payment_applied_to => self.billing_account,
|
27
|
+
:money => money,
|
28
|
+
:comment => "AutoPayment"
|
29
|
+
)
|
30
|
+
|
31
|
+
#make sure the payment is below the pay_up_to_amount
|
32
|
+
else
|
33
|
+
#notify payment greater than amount in autopay
|
34
|
+
end
|
35
|
+
end#make sure the account has an outstanding balance
|
36
|
+
end#make sure it has a payment account
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
data/config/routes.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
ErpInvoicing::Engine.routes.draw do
|
2
2
|
|
3
|
+
match '/sms/receive_response' => "sms#receive_response"
|
4
|
+
|
3
5
|
match '/erp_app/organizer/bill_pay/base(/:action)' => "erp_app/organizer/bill_pay/base#index"
|
4
6
|
match '/erp_app/organizer/bill_pay/accounts(/:action)' => "erp_app/organizer/bill_pay/accounts#index"
|
5
7
|
|
6
|
-
|
8
|
+
#shared
|
9
|
+
match '/erp_app/shared/billing_accounts(/:action(/:id))' => "erp_app/shared/billing_accounts"
|
10
|
+
match '/erp_app/shared/invoices(/:action(/:id))' => "erp_app/shared/invoices"
|
11
|
+
match '/erp_app/shared/invoices/files/:invoice_id(/:action)' => "erp_app/shared/files"
|
12
|
+
|
13
|
+
end
|
@@ -2,7 +2,7 @@ class CreateBillPayOrganizerApplication
|
|
2
2
|
def self.up
|
3
3
|
OrganizerApplication.create(
|
4
4
|
:description => 'Bill Pay',
|
5
|
-
:icon => 'icon-
|
5
|
+
:icon => 'icon-creditcards',
|
6
6
|
:javascript_class_name => 'Compass.ErpApp.Organizer.Applications.BillPay.Base',
|
7
7
|
:internal_identifier => 'bill_pay'
|
8
8
|
)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class CreateInvoiceManagementDesktopApplication
|
2
|
+
def self.up
|
3
|
+
app = DesktopApplication.create(
|
4
|
+
:description => 'Invoice Management',
|
5
|
+
:icon => 'icon-creditcards',
|
6
|
+
:javascript_class_name => 'Compass.ErpApp.Desktop.Applications.InvoiceManagement',
|
7
|
+
:internal_identifier => 'invoice_management',
|
8
|
+
:shortcut_id => 'invoice_management-win'
|
9
|
+
)
|
10
|
+
|
11
|
+
app.save
|
12
|
+
|
13
|
+
pt1 = PreferenceType.iid('desktop_shortcut')
|
14
|
+
pt1.preferenced_records << app
|
15
|
+
pt1.save
|
16
|
+
|
17
|
+
pt2 = PreferenceType.iid('autoload_application')
|
18
|
+
pt2.preferenced_records << app
|
19
|
+
pt2.save
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.down
|
24
|
+
DesktopApplication.destroy_all(['internal_identifier = ?','invoice_management'])
|
25
|
+
end
|
26
|
+
end
|