invoicing 0.1.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 +3 -0
- data/LICENSE +20 -0
- data/Manifest +60 -0
- data/README +48 -0
- data/Rakefile +75 -0
- data/invoicing.gemspec +41 -0
- data/lib/invoicing.rb +9 -0
- data/lib/invoicing/cached_record.rb +107 -0
- data/lib/invoicing/class_info.rb +187 -0
- data/lib/invoicing/connection_adapter_ext.rb +44 -0
- data/lib/invoicing/countries/uk.rb +24 -0
- data/lib/invoicing/currency_value.rb +212 -0
- data/lib/invoicing/find_subclasses.rb +193 -0
- data/lib/invoicing/ledger_item.rb +718 -0
- data/lib/invoicing/ledger_item/render_html.rb +515 -0
- data/lib/invoicing/ledger_item/render_ubl.rb +268 -0
- data/lib/invoicing/line_item.rb +246 -0
- data/lib/invoicing/price.rb +9 -0
- data/lib/invoicing/tax_rate.rb +9 -0
- data/lib/invoicing/taxable.rb +355 -0
- data/lib/invoicing/time_dependent.rb +388 -0
- data/lib/invoicing/version.rb +21 -0
- data/test/cached_record_test.rb +100 -0
- data/test/class_info_test.rb +253 -0
- data/test/connection_adapter_ext_test.rb +71 -0
- data/test/currency_value_test.rb +184 -0
- data/test/find_subclasses_test.rb +120 -0
- data/test/fixtures/README +7 -0
- data/test/fixtures/cached_record.sql +22 -0
- data/test/fixtures/class_info.sql +28 -0
- data/test/fixtures/currency_value.sql +29 -0
- data/test/fixtures/find_subclasses.sql +43 -0
- data/test/fixtures/ledger_item.sql +39 -0
- data/test/fixtures/line_item.sql +33 -0
- data/test/fixtures/price.sql +4 -0
- data/test/fixtures/tax_rate.sql +4 -0
- data/test/fixtures/taxable.sql +14 -0
- data/test/fixtures/time_dependent.sql +35 -0
- data/test/ledger_item_test.rb +352 -0
- data/test/line_item_test.rb +139 -0
- data/test/models/README +4 -0
- data/test/models/test_subclass_in_another_file.rb +3 -0
- data/test/models/test_subclass_not_in_database.rb +6 -0
- data/test/price_test.rb +9 -0
- data/test/ref-output/creditnote3.html +82 -0
- data/test/ref-output/creditnote3.xml +89 -0
- data/test/ref-output/invoice1.html +93 -0
- data/test/ref-output/invoice1.xml +111 -0
- data/test/ref-output/invoice2.html +86 -0
- data/test/ref-output/invoice2.xml +98 -0
- data/test/ref-output/invoice_null.html +36 -0
- data/test/render_html_test.rb +69 -0
- data/test/render_ubl_test.rb +32 -0
- data/test/setup.rb +37 -0
- data/test/tax_rate_test.rb +9 -0
- data/test/taxable_test.rb +180 -0
- data/test/test_helper.rb +48 -0
- data/test/time_dependent_test.rb +180 -0
- data/website/curvycorners.js +1 -0
- data/website/screen.css +149 -0
- data/website/template.html.erb +43 -0
- metadata +180 -0
@@ -0,0 +1,718 @@
|
|
1
|
+
module Invoicing
|
2
|
+
# = Ledger item objects
|
3
|
+
#
|
4
|
+
# This module implements a simple ledger, i.e. the record of all of the business transactions
|
5
|
+
# which are handled by your application. Each transaction is recorded as one +LedgerItem+ object,
|
6
|
+
# each of which may have one of the following three types:
|
7
|
+
#
|
8
|
+
# +Invoice+::
|
9
|
+
# When you send an invoice to someone (= a customer), this is a record of the fact
|
10
|
+
# that you have sold them something (a product, a service etc.), and how much you expect to be
|
11
|
+
# paid for it. An invoice can consist of a list of individual charges, but it is considered as
|
12
|
+
# one document for legal purposes. You can also create invoices from someone else to yourself,
|
13
|
+
# if you owe someone else money -- for example, if you need to pay commissions to a reseller of
|
14
|
+
# your application.
|
15
|
+
# +CreditNote+::
|
16
|
+
# This is basically a invoice for a negative amount; you should use it if you have previously
|
17
|
+
# sent a customer an invoice with an amount which was too great (i.e. you have overcharged them).
|
18
|
+
# The numeric values stored in the database for a credit note are negative, to make it easier to
|
19
|
+
# calculate account summaries, but they may be formatted as positive values when presented to
|
20
|
+
# users if that is customary in your country. For example, if you send a customer an invoice
|
21
|
+
# with a +total_amount+ of $20 and a credit note with a +total_amount+ of -$10, that means that
|
22
|
+
# overall you're asking them to pay $10.
|
23
|
+
# +Payment+::
|
24
|
+
# This is a record of the fact that a payment has been made. It's a simple object, in effect just
|
25
|
+
# saying that party A paid amount X to party B on date Y. This module does not implement any
|
26
|
+
# particular payment mechanism such as credit card handling, although it could be implemented on
|
27
|
+
# top of a +Payment+ object.
|
28
|
+
#
|
29
|
+
# == Important principles
|
30
|
+
#
|
31
|
+
# Note the distinction between Invoices/Credit Notes and Payments; to keep your accounts clean,
|
32
|
+
# it is important that you do not muddle these up.
|
33
|
+
#
|
34
|
+
# * <b>Invoices and Credit Notes</b> are the important documents for tax purposes in most
|
35
|
+
# jurisdictions. They record the date on which the sale is officially made, and that date
|
36
|
+
# determines which tax rates apply. An invoice often also represents the transfer of ownership from
|
37
|
+
# the supplier to the customer; for example, if you ask your customers to send payment in
|
38
|
+
# advance (such as 'topping up' their account), that money still belongs to your customer
|
39
|
+
# until the point where they have used your service, and you have charged them for your
|
40
|
+
# service by sending them an invoice. You should only invoice them for what they have actually
|
41
|
+
# used, then the remaining balance will automatically be retained on their account.
|
42
|
+
# * <b>Payments</b> are just what it says on the tin -- the transfer of money from one hand
|
43
|
+
# to another. A payment may occur before an invoice is issued (payment in advance), or
|
44
|
+
# after/at the same time as an invoice is issued to settle the debt (payment in arrears, giving
|
45
|
+
# your customers credit). You can choose whatever makes sense for your business.
|
46
|
+
# Payments may often be associated one-to-one with invoices, but not necessarily -- an invoice
|
47
|
+
# may be paid in instalments, or several invoices may be lumped together to one payment. Your
|
48
|
+
# customer may even refuse to pay some charges, in which case there is an invoice but no payment
|
49
|
+
# (until at some point you either reverse it with a credit note, or write it off as bad debt,
|
50
|
+
# but that's beyond our scope right now).
|
51
|
+
#
|
52
|
+
# Another very important principle is that once a piece of information has been added to the
|
53
|
+
# ledger, you <b>should not modify or delete it</b>. Particularly when you have 'sent' one of your
|
54
|
+
# customers or suppliers a document (which may mean simply that they have seen it on the web) you should
|
55
|
+
# not change it again, because they might have added that information to their own accounting system.
|
56
|
+
# Changing any information is guaranteed to lead to confusion. (The model objects do not restrict your
|
57
|
+
# editing capabilities because they might be necessary in specific circumstances, but you should be
|
58
|
+
# extremely careful when changing anything.)
|
59
|
+
#
|
60
|
+
# Of course you make mistakes or change your mind, but please deal with them cleanly:
|
61
|
+
# * If you create an invoice whose value is too small, don't amend the invoice, but send them
|
62
|
+
# another invoice to cover the remaining amount.
|
63
|
+
# * If you create an invoice whose value is too great (for example because you want to offer one
|
64
|
+
# customer a special discount), don't amend the invoice, but send them a credit note to waive
|
65
|
+
# your claim to the difference.
|
66
|
+
# * If you create a payment, mark it as +pending+ until it the money has actually arrived.
|
67
|
+
# If it never arrives, keep the record but mark it as +failed+ in case you need to investigate
|
68
|
+
# it later.
|
69
|
+
#
|
70
|
+
# The exception to the 'no modifications' rule are invoices on which you accumulate charges
|
71
|
+
# (e.g. over the course of a month)
|
72
|
+
# and then officially 'send' the invoice at the end of the period. In this gem we call such
|
73
|
+
# invoices +open+ while they may still be changed. It's ok to add charges to +open+ invoices
|
74
|
+
# as you go along; while it is +open+ it is not legally an invoice, but only a statement
|
75
|
+
# of accumulated charges. If you display it to users, make sure that you don't call it "invoice",
|
76
|
+
# to avoid confusion. Only when you set it to +closed+ at the end of the month does the
|
77
|
+
# statement become an invoice for legal purposes. Once it's +closed+ you must not add
|
78
|
+
# any further charges to it.
|
79
|
+
#
|
80
|
+
# Finally, each ledger item has a sender and a recipient; typically one of the two will be
|
81
|
+
# <b>you</b> (the person/organsation who owns/operates the application):
|
82
|
+
# * For invoices, credit notes and payments between you and your customers, set the sender
|
83
|
+
# to be yourself and the recipient to be your customer;
|
84
|
+
# * If you use this system to record suppliers, set the sender to be your supplier and the
|
85
|
+
# recipient to be yourself.
|
86
|
+
# (See below for details.) It is perfectly ok to have documents which are sent between your
|
87
|
+
# users, where you are neither sender nor recipient; this may be useful if you want to allow
|
88
|
+
# users to trade directly with each other.
|
89
|
+
#
|
90
|
+
# == Using invoices, credit notes and payments in your application
|
91
|
+
#
|
92
|
+
# All invoices, credit notes and payments (collectively called 'ledger items') are stored in a
|
93
|
+
# single database table. We use <b>single table inheritance</b> to distinguish the object types.
|
94
|
+
# You need to create at least the following four model classes in your application:
|
95
|
+
#
|
96
|
+
# class LedgerItem < ActiveRecord::Base
|
97
|
+
# acts_as_ledger_item
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
# class Invoice < LedgerItem # Base class for all types of invoice
|
101
|
+
# acts_as_ledger_item :subtype => :invoice
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
# class CreditNote < LedgerItem # Base class for all types of credit note
|
105
|
+
# acts_as_ledger_item :subtype => :credit_note
|
106
|
+
# end
|
107
|
+
#
|
108
|
+
# class Payment < LedgerItem # Base class for all types of payment
|
109
|
+
# acts_as_ledger_item :subtype => :payment
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# You may give the classes different names than these, and you can package them in modules if
|
113
|
+
# you wish, but they need to have the <tt>:subtype => ...</tt> option parameters as above.
|
114
|
+
#
|
115
|
+
# You can create as many subclasses as you like of each of Invoice, CreditNote and Payment. This
|
116
|
+
# provides a convenient mechanism for encapsulating different types of functionality which you
|
117
|
+
# may need for different types of transactions, but still keeping the accounts in one place. You
|
118
|
+
# may start with only one subclass of +Invoice+ (e.g. <tt>class MonthlyChargesInvoice < Invoice</tt>
|
119
|
+
# to bill users for their use of your application; but as you want to do more clever things, you
|
120
|
+
# can add other subclasses of +Invoice+ as and when you need them (such as +ConsultancyServicesInvoice+
|
121
|
+
# and +SalesCommissionInvoice+, for example). Similarly for payments, you may have subclasses
|
122
|
+
# representing credit card payments, cash payments, bank transfers etc.
|
123
|
+
#
|
124
|
+
# Please note that the +Payment+ ledger item type does not itself implement any particular
|
125
|
+
# payment methods such as credit card handling; however, for third-party libraries providing
|
126
|
+
# credit card handling, this would be a good place to integrate.
|
127
|
+
#
|
128
|
+
# The model classes must have a certain minimum set of columns and a few common methods, documented
|
129
|
+
# below (although you may rename any of them if you wish). Beyond those, you may add other methods and
|
130
|
+
# database columns for your application's own needs, provided they don't interfere with names used here.
|
131
|
+
#
|
132
|
+
# == Required methods/database columns
|
133
|
+
#
|
134
|
+
# The following methods/database columns are <b>required</b> for +LedgerItem+ objects (you may give them
|
135
|
+
# different names, but then you need to tell +acts_as_ledger_item+ about your custom names):
|
136
|
+
#
|
137
|
+
# +type+::
|
138
|
+
# String to store the class name, for ActiveRecord single table inheritance.
|
139
|
+
#
|
140
|
+
# +sender_id+::
|
141
|
+
# Integer-valued foreign key, used to refer to some other model object representing the party
|
142
|
+
# (person, company etc.) who is the sender of the transaction.
|
143
|
+
# - In the case of an invoice or credit note, the +sender_id+ identifies the supplier of the product or service,
|
144
|
+
# i.e. the person who is owed the amount specified on the invoice, also known as the creditor.
|
145
|
+
# - In the case of a payment record, the +sender_id+ identifies the payee, i.e. the person who sends the note
|
146
|
+
# confirming that they received payment.
|
147
|
+
# - This field may be +NULL+ to refer to yourself (i.e. the company/person who owns or
|
148
|
+
# operates this application), but you may also use non-+NULL+ values to refer to yourself. It's just
|
149
|
+
# important that you consistently refer to the same party by the same value in different ledger items.
|
150
|
+
#
|
151
|
+
# +recipient_id+::
|
152
|
+
# The counterpart to +sender_id+: foreign key to a model object which represents the
|
153
|
+
# party who is the recipient of the transaction.
|
154
|
+
# - In the case of an invoice or credit note, the +recipient_id+ identifies the customer/buyer of the product or
|
155
|
+
# service, i.e. the person who owes the amount specified on the invoice, also known as the debtor.
|
156
|
+
# - In the case of a payment record, the +recipient_id+ identifies the payer, i.e. the recipient of the
|
157
|
+
# payment receipt.
|
158
|
+
# - +NULL+ may be used as in +sender_id+.
|
159
|
+
#
|
160
|
+
# +sender_details+::
|
161
|
+
# A method (does not have to be a database column) which returns a hash with information
|
162
|
+
# about the party identified by +sender_id+. See the documentation of +sender_details+ for the expected
|
163
|
+
# contents of the hash. Must always return valid details, even if +sender_id+ is +NULL+.
|
164
|
+
#
|
165
|
+
# +recipient_details+::
|
166
|
+
# A method (does not have to be a database column) which returns a hash with information
|
167
|
+
# about the party identified by +recipient_id+. See the documentation of +sender_details+ for the expected
|
168
|
+
# contents of the hash (+recipient_details+ uses the same format as +sender_details+). Must always
|
169
|
+
# return valid details, even if +recipient_id+ is +NULL+.
|
170
|
+
#
|
171
|
+
# +identifier+::
|
172
|
+
# A number or string used to identify this record, i.e. the invoice number, credit note number or
|
173
|
+
# payment receipt number as appropriate.
|
174
|
+
# - There may be legal requirements in your country concerning its format, but as long as it uniquely identifies
|
175
|
+
# the document within your organisation you should be safe.
|
176
|
+
# - It's possible to simply make this an alias of the primary key, but it's strongly recommended that you use a
|
177
|
+
# separate database column. If you ever need to generate invoices on behalf of other people (i.e. where
|
178
|
+
# +sender_id+ is not you), you need to give the sender of the invoice the opportunity to enter their own
|
179
|
+
# +identifier+ (because it then must be unique within the sender's organisation, not yours).
|
180
|
+
#
|
181
|
+
# +issue_date+::
|
182
|
+
# A datetime column which indicates the date on which the document is issued, and which may also
|
183
|
+
# serve as the tax point (the date which determines which tax rate is applied). This should be a separate
|
184
|
+
# column, because it won't necessarily be the same as +created_at+ or +updated_at+. There may be business
|
185
|
+
# reasons for choosing particular dates, but the date at which you send the invoice or receive the payment
|
186
|
+
# should do unless your accountant advises you otherwise.
|
187
|
+
#
|
188
|
+
# +currency+::
|
189
|
+
# The 3-letter code which identifies the currency used in this transaction; must be one of the list
|
190
|
+
# of codes in ISO-4217[http://en.wikipedia.org/wiki/ISO_4217]. (Even if you only use one currency throughout
|
191
|
+
# your site, this is needed to format monetary amounts correctly.)
|
192
|
+
#
|
193
|
+
# +total_amount+::
|
194
|
+
# A decimal column containing the grand total monetary sum (of the invoice or credit note), or the monetary
|
195
|
+
# amount paid (of the payment record), including all taxes, charges etc. For invoices and credit notes, a
|
196
|
+
# +before_validation+ filter is automatically invoked, which adds up the +net_amount+ and +tax_amount+ values
|
197
|
+
# of all line items and assigns that sum to +total_amount+. For payment records, which do not usually have
|
198
|
+
# line items, you must assign the correct value to this column. See the documentation of the +CurrencyValue+
|
199
|
+
# module for notes on suitable datatypes for monetary values. +acts_as_currency_value+ is automatically applied
|
200
|
+
# to this attribute.
|
201
|
+
#
|
202
|
+
# +tax_amount+::
|
203
|
+
# If you're a small business you maybe don't need to add tax to your invoices; but if you are successful,
|
204
|
+
# you almost certainly will need to do so eventually. In most countries this takes the form of Value Added
|
205
|
+
# Tax (VAT) or Sales Tax. For invoices and credit notes, you must store the amount of tax in this table;
|
206
|
+
# a +before_validation+ filter is automatically invoked, which adds up the +tax_amount+ values of all
|
207
|
+
# line items and assigns that sum to +total_amount+. For payment records this should be zero (unless you
|
208
|
+
# use a cash accounting scheme, which is currently not supported). See the documentation of the
|
209
|
+
# +CurrencyValue+ module for notes on suitable datatypes for monetary values. +acts_as_currency_value+ is
|
210
|
+
# automatically applied to this attribute.
|
211
|
+
#
|
212
|
+
# +status+::
|
213
|
+
# A string column used to keep track of the status of ledger items. Currently the following values are defined
|
214
|
+
# (but future versions may add further +status+ values):
|
215
|
+
# +open+:: For invoices/credit notes: the document is not yet finalised, further line items may be added.
|
216
|
+
# +closed+:: For invoices/credit notes: the document has been sent to the recipient and will not be changed again.
|
217
|
+
# +cancelled+:: For invoices/credit notes: the document has been declared void and does not count towards accounts.
|
218
|
+
# (Use this sparingly; if you want to refund an invoice that has been sent, send a credit note.)
|
219
|
+
# +pending+:: For payments: payment is expected or has been sent, but has not yet been confirmed as received.
|
220
|
+
# +cleared+:: For payments: payment has completed successfully.
|
221
|
+
# +failed+:: For payments: payment did not succeed; this record is not counted towards accounts.
|
222
|
+
#
|
223
|
+
# +description+::
|
224
|
+
# A method which returns a short string describing what this invoice, credit note or payment is about.
|
225
|
+
# Can be a database column but doesn't have to be.
|
226
|
+
#
|
227
|
+
# +line_items+::
|
228
|
+
# You should define an association <tt>has_many :line_items, ...</tt> referring to the +LineItem+ objects
|
229
|
+
# associated with this ledger item.
|
230
|
+
#
|
231
|
+
#
|
232
|
+
# == Optional methods/database columns
|
233
|
+
#
|
234
|
+
# The following methods/database columns are <b>optional, but recommended</b> for +LedgerItem+ objects:
|
235
|
+
#
|
236
|
+
# +period_start+, +period_end+::
|
237
|
+
# Two datetime columns which define the period of time covered by an invoice or credit note. If the thing you
|
238
|
+
# are selling is a one-off, you can omit these columns or leave them as +NULL+. However, if there is any sort
|
239
|
+
# of duration associated with an invoice/credit note (e.g. charges incurred during a particular month, or
|
240
|
+
# an annual subscription, or a validity period of a license, etc.), please store that period here. It's
|
241
|
+
# important for accounting purposes. (For +Payment+ objects it usually makes most sense to just leave these
|
242
|
+
# as +NULL+.)
|
243
|
+
#
|
244
|
+
# +uuid+::
|
245
|
+
# A Universally Unique Identifier (UUID)[http://en.wikipedia.org/wiki/UUID] string for this invoice, credit
|
246
|
+
# note or payment. It may seem unnecessary now, but may help you to keep track of your data later on as
|
247
|
+
# your system grows. If you have the +uuid+ gem installed and this column is present, a UUID is automatically
|
248
|
+
# generated when you create a new ledger item.
|
249
|
+
#
|
250
|
+
# +due_date+::
|
251
|
+
# The date at which the invoice or credit note is due for payment. +nil+ on +Payment+ records.
|
252
|
+
#
|
253
|
+
# +created_at+, +updated_at+::
|
254
|
+
# The standard ActiveRecord datetime columns for recording when an object was created and last changed.
|
255
|
+
# The values are not directly used at the moment, but it's useful information in case you need to track down
|
256
|
+
# a particular transaction sometime; and ActiveRecord manages them for you anyway.
|
257
|
+
#
|
258
|
+
#
|
259
|
+
# == Generated methods
|
260
|
+
#
|
261
|
+
# In return for providing +LedgerItem+ with all the required information as documented above, you are given
|
262
|
+
# a number of class and instance methods which you will find useful sooner or later. In addition to those
|
263
|
+
# documented in this module (instance methods) and <tt>Invoicing::LedgerItem::ClassMethods</tt>
|
264
|
+
# (class methods), the following methods are generated dynamically:
|
265
|
+
#
|
266
|
+
# +sent_by+:: Named scope which takes a person/company ID and matches all ledger items whose
|
267
|
+
# +sender_id+ matches that value.
|
268
|
+
# +received_by+:: Named scope which takes a person/company ID and matches all ledger items whose
|
269
|
+
# +recipient_id+ matches that value.
|
270
|
+
# +sent_or_received_by+:: Union of +sent_by+ and +received_by+.
|
271
|
+
# +in_effect+:: Named scope which matches all closed invoices/credit notes (not open or cancelled)
|
272
|
+
# and all cleared payments (not pending or failed). You probably want to use this
|
273
|
+
# quite often, for all reporting purposes.
|
274
|
+
# +open_or_pending+:: Named scope which matches all open invoices/credit notes and all pending
|
275
|
+
# payments.
|
276
|
+
# +due_at+:: Named scope which takes a +DateTime+ argument and matches all ledger items whose
|
277
|
+
# +due_date+ value is either +NULL+ or is not after the given time. For example,
|
278
|
+
# you could run <tt>LedgerItem.due_at(Time.now).account_summaries</tt>
|
279
|
+
# once a day and process payment for all accounts whose balance is not zero.
|
280
|
+
# +sorted+:: Named scope which takes a column name as documented above (even if it has been
|
281
|
+
# renamed), and sorts the query by that column. If the column does not exist,
|
282
|
+
# silently falls back to sorting by the primary key.
|
283
|
+
# +exclude_empty_invoices+:: Named scope which excludes any invoices or credit notes which do not
|
284
|
+
# have any associated line items (payments without line items are
|
285
|
+
# included though). If you're chaining scopes it would be advantageous
|
286
|
+
# to put this one close to the beginning of your scope chain.
|
287
|
+
module LedgerItem
|
288
|
+
|
289
|
+
module ActMethods
|
290
|
+
# Declares that the current class is a model for ledger items (i.e. invoices, credit notes and
|
291
|
+
# payment notes).
|
292
|
+
#
|
293
|
+
# This method accepts a hash of options, all of which are optional:
|
294
|
+
# <tt>:subtype</tt>:: One of <tt>:invoice</tt>, <tt>:credit_note</tt> or <tt>:payment</tt>.
|
295
|
+
#
|
296
|
+
# Also, the name of any attribute or method required by +LedgerItem+ (as documented on the
|
297
|
+
# +LedgerItem+ module) may be used as an option, with the value being the name under which
|
298
|
+
# that particular method or attribute can be found. This allows you to use names other than
|
299
|
+
# the defaults. For example, if your database column storing the invoice value is called
|
300
|
+
# +gross_amount+ instead of +total_amount+:
|
301
|
+
#
|
302
|
+
# acts_as_ledger_item :total_amount => :gross_amount
|
303
|
+
def acts_as_ledger_item(*args)
|
304
|
+
Invoicing::ClassInfo.acts_as(Invoicing::LedgerItem, self, args)
|
305
|
+
|
306
|
+
info = ledger_item_class_info
|
307
|
+
return unless info.previous_info.nil? # Called for the first time?
|
308
|
+
|
309
|
+
before_validation :calculate_total_amount
|
310
|
+
|
311
|
+
# Set the 'amount' columns to act as currency values
|
312
|
+
acts_as_currency_value(info.method(:total_amount), info.method(:tax_amount),
|
313
|
+
:currency => info.method(:currency))
|
314
|
+
|
315
|
+
extend Invoicing::FindSubclasses
|
316
|
+
include Invoicing::LedgerItem::RenderHTML
|
317
|
+
include Invoicing::LedgerItem::RenderUBL
|
318
|
+
|
319
|
+
# Dynamically created named scopes
|
320
|
+
named_scope :sent_by, lambda{ |sender_id|
|
321
|
+
{ :conditions => {info.method(:sender_id) => sender_id} }
|
322
|
+
}
|
323
|
+
|
324
|
+
named_scope :received_by, lambda{ |recipient_id|
|
325
|
+
{ :conditions => {info.method(:recipient_id) => recipient_id} }
|
326
|
+
}
|
327
|
+
|
328
|
+
named_scope :sent_or_received_by, lambda{ |sender_or_recipient_id|
|
329
|
+
sender_col = connection.quote_column_name(info.method(:sender_id))
|
330
|
+
recipient_col = connection.quote_column_name(info.method(:recipient_id))
|
331
|
+
{ :conditions => ["#{sender_col} = ? OR #{recipient_col} = ?",
|
332
|
+
sender_or_recipient_id, sender_or_recipient_id] }
|
333
|
+
}
|
334
|
+
|
335
|
+
named_scope :in_effect, :conditions => {info.method(:status) => ['closed', 'cleared']}
|
336
|
+
|
337
|
+
named_scope :open_or_pending, :conditions => {info.method(:status) => ['open', 'pending']}
|
338
|
+
|
339
|
+
named_scope :due_at, lambda{ |date|
|
340
|
+
due_date = connection.quote_column_name(info.method(:due_date))
|
341
|
+
{:conditions => ["#{due_date} <= ? OR #{due_date} IS NULL", date]}
|
342
|
+
}
|
343
|
+
|
344
|
+
named_scope :sorted, lambda{|column|
|
345
|
+
column = ledger_item_class_info.method(column).to_s
|
346
|
+
if column_names.include?(column)
|
347
|
+
{:order => "#{connection.quote_column_name(column)}, #{connection.quote_column_name(primary_key)}"}
|
348
|
+
else
|
349
|
+
{:order => connection.quote_column_name(primary_key)}
|
350
|
+
end
|
351
|
+
}
|
352
|
+
|
353
|
+
named_scope :exclude_empty_invoices, lambda{
|
354
|
+
line_items_assoc_id = info.method(:line_items).to_sym
|
355
|
+
line_items_refl = reflections[line_items_assoc_id]
|
356
|
+
line_items_table = line_items_refl.quoted_table_name
|
357
|
+
|
358
|
+
# e.g. `ledger_items`.`id`
|
359
|
+
ledger_items_id = quoted_table_name + "." + connection.quote_column_name(primary_key)
|
360
|
+
|
361
|
+
# e.g. `line_items`.`id`
|
362
|
+
line_items_id = line_items_table + "." +
|
363
|
+
connection.quote_column_name(line_items_refl.klass.primary_key)
|
364
|
+
|
365
|
+
# e.g. `line_items`.`ledger_item_id`
|
366
|
+
ledger_item_foreign_key = line_items_table + "." + connection.quote_column_name(
|
367
|
+
line_items_refl.klass.send(:line_item_class_info).method(:ledger_item_id))
|
368
|
+
|
369
|
+
payment_classes = select_matching_subclasses(:is_payment, true).map{|c| c.name}
|
370
|
+
is_payment_class = merge_conditions({info.method(:type) => payment_classes})
|
371
|
+
|
372
|
+
subquery = construct_finder_sql(
|
373
|
+
:select => "#{quoted_table_name}.*, COUNT(#{line_items_id}) AS number_of_line_items",
|
374
|
+
:joins => "LEFT JOIN #{line_items_table} ON #{ledger_item_foreign_key} = #{ledger_items_id}",
|
375
|
+
:group => Invoicing::ConnectionAdapterExt.group_by_all_columns(self)
|
376
|
+
)
|
377
|
+
|
378
|
+
{:from => "(#{subquery}) AS #{quoted_table_name}",
|
379
|
+
:conditions => "number_of_line_items > 0 OR #{is_payment_class}"}
|
380
|
+
}
|
381
|
+
end # def acts_as_ledger_item
|
382
|
+
|
383
|
+
# Synonym for <tt>acts_as_ledger_item :subtype => :invoice</tt>. All options other than
|
384
|
+
# <tt>:subtype</tt> are passed on to +acts_as_ledger_item+. You should apply
|
385
|
+
# +acts_as_invoice+ only to a model which is a subclass of an +acts_as_ledger_item+ type.
|
386
|
+
def acts_as_invoice(options={})
|
387
|
+
acts_as_ledger_item(options.clone.update({:subtype => :invoice}))
|
388
|
+
end
|
389
|
+
|
390
|
+
# Synonym for <tt>acts_as_ledger_item :subtype => :credit_note</tt>. All options other than
|
391
|
+
# <tt>:subtype</tt> are passed on to +acts_as_ledger_item+. You should apply
|
392
|
+
# +acts_as_credit_note+ only to a model which is a subclass of an +acts_as_ledger_item+ type.
|
393
|
+
def acts_as_credit_note(options={})
|
394
|
+
acts_as_ledger_item(options.clone.update({:subtype => :credit_note}))
|
395
|
+
end
|
396
|
+
|
397
|
+
# Synonym for <tt>acts_as_ledger_item :subtype => :payment</tt>. All options other than
|
398
|
+
# <tt>:subtype</tt> are passed on to +acts_as_ledger_item+. You should apply
|
399
|
+
# +acts_as_payment+ only to a model which is a subclass of an +acts_as_ledger_item+ type.
|
400
|
+
def acts_as_payment(options={})
|
401
|
+
acts_as_ledger_item(options.clone.update({:subtype => :payment}))
|
402
|
+
end
|
403
|
+
end # module ActMethods
|
404
|
+
|
405
|
+
# Overrides the default constructor of <tt>ActiveRecord::Base</tt> when +acts_as_ledger_item+
|
406
|
+
# is called. If the +uuid+ gem is installed, this constructor creates a new UUID and assigns
|
407
|
+
# it to the +uuid+ property when a new ledger item model object is created.
|
408
|
+
def initialize(*args)
|
409
|
+
super
|
410
|
+
# Initialise uuid attribute if possible
|
411
|
+
info = ledger_item_class_info
|
412
|
+
if self.has_attribute?(info.method(:uuid)) && info.uuid_generator
|
413
|
+
write_attribute(info.method(:uuid), info.uuid_generator.generate)
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
# Calculate sum of net_amount and tax_amount across all line items, and assign it to total_amount;
|
418
|
+
# calculate sum of tax_amount across all line items, and assign it to tax_amount.
|
419
|
+
# Called automatically as a +before_validation+ callback.
|
420
|
+
def calculate_total_amount
|
421
|
+
net_total = tax_total = BigDecimal('0')
|
422
|
+
line_items = ledger_item_class_info.get(self, :line_items)
|
423
|
+
|
424
|
+
line_items.each do |line|
|
425
|
+
info = line.send(:line_item_class_info)
|
426
|
+
|
427
|
+
# Make sure ledger_item association is assigned -- the CurrencyValue
|
428
|
+
# getters depend on it to fetch the currency
|
429
|
+
info.set(line, :ledger_item, self)
|
430
|
+
net_amount = info.get(line, :net_amount)
|
431
|
+
tax_amount = info.get(line, :tax_amount)
|
432
|
+
net_total += net_amount unless net_amount.nil?
|
433
|
+
tax_total += tax_amount unless tax_amount.nil?
|
434
|
+
end
|
435
|
+
|
436
|
+
ledger_item_class_info.set(self, :total_amount, net_total + tax_total)
|
437
|
+
ledger_item_class_info.set(self, :tax_amount, tax_total)
|
438
|
+
end
|
439
|
+
|
440
|
+
# We don't actually implement anything using +method_missing+ at the moment, but use it to
|
441
|
+
# generate slightly more useful error messages in certain cases.
|
442
|
+
def method_missing(method_id, *args)
|
443
|
+
method_name = method_id.to_s
|
444
|
+
if ['line_items', ledger_item_class_info.method(:line_items)].include? method_name
|
445
|
+
raise RuntimeError, "You need to define an association like 'has_many :line_items' on #{self.class.name}. If you " +
|
446
|
+
"have defined the association with a different name, pass the option :line_items => :your_association_name to " +
|
447
|
+
"acts_as_ledger_item."
|
448
|
+
else
|
449
|
+
super
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
# The difference +total_amount+ minus +tax_amount+.
|
454
|
+
def net_amount
|
455
|
+
total_amount = ledger_item_class_info.get(self, :total_amount)
|
456
|
+
tax_amount = ledger_item_class_info.get(self, :tax_amount)
|
457
|
+
(total_amount && tax_amount) ? (total_amount - tax_amount) : nil
|
458
|
+
end
|
459
|
+
|
460
|
+
# +net_amount+ formatted in human-readable form using the ledger item's currency.
|
461
|
+
def net_amount_formatted
|
462
|
+
format_currency_value(net_amount)
|
463
|
+
end
|
464
|
+
|
465
|
+
|
466
|
+
# You must overwrite this method in subclasses of +Invoice+, +CreditNote+ and +Payment+ so that it returns
|
467
|
+
# details of the party sending the document. See +sender_id+ above for a detailed interpretation of
|
468
|
+
# sender and receiver.
|
469
|
+
#
|
470
|
+
# The methods +sender_details+ and +recipient_details+ are required to return hashes
|
471
|
+
# containing details about the sender and recipient of an invoice, credit note or payment. The reason we
|
472
|
+
# do this is that you probably already have your own system for handling users, customers and their personal
|
473
|
+
# or business details, and this framework shouldn't require you to change any of that.
|
474
|
+
#
|
475
|
+
# The invoicing framework currently uses these details only for rendering invoices and credit notes, but
|
476
|
+
# in future it may serve more advanced purposes, such as determining which tax rate to apply for overseas
|
477
|
+
# customers.
|
478
|
+
#
|
479
|
+
# In the hash returned by +sender_details+ and +recipient_details+, the following keys are recognised --
|
480
|
+
# please fill in as many as possible:
|
481
|
+
# <tt>:is_self</tt>:: +true+ if these details refer to yourself, i.e. the person or organsiation who owns/operates
|
482
|
+
# this application. +false+ if these details refer to any other party.
|
483
|
+
# <tt>:name</tt>:: The name of the person or organisation whose billing address is defined below.
|
484
|
+
# <tt>:contact_name</tt>:: The name of a person/department within the organisation named by <tt>:name</tt>.
|
485
|
+
# <tt>:address</tt>:: The body of the billing address (not including city, postcode, state and country); may be
|
486
|
+
# a multi-line string, with lines separated by '\n' line breaks.
|
487
|
+
# <tt>:city</tt>:: The name of the city or town in the billing address.
|
488
|
+
# <tt>:state</tt>:: The state/region/province/county of the billing address as appropriate.
|
489
|
+
# <tt>:postal_code</tt>:: The postal code of the billing address (e.g. ZIP code in the US).
|
490
|
+
# <tt>:country</tt>:: The billing address country (human-readable).
|
491
|
+
# <tt>:country_code</tt>:: The two-letter country code of the billing address, according to
|
492
|
+
# ISO-3166-1[http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2].
|
493
|
+
# <tt>:tax_number</tt>:: The Value Added Tax registration code of this person or organisation, if they have
|
494
|
+
# one, preferably including the country identifier at the beginning. This is important for
|
495
|
+
# transactions within the European Union.
|
496
|
+
def sender_details
|
497
|
+
raise 'overwrite this method'
|
498
|
+
end
|
499
|
+
|
500
|
+
# You must overwrite this method in subclasses of +Invoice+, +CreditNote+ and +Payment+ so that it returns
|
501
|
+
# details of the party receiving the document. See +recipient_id+ above for a detailed interpretation of
|
502
|
+
# sender and receiver. See +sender_details+ for a list of fields to return in the hash.
|
503
|
+
def recipient_details
|
504
|
+
raise 'overwrite this method'
|
505
|
+
end
|
506
|
+
|
507
|
+
# Returns +true+ if this document was sent by the user with ID +user_id+. If the argument is +nil+
|
508
|
+
# (indicating yourself), this also returns +true+ if <tt>sender_details[:is_self]</tt>.
|
509
|
+
def sent_by?(user_id)
|
510
|
+
(ledger_item_class_info.get(self, :sender_id) == user_id) ||
|
511
|
+
!!(user_id.nil? && ledger_item_class_info.get(self, :sender_details)[:is_self])
|
512
|
+
end
|
513
|
+
|
514
|
+
# Returns +true+ if this document was received by the user with ID +user_id+. If the argument is +nil+
|
515
|
+
# (indicating yourself), this also returns +true+ if <tt>recipient_details[:is_self]</tt>.
|
516
|
+
def received_by?(user_id)
|
517
|
+
(ledger_item_class_info.get(self, :recipient_id) == user_id) ||
|
518
|
+
!!(user_id.nil? && ledger_item_class_info.get(self, :recipient_details)[:is_self])
|
519
|
+
end
|
520
|
+
|
521
|
+
# Returns a boolean which specifies whether this transaction should be recorded as a debit (+true+)
|
522
|
+
# or a credit (+false+) on a particular ledger. Unless you know what you are doing, you probably
|
523
|
+
# do not need to touch this method.
|
524
|
+
#
|
525
|
+
# It takes an argument +self_id+, which should be equal to either +sender_id+ or +recipient_id+ of this
|
526
|
+
# object, and which determines from which perspective the account is viewed. The default behaviour is:
|
527
|
+
# * A sent invoice (<tt>self_id == sender_id</tt>) is a debit since it increases the recipient's
|
528
|
+
# liability; a sent credit note decreases the recipient's liability with a negative-valued
|
529
|
+
# debit; a sent payment receipt is a positive-valued credit and thus decreases the recipient's
|
530
|
+
# liability.
|
531
|
+
# * A received invoice (<tt>self_id == recipient_id</tt>) is a credit because it increases your own
|
532
|
+
# liability; a received credit note decreases your own liability with a negative-valued credit;
|
533
|
+
# a received payment receipt is a positive-valued debit and thus decreases your own liability.
|
534
|
+
#
|
535
|
+
# Note that accounting practices differ with regard to credit notes: some think that a sent
|
536
|
+
# credit note should be recorded as a positive credit (hence the name 'credit note'); others
|
537
|
+
# prefer to use a negative debit. We chose the latter because it allows you to calculate the
|
538
|
+
# total sale volume on an account simply by adding up all the debits. If there is enough demand
|
539
|
+
# for the positive-credit model, we may add support for it sometime in future.
|
540
|
+
def debit?(self_id)
|
541
|
+
sender_is_self = sent_by?(self_id)
|
542
|
+
recipient_is_self = received_by?(self_id)
|
543
|
+
raise ArgumentError, "self_id #{self_id.inspect} is neither sender nor recipient" unless sender_is_self || recipient_is_self
|
544
|
+
raise ArgumentError, "self_id #{self_id.inspect} is both sender and recipient" if sender_is_self && recipient_is_self
|
545
|
+
self.class.debit_when_sent_by_self ? sender_is_self : recipient_is_self
|
546
|
+
end
|
547
|
+
|
548
|
+
|
549
|
+
module ClassMethods
|
550
|
+
# Returns +true+ if this type of ledger item should be recorded as a debit when the party
|
551
|
+
# viewing the account is the sender of the document, and recorded as a credit when
|
552
|
+
# the party viewing the account is the recipient. Returns +false+ if those roles are
|
553
|
+
# reversed. This method implements default behaviour for invoices, credit notes and
|
554
|
+
# payments (see <tt>Invoicing::LedgerItem#debit?</tt>); if you define custom ledger item
|
555
|
+
# subtypes (other than +invoice+, +credit_note+ and +payment+), you should override this
|
556
|
+
# method accordingly in those subclasses.
|
557
|
+
def debit_when_sent_by_self
|
558
|
+
case ledger_item_class_info.subtype
|
559
|
+
when :invoice then true
|
560
|
+
when :credit_note then true
|
561
|
+
when :payment then false
|
562
|
+
else nil
|
563
|
+
end
|
564
|
+
end
|
565
|
+
|
566
|
+
# Returns +true+ if this type of ledger item is a +invoice+ subtype, and +false+ otherwise.
|
567
|
+
def is_invoice
|
568
|
+
ledger_item_class_info.subtype == :invoice
|
569
|
+
end
|
570
|
+
|
571
|
+
# Returns +true+ if this type of ledger item is a +credit_note+ subtype, and +false+ otherwise.
|
572
|
+
def is_credit_note
|
573
|
+
ledger_item_class_info.subtype == :credit_note
|
574
|
+
end
|
575
|
+
|
576
|
+
# Returns +true+ if this type of ledger item is a +payment+ subtype, and +false+ otherwise.
|
577
|
+
def is_payment
|
578
|
+
ledger_item_class_info.subtype == :payment
|
579
|
+
end
|
580
|
+
|
581
|
+
# Returns a summary of the customer or supplier account between two parties identified
|
582
|
+
# by +self_id+ (the party from whose perspective the account is seen, 'you') and +other_id+
|
583
|
+
# ('them', your supplier/customer). The return value is a hash with the following structure:
|
584
|
+
#
|
585
|
+
# { :GBP => { # ISO 4217 currency code for the following figures
|
586
|
+
# :sales => BigDecimal(...), # Sum of sales (invoices sent by self_id)
|
587
|
+
# :purchases => BigDecimal(...), # Sum of purchases (invoices received by self_id)
|
588
|
+
# :sale_receipts => BigDecimal(...), # Sum of payments received from customer
|
589
|
+
# :purchase_payments => BigDecimal(...), # Sum of payments made to supplier
|
590
|
+
# :balance => BigDecimal(...) # sales - purchases - sale_receipts + purchase_payments
|
591
|
+
# },
|
592
|
+
# :USD => { # Another block as above, if the account uses
|
593
|
+
# ... # multiple currencies
|
594
|
+
# }}
|
595
|
+
#
|
596
|
+
# The <tt>:balance</tt> fields indicate any outstanding money owed on the account: the value is
|
597
|
+
# positive if they owe you money, and negative if you owe them money.
|
598
|
+
def account_summary(self_id, other_id)
|
599
|
+
info = ledger_item_class_info
|
600
|
+
conditions = {info.method(:sender_id) => [self_id, other_id],
|
601
|
+
info.method(:recipient_id) => [self_id, other_id]}
|
602
|
+
|
603
|
+
with_scope :find => {:conditions => conditions} do
|
604
|
+
summaries = account_summaries(self_id)
|
605
|
+
summaries[other_id] || {}
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
# Returns a summary account status for all customers or suppliers with which a particular party
|
610
|
+
# has dealings. Takes into account all +closed+ invoices/credit notes and all +cleared+ payments
|
611
|
+
# which have +self_id+ as their +sender_id+ or +recipient_id+. Returns a hash whose keys are the
|
612
|
+
# other party of each account (i.e. the value of +sender_id+ or +recipient_id+ which is not
|
613
|
+
# +self_id+, as an integer), and whose values are again hashes, of the same form as returned by
|
614
|
+
# +account_summary+:
|
615
|
+
#
|
616
|
+
# LedgerItem.account_summaries(1)
|
617
|
+
# # => { 2 => { :USD => { :sales => ... }, :EUR => { :sales => ... } },
|
618
|
+
# # 3 => { :EUR => { :sales => ... } } }
|
619
|
+
#
|
620
|
+
# If you want to further restrict the ledger items taken into account in this calculation (e.g.
|
621
|
+
# include only data from a particular quarter) you can call this method within an ActiveRecord
|
622
|
+
# scope:
|
623
|
+
#
|
624
|
+
# q3_2008 = ['issue_date >= ? AND issue_date < ?', DateTime.parse('2008-07-01'), DateTime.parse('2008-10-01')]
|
625
|
+
# LedgerItem.scoped(:conditions => q3_2008).account_summaries(1)
|
626
|
+
#
|
627
|
+
def account_summaries(self_id)
|
628
|
+
info = ledger_item_class_info
|
629
|
+
ext = Invoicing::ConnectionAdapterExt
|
630
|
+
scope = scope(:find)
|
631
|
+
|
632
|
+
debit_classes = select_matching_subclasses(:debit_when_sent_by_self, true, self.table_name, self.inheritance_column).map{|c| c.name}
|
633
|
+
credit_classes = select_matching_subclasses(:debit_when_sent_by_self, false, self.table_name, self.inheritance_column).map{|c| c.name}
|
634
|
+
debit_when_sent = merge_conditions({info.method(:sender_id) => self_id, info.method(:type) => debit_classes})
|
635
|
+
debit_when_received = merge_conditions({info.method(:recipient_id) => self_id, info.method(:type) => credit_classes})
|
636
|
+
credit_when_sent = merge_conditions({info.method(:sender_id) => self_id, info.method(:type) => credit_classes})
|
637
|
+
credit_when_received = merge_conditions({info.method(:recipient_id) => self_id, info.method(:type) => debit_classes})
|
638
|
+
|
639
|
+
cols = {}
|
640
|
+
[:total_amount, :sender_id, :recipient_id, :status, :currency].each do |col|
|
641
|
+
cols[col] = connection.quote_column_name(info.method(col))
|
642
|
+
end
|
643
|
+
|
644
|
+
sender_is_self = merge_conditions({info.method(:sender_id) => self_id})
|
645
|
+
recipient_is_self = merge_conditions({info.method(:recipient_id) => self_id})
|
646
|
+
other_id_column = ext.conditional_function(sender_is_self, cols[:recipient_id], cols[:sender_id])
|
647
|
+
filter_conditions = "#{cols[:status]} IN ('closed','cleared') AND (#{sender_is_self} OR #{recipient_is_self})"
|
648
|
+
|
649
|
+
|
650
|
+
|
651
|
+
sql = "SELECT #{other_id_column} AS other_id, #{cols[:currency]} AS currency, " +
|
652
|
+
"SUM(#{ext.conditional_function(debit_when_sent, cols[:total_amount], 0)}) AS sales, " +
|
653
|
+
"SUM(#{ext.conditional_function(debit_when_received, cols[:total_amount], 0)}) AS purchase_payments, " +
|
654
|
+
"SUM(#{ext.conditional_function(credit_when_sent, cols[:total_amount], 0)}) AS sale_receipts, " +
|
655
|
+
"SUM(#{ext.conditional_function(credit_when_received, cols[:total_amount], 0)}) AS purchases " +
|
656
|
+
"FROM #{(scope && scope[:from]) || quoted_table_name} "
|
657
|
+
|
658
|
+
# Structure borrowed from ActiveRecord::Base.construct_finder_sql
|
659
|
+
add_joins!(sql, nil, scope)
|
660
|
+
add_conditions!(sql, filter_conditions, scope)
|
661
|
+
|
662
|
+
sql << " GROUP BY other_id, currency"
|
663
|
+
|
664
|
+
add_order!(sql, nil, scope)
|
665
|
+
add_limit!(sql, {}, scope)
|
666
|
+
add_lock!(sql, {}, scope)
|
667
|
+
|
668
|
+
rows = connection.select_all(sql)
|
669
|
+
|
670
|
+
results = {}
|
671
|
+
rows.each do |row|
|
672
|
+
row.symbolize_keys!
|
673
|
+
other_id = row[:other_id].to_i
|
674
|
+
currency = row[:currency].to_sym
|
675
|
+
summary = {:balance => BigDecimal('0')}
|
676
|
+
|
677
|
+
{:sales => 1, :purchases => -1, :sale_receipts => -1, :purchase_payments => 1}.each_pair do |field, factor|
|
678
|
+
summary[field] = BigDecimal(row[field])
|
679
|
+
summary[:balance] += BigDecimal(factor.to_s) * summary[field]
|
680
|
+
end
|
681
|
+
|
682
|
+
results[other_id] ||= {}
|
683
|
+
results[other_id][currency] = summary
|
684
|
+
end
|
685
|
+
|
686
|
+
results
|
687
|
+
end
|
688
|
+
end
|
689
|
+
|
690
|
+
|
691
|
+
# Stores state in the ActiveRecord class object
|
692
|
+
class ClassInfo < Invoicing::ClassInfo::Base #:nodoc:
|
693
|
+
attr_reader :subtype, :uuid_generator
|
694
|
+
|
695
|
+
def initialize(model_class, previous_info, args)
|
696
|
+
super
|
697
|
+
@subtype = all_options[:subtype]
|
698
|
+
|
699
|
+
begin # try to load the UUID gem
|
700
|
+
require 'uuid'
|
701
|
+
@uuid_generator = UUID.new
|
702
|
+
rescue LoadError, NameError # silently ignore if gem not found
|
703
|
+
@uuid_generator = nil
|
704
|
+
end
|
705
|
+
end
|
706
|
+
|
707
|
+
# Allow methods generated by +CurrencyValue+ to be renamed as well
|
708
|
+
def method(name)
|
709
|
+
if name.to_s =~ /^(.*)_formatted$/
|
710
|
+
"#{super($1)}_formatted"
|
711
|
+
else
|
712
|
+
super
|
713
|
+
end
|
714
|
+
end
|
715
|
+
end
|
716
|
+
|
717
|
+
end # module LedgerItem
|
718
|
+
end
|