invoicing 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|