invoicing 0.2.1 → 1.0.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.
- checksums.yaml +7 -0
- data/LICENSE +1 -0
- data/README.md +57 -0
- data/Rakefile +16 -37
- data/lib/invoicing.rb +20 -10
- data/lib/invoicing/cached_record.rb +9 -6
- data/lib/invoicing/class_info.rb +34 -34
- data/lib/invoicing/connection_adapter_ext.rb +4 -4
- data/lib/invoicing/countries/uk.rb +6 -6
- data/lib/invoicing/currency_value.rb +39 -32
- data/lib/invoicing/find_subclasses.rb +40 -15
- data/lib/invoicing/ledger_item.rb +166 -145
- data/lib/invoicing/ledger_item/pdf_generator.rb +108 -0
- data/lib/invoicing/ledger_item/render_html.rb +76 -73
- data/lib/invoicing/ledger_item/render_ubl.rb +37 -35
- data/lib/invoicing/line_item.rb +43 -38
- data/lib/invoicing/price.rb +1 -1
- data/lib/invoicing/tax_rate.rb +3 -6
- data/lib/invoicing/taxable.rb +37 -32
- data/lib/invoicing/time_dependent.rb +40 -40
- data/lib/invoicing/version.rb +4 -4
- data/lib/rails/generators/invoicing/invoicing_generator.rb +14 -0
- data/lib/rails/generators/invoicing/ledger_item/ledger_item_generator.rb +17 -0
- data/lib/rails/generators/invoicing/ledger_item/templates/migration.rb +25 -0
- data/lib/rails/generators/invoicing/ledger_item/templates/model.rb +5 -0
- data/lib/rails/generators/invoicing/line_item/line_item_generator.rb +17 -0
- data/lib/rails/generators/invoicing/line_item/templates/migration.rb +20 -0
- data/lib/rails/generators/invoicing/line_item/templates/model.rb +5 -0
- data/lib/rails/generators/invoicing/tax_rate/tax_rate_generator.rb +17 -0
- data/lib/rails/generators/invoicing/tax_rate/templates/migration.rb +14 -0
- data/lib/rails/generators/invoicing/tax_rate/templates/model.rb +3 -0
- metadata +110 -153
- data.tar.gz.sig +0 -1
- data/History.txt +0 -31
- data/Manifest.txt +0 -62
- data/PostInstall.txt +0 -10
- data/README.rdoc +0 -58
- data/script/console +0 -10
- data/script/destroy +0 -14
- data/script/generate +0 -14
- data/tasks/rcov.rake +0 -4
- data/test/cached_record_test.rb +0 -100
- data/test/class_info_test.rb +0 -253
- data/test/connection_adapter_ext_test.rb +0 -79
- data/test/currency_value_test.rb +0 -209
- data/test/find_subclasses_test.rb +0 -120
- data/test/fixtures/README +0 -7
- data/test/fixtures/cached_record.sql +0 -22
- data/test/fixtures/class_info.sql +0 -28
- data/test/fixtures/currency_value.sql +0 -29
- data/test/fixtures/find_subclasses.sql +0 -43
- data/test/fixtures/ledger_item.sql +0 -39
- data/test/fixtures/line_item.sql +0 -33
- data/test/fixtures/price.sql +0 -4
- data/test/fixtures/tax_rate.sql +0 -4
- data/test/fixtures/taxable.sql +0 -14
- data/test/fixtures/time_dependent.sql +0 -35
- data/test/ledger_item_test.rb +0 -444
- data/test/line_item_test.rb +0 -139
- data/test/models/README +0 -4
- data/test/models/test_subclass_in_another_file.rb +0 -3
- data/test/models/test_subclass_not_in_database.rb +0 -6
- data/test/price_test.rb +0 -9
- data/test/ref-output/creditnote3.html +0 -82
- data/test/ref-output/creditnote3.xml +0 -89
- data/test/ref-output/invoice1.html +0 -93
- data/test/ref-output/invoice1.xml +0 -111
- data/test/ref-output/invoice2.html +0 -86
- data/test/ref-output/invoice2.xml +0 -98
- data/test/ref-output/invoice_null.html +0 -36
- data/test/render_html_test.rb +0 -70
- data/test/render_ubl_test.rb +0 -44
- data/test/setup.rb +0 -37
- data/test/tax_rate_test.rb +0 -9
- data/test/taxable_test.rb +0 -180
- data/test/test_helper.rb +0 -72
- data/test/time_dependent_test.rb +0 -180
- metadata.gz.sig +0 -4
@@ -1,3 +1,5 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
|
1
3
|
module Invoicing
|
2
4
|
# = Input and output of monetary values
|
3
5
|
#
|
@@ -56,9 +58,10 @@ module Invoicing
|
|
56
58
|
# The string returned by a +_formatted+ method is UTF-8 encoded -- remember most currency symbols (except $)
|
57
59
|
# are outside basic 7-bit ASCII.
|
58
60
|
module CurrencyValue
|
59
|
-
|
61
|
+
extend ActiveSupport::Concern
|
62
|
+
|
60
63
|
# Data about currencies, indexed by ISO 4217 code. (Currently a very short list, in need of extending.)
|
61
|
-
# The values are hashes, in which the following keys are recognised:
|
64
|
+
# The values are hashes, in which the following keys are recognised:
|
62
65
|
# <tt>:round</tt>:: Smallest unit of the currency in normal use, to which values are rounded. Default is 0.01.
|
63
66
|
# <tt>:symbol</tt>:: Symbol or string usually used to denote the currency. Encoded as UTF-8. Default is ISO 4217 code.
|
64
67
|
# <tt>:suffix</tt>:: +true+ if the currency symbol appears after the number, +false+ if it appears before. Default +false+.
|
@@ -72,7 +75,7 @@ module Invoicing
|
|
72
75
|
'INR' => {:symbol => "\xE2\x82\xA8"}, # Indian Rupee
|
73
76
|
'JPY' => {:symbol => "\xC2\xA5", :round => 1} # Japanese Yen
|
74
77
|
}
|
75
|
-
|
78
|
+
|
76
79
|
module ActMethods
|
77
80
|
# Declares that the current model object has columns storing monetary amounts. Pass those attribute
|
78
81
|
# names to +acts_as_currency_value+. By default, we try to find an attribute or method called +currency+,
|
@@ -111,26 +114,30 @@ module Invoicing
|
|
111
114
|
# (The example above is actually a real part of +LedgerItem+.)
|
112
115
|
def acts_as_currency_value(*args)
|
113
116
|
Invoicing::ClassInfo.acts_as(Invoicing::CurrencyValue, self, args)
|
114
|
-
|
115
|
-
# Register callback if this is the first time acts_as_currency_value has been called
|
116
|
-
before_save :write_back_currency_values if currency_value_class_info.previous_info.nil?
|
117
117
|
end
|
118
118
|
end
|
119
119
|
|
120
|
+
included do
|
121
|
+
# Register callback if this is the first time acts_as_currency_value has been called
|
122
|
+
before_save :write_back_currency_values, :if => "currency_value_class_info.previous_info.nil?"
|
123
|
+
end
|
124
|
+
|
120
125
|
# Format a numeric monetary value into a human-readable string, in the currency of the
|
121
126
|
# current model object.
|
122
127
|
def format_currency_value(value, options={})
|
123
128
|
currency_value_class_info.format_value(self, value, options)
|
124
129
|
end
|
125
|
-
|
126
|
-
|
130
|
+
|
131
|
+
|
127
132
|
# Called automatically via +before_save+. Writes the result of converting +CurrencyValue+ attributes
|
128
133
|
# back to the actual attributes, so that they are saved in the database. (This doesn't happen in
|
129
134
|
# +convert_currency_values+ to avoid losing the +_before_type_cast+ attribute values.)
|
130
135
|
def write_back_currency_values
|
131
|
-
currency_value_class_info.all_args.each
|
136
|
+
currency_value_class_info.all_args.each do |attr|
|
137
|
+
write_attribute(attr, send(attr))
|
138
|
+
end
|
132
139
|
end
|
133
|
-
|
140
|
+
|
134
141
|
protected :write_back_currency_values
|
135
142
|
|
136
143
|
|
@@ -138,7 +145,7 @@ module Invoicing
|
|
138
145
|
# These methods do not depend on ActiveRecord and can thus also be called externally.
|
139
146
|
module Formatter
|
140
147
|
class << self
|
141
|
-
|
148
|
+
|
142
149
|
# Given the three-letter ISO 4217 code of a currency, returns a hash with useful bits of information:
|
143
150
|
# <tt>:code</tt>:: The ISO 4217 code of the currency.
|
144
151
|
# <tt>:round</tt>:: Smallest unit of the currency in normal use, to which values are rounded. Default is 0.01.
|
@@ -154,15 +161,15 @@ module Invoicing
|
|
154
161
|
info.update(::Invoicing::CurrencyValue::CURRENCIES[code])
|
155
162
|
end
|
156
163
|
options.each_pair {|key, value| info[key] = value if valid_options.include? key }
|
157
|
-
|
164
|
+
|
158
165
|
info[:suffix] ||= (info[:code] == info[:symbol]) && !info[:code].nil?
|
159
166
|
info[:space] ||= info[:suffix]
|
160
167
|
info[:digits] = -Math.log10(info[:round]).floor if info[:digits].nil?
|
161
168
|
info[:digits] = 0 if info[:digits] < 0
|
162
|
-
|
169
|
+
|
163
170
|
info
|
164
171
|
end
|
165
|
-
|
172
|
+
|
166
173
|
# Given the three-letter ISO 4217 code of a currency and a BigDecimal value, returns the
|
167
174
|
# value formatted as an UTF-8 string, ready for human consumption.
|
168
175
|
#
|
@@ -170,17 +177,17 @@ module Invoicing
|
|
170
177
|
# as decimal separator and the comma as thousands separator.
|
171
178
|
def format_value(currency_code, value, options={})
|
172
179
|
info = currency_info(currency_code, options)
|
173
|
-
|
180
|
+
|
174
181
|
negative = false
|
175
182
|
if value < 0
|
176
183
|
negative = true
|
177
184
|
value = -value
|
178
185
|
end
|
179
|
-
|
186
|
+
|
180
187
|
value = "%.#{info[:digits]}f" % value
|
181
188
|
while value.sub!(/(\d+)(\d\d\d)/, '\1,\2'); end
|
182
189
|
value.sub!(/^\-/, '') # avoid displaying minus zero
|
183
|
-
|
190
|
+
|
184
191
|
formatted = if ['', nil].include? info[:symbol]
|
185
192
|
value
|
186
193
|
elsif info[:space]
|
@@ -188,26 +195,26 @@ module Invoicing
|
|
188
195
|
else
|
189
196
|
info[:suffix] ? "#{value}#{info[:symbol]}" : "#{info[:symbol]}#{value}"
|
190
197
|
end
|
191
|
-
|
198
|
+
|
192
199
|
if negative
|
193
200
|
# default is to use proper unicode minus sign
|
194
201
|
formatted = (options[:negative] == :brackets) ? "(#{formatted})" : (
|
195
202
|
(options[:negative] == :hyphen) ? "-#{formatted}" : "\xE2\x88\x92#{formatted}"
|
196
203
|
)
|
197
204
|
end
|
198
|
-
formatted
|
205
|
+
formatted.force_encoding("utf-8")
|
199
206
|
end
|
200
207
|
end
|
201
208
|
end
|
202
209
|
|
203
210
|
|
204
211
|
class ClassInfo < Invoicing::ClassInfo::Base #:nodoc:
|
205
|
-
|
212
|
+
|
206
213
|
def initialize(model_class, previous_info, args)
|
207
214
|
super
|
208
215
|
new_args.each{|attr| generate_attrs(attr)}
|
209
216
|
end
|
210
|
-
|
217
|
+
|
211
218
|
# Generates the getter and setter method for attribute +attr+.
|
212
219
|
def generate_attrs(attr)
|
213
220
|
model_class.class_eval do
|
@@ -215,23 +222,23 @@ module Invoicing
|
|
215
222
|
currency_info = currency_value_class_info.currency_info_for(self)
|
216
223
|
return read_attribute(attr) if currency_info.nil?
|
217
224
|
round_factor = BigDecimal(currency_info[:round].to_s)
|
218
|
-
|
225
|
+
|
219
226
|
value = currency_value_class_info.attr_conversion_input(self, attr)
|
220
227
|
value.nil? ? nil : (value / round_factor).round * round_factor
|
221
228
|
end
|
222
|
-
|
229
|
+
|
223
230
|
define_method("#{attr}=") do |new_value|
|
224
231
|
write_attribute(attr, new_value)
|
225
232
|
end
|
226
|
-
|
233
|
+
|
227
234
|
define_method("#{attr}_formatted") do |*args|
|
228
235
|
options = args.first || {}
|
229
236
|
value_as_float = begin
|
230
|
-
Kernel.Float(send("#{attr}_before_type_cast"))
|
237
|
+
Kernel.Float(send("#{attr}_before_type_cast"))
|
231
238
|
rescue ArgumentError, TypeError
|
232
239
|
nil
|
233
240
|
end
|
234
|
-
|
241
|
+
|
235
242
|
if value_as_float.nil?
|
236
243
|
''
|
237
244
|
else
|
@@ -240,7 +247,7 @@ module Invoicing
|
|
240
247
|
end
|
241
248
|
end
|
242
249
|
end
|
243
|
-
|
250
|
+
|
244
251
|
# Returns the value of the currency code column of +object+, if available; otherwise the
|
245
252
|
# default currency code (set by the <tt>:currency_code</tt> option), if available; +nil+ if all
|
246
253
|
# else fails.
|
@@ -251,12 +258,12 @@ module Invoicing
|
|
251
258
|
all_options[:currency_code]
|
252
259
|
end
|
253
260
|
end
|
254
|
-
|
261
|
+
|
255
262
|
# Returns a hash of information about the currency used by model +object+.
|
256
263
|
def currency_info_for(object)
|
257
264
|
::Invoicing::CurrencyValue::Formatter.currency_info(currency_of(object), all_options)
|
258
265
|
end
|
259
|
-
|
266
|
+
|
260
267
|
# Formats a numeric value as a nice currency string in UTF-8 encoding.
|
261
268
|
# +object+ is the model object carrying the value (used to determine the currency).
|
262
269
|
def format_value(object, value, options={})
|
@@ -267,16 +274,16 @@ module Invoicing
|
|
267
274
|
end
|
268
275
|
::Invoicing::CurrencyValue::Formatter.format_value(currency_of(object), value, options)
|
269
276
|
end
|
270
|
-
|
277
|
+
|
271
278
|
# If other modules have registered callbacks for the event of reading a rounded attribute,
|
272
279
|
# they are executed here. +attr+ is the name of the attribute being read.
|
273
280
|
def attr_conversion_input(object, attr)
|
274
281
|
value = nil
|
275
|
-
|
282
|
+
|
276
283
|
if callback = all_options[:conversion_input]
|
277
284
|
value = object.send(callback, attr)
|
278
285
|
end
|
279
|
-
|
286
|
+
|
280
287
|
unless value
|
281
288
|
raw_value = object.read_attribute(attr)
|
282
289
|
value = BigDecimal.new(raw_value.to_s) unless raw_value.nil?
|
@@ -12,14 +12,14 @@ module Invoicing
|
|
12
12
|
# extend Invoicing::FindSubclasses
|
13
13
|
# def self.needs_refrigeration; false; end
|
14
14
|
# end
|
15
|
-
#
|
15
|
+
#
|
16
16
|
# class Food < Product; end
|
17
17
|
# class Bread < Food; end
|
18
18
|
# class Yoghurt < Food
|
19
19
|
# def self.needs_refrigeration; true; end
|
20
20
|
# end
|
21
21
|
# class GreekYoghurt < Yoghurt; end
|
22
|
-
#
|
22
|
+
#
|
23
23
|
# class Drink < Product; end
|
24
24
|
# class SoftDrink < Drink; end
|
25
25
|
# class Smoothie < Drink
|
@@ -72,11 +72,11 @@ module Invoicing
|
|
72
72
|
# <tt>Class#inherited</tt> method; we use this to gather together a list of subclasses. Of course,
|
73
73
|
# we won't necessarily know about every class in the world which may subclass our class; in
|
74
74
|
# particular, <tt>Class#inherited</tt> won't be called until that subclass is loaded.
|
75
|
-
#
|
75
|
+
#
|
76
76
|
# If you're including the Ruby files with the subclass definitions using +require+, we will learn
|
77
|
-
# about subclasses as soon as they are defined. However, if class loading is delayed until a
|
77
|
+
# about subclasses as soon as they are defined. However, if class loading is delayed until a
|
78
78
|
# class is first used (for example, <tt>ActiveSupport::Dependencies</tt> does this with model
|
79
|
-
# objects in Rails projects), we could run into a situation where we don't yet know about all
|
79
|
+
# objects in Rails projects), we could run into a situation where we don't yet know about all
|
80
80
|
# subclasses used in a project at the point where we need to process a class method condition.
|
81
81
|
# This would cause us to omit some objects we should have found.
|
82
82
|
#
|
@@ -89,7 +89,7 @@ module Invoicing
|
|
89
89
|
# to be on the safe side you can ensure all subclasses are loaded when your application
|
90
90
|
# initialises -- but that's not completely DRY ;-)
|
91
91
|
module FindSubclasses
|
92
|
-
|
92
|
+
|
93
93
|
# Overrides <tt>ActiveRecord::Base.sanitize_sql_hash_for_conditions</tt> since this is the method
|
94
94
|
# used to transform a hash of conditions into an SQL query fragment. This overriding method
|
95
95
|
# searches for class method conditions in the hash and transforms them into a condition on the
|
@@ -103,14 +103,14 @@ module Invoicing
|
|
103
103
|
# {:my_class_method => false} # known_subclasses.reject{|cls| cls.my_class_method }
|
104
104
|
def sanitize_sql_hash_for_conditions(attrs, table_name = quoted_table_name)
|
105
105
|
new_attrs = {}
|
106
|
-
|
106
|
+
|
107
107
|
attrs.each_pair do |attr, value|
|
108
108
|
attr = attr_base = attr.to_s
|
109
109
|
attr_table_name = table_name
|
110
110
|
|
111
111
|
# Extract table name from qualified attribute names
|
112
112
|
attr_table_name, attr_base = attr.split('.', 2) if attr.include?('.')
|
113
|
-
|
113
|
+
|
114
114
|
if columns_hash.include?(attr_base) || ![self.table_name, quoted_table_name].include?(attr_table_name)
|
115
115
|
new_attrs[attr] = value # Condition on a table column, or another table -- pass through unmodified
|
116
116
|
else
|
@@ -125,10 +125,35 @@ module Invoicing
|
|
125
125
|
|
126
126
|
super(new_attrs, table_name)
|
127
127
|
end
|
128
|
-
|
128
|
+
|
129
|
+
def expand_hash_conditions_for_aggregates(attrs)
|
130
|
+
new_attrs = {}
|
131
|
+
|
132
|
+
attrs.each_pair do |attr, value|
|
133
|
+
attr = attr_base = attr.to_s
|
134
|
+
attr_table_name = table_name
|
135
|
+
|
136
|
+
# Extract table name from qualified attribute names
|
137
|
+
attr_table_name, attr_base = attr.split('.', 2) if attr.include?('.')
|
138
|
+
|
139
|
+
if columns_hash.include?(attr_base) || ![self.table_name, quoted_table_name].include?(attr_table_name)
|
140
|
+
new_attrs[attr] = value # Condition on a table column, or another table -- pass through unmodified
|
141
|
+
else
|
142
|
+
begin
|
143
|
+
matching_classes = select_matching_subclasses(attr_base, value)
|
144
|
+
new_attrs["#{self.table_name}.#{inheritance_column}"] = matching_classes.map{|cls| cls.name.to_s}
|
145
|
+
rescue NoMethodError
|
146
|
+
new_attrs[attr] = value # If the class method doesn't exist, fall back to passing condition through unmodified
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
super(new_attrs)
|
152
|
+
end
|
153
|
+
|
129
154
|
# Returns a list of those classes within +known_subclasses+ which match a condition
|
130
155
|
# <tt>method_name => value</tt>. May raise +NoMethodError+ if a class object does not
|
131
|
-
# respond to +method_name+.
|
156
|
+
# respond to +method_name+.
|
132
157
|
def select_matching_subclasses(method_name, value, table = table_name, type_column = inheritance_column)
|
133
158
|
known_subclasses(table, type_column).select do |cls|
|
134
159
|
returned = cls.send(method_name)
|
@@ -139,28 +164,28 @@ module Invoicing
|
|
139
164
|
end
|
140
165
|
end
|
141
166
|
end
|
142
|
-
|
167
|
+
|
143
168
|
# Ruby callback which is invoked when a subclass is created. We use this to build a list of known
|
144
169
|
# subclasses.
|
145
170
|
def inherited(subclass)
|
146
171
|
remember_subclass subclass
|
147
172
|
super
|
148
173
|
end
|
149
|
-
|
174
|
+
|
150
175
|
# Add +subclass+ to the list of know subclasses of this class.
|
151
176
|
def remember_subclass(subclass)
|
152
177
|
@known_subclasses ||= [self]
|
153
178
|
@known_subclasses << subclass unless @known_subclasses.include? subclass
|
154
179
|
self.superclass.remember_subclass(subclass) if self.superclass.respond_to? :remember_subclass
|
155
180
|
end
|
156
|
-
|
181
|
+
|
157
182
|
# Return the list of all known subclasses of this class, if necessary checking the database for
|
158
183
|
# classes which have not yet been loaded.
|
159
184
|
def known_subclasses(table = table_name, type_column = inheritance_column)
|
160
185
|
load_all_subclasses_found_in_database(table, type_column)
|
161
186
|
@known_subclasses ||= [self]
|
162
187
|
end
|
163
|
-
|
188
|
+
|
164
189
|
private
|
165
190
|
# Query the database for all qualified class names found in the +type_column+ column
|
166
191
|
# (called +type+ by default), and check that classes of that name have been loaded by the Ruby
|
@@ -190,4 +215,4 @@ module Invoicing
|
|
190
215
|
end
|
191
216
|
end
|
192
217
|
end
|
193
|
-
end
|
218
|
+
end
|
@@ -1,3 +1,7 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
require "invoicing/ledger_item/render_html"
|
3
|
+
require "invoicing/ledger_item/render_ubl"
|
4
|
+
|
1
5
|
module Invoicing
|
2
6
|
# = Ledger item objects
|
3
7
|
#
|
@@ -285,7 +289,8 @@ module Invoicing
|
|
285
289
|
# included though). If you're chaining scopes it would be advantageous
|
286
290
|
# to put this one close to the beginning of your scope chain.
|
287
291
|
module LedgerItem
|
288
|
-
|
292
|
+
extend ActiveSupport::Concern
|
293
|
+
|
289
294
|
module ActMethods
|
290
295
|
# Declares that the current class is a model for ledger items (i.e. invoices, credit notes and
|
291
296
|
# payment notes).
|
@@ -302,98 +307,33 @@ module Invoicing
|
|
302
307
|
# acts_as_ledger_item :total_amount => :gross_amount
|
303
308
|
def acts_as_ledger_item(*args)
|
304
309
|
Invoicing::ClassInfo.acts_as(Invoicing::LedgerItem, self, args)
|
305
|
-
|
310
|
+
|
306
311
|
info = ledger_item_class_info
|
307
312
|
return unless info.previous_info.nil? # Called for the first time?
|
308
|
-
|
309
|
-
before_validation :calculate_total_amount
|
310
|
-
|
313
|
+
|
311
314
|
# Set the 'amount' columns to act as currency values
|
312
315
|
acts_as_currency_value(info.method(:total_amount), info.method(:tax_amount),
|
313
316
|
:currency => info.method(:currency), :value_for_formatting => :value_for_formatting)
|
314
|
-
|
315
|
-
extend
|
317
|
+
|
318
|
+
extend Invoicing::FindSubclasses
|
316
319
|
include Invoicing::LedgerItem::RenderHTML
|
317
320
|
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
321
|
end # def acts_as_ledger_item
|
382
|
-
|
322
|
+
|
383
323
|
# Synonym for <tt>acts_as_ledger_item :subtype => :invoice</tt>. All options other than
|
384
324
|
# <tt>:subtype</tt> are passed on to +acts_as_ledger_item+. You should apply
|
385
325
|
# +acts_as_invoice+ only to a model which is a subclass of an +acts_as_ledger_item+ type.
|
386
326
|
def acts_as_invoice(options={})
|
387
327
|
acts_as_ledger_item(options.clone.update({:subtype => :invoice}))
|
388
328
|
end
|
389
|
-
|
329
|
+
|
390
330
|
# Synonym for <tt>acts_as_ledger_item :subtype => :credit_note</tt>. All options other than
|
391
331
|
# <tt>:subtype</tt> are passed on to +acts_as_ledger_item+. You should apply
|
392
332
|
# +acts_as_credit_note+ only to a model which is a subclass of an +acts_as_ledger_item+ type.
|
393
333
|
def acts_as_credit_note(options={})
|
394
334
|
acts_as_ledger_item(options.clone.update({:subtype => :credit_note}))
|
395
335
|
end
|
396
|
-
|
336
|
+
|
397
337
|
# Synonym for <tt>acts_as_ledger_item :subtype => :payment</tt>. All options other than
|
398
338
|
# <tt>:subtype</tt> are passed on to +acts_as_ledger_item+. You should apply
|
399
339
|
# +acts_as_payment+ only to a model which is a subclass of an +acts_as_ledger_item+ type.
|
@@ -401,7 +341,73 @@ module Invoicing
|
|
401
341
|
acts_as_ledger_item(options.clone.update({:subtype => :payment}))
|
402
342
|
end
|
403
343
|
end # module ActMethods
|
404
|
-
|
344
|
+
|
345
|
+
included do
|
346
|
+
before_validation :calculate_total_amount
|
347
|
+
|
348
|
+
# Dynamically created named scopes
|
349
|
+
scope :sent_by, lambda { |sender_id|
|
350
|
+
where(ledger_item_class_info.method(:sender_id) => sender_id)
|
351
|
+
}
|
352
|
+
|
353
|
+
scope :received_by, lambda {|recipient_id|
|
354
|
+
where(ledger_item_class_info.method(:recipient_id) => recipient_id)
|
355
|
+
}
|
356
|
+
|
357
|
+
scope :sent_or_received_by, lambda { |sender_or_recipient_id|
|
358
|
+
sender_col = connection.quote_column_name(ledger_item_class_info.method(:sender_id))
|
359
|
+
recipient_col = connection.quote_column_name(ledger_item_class_info.method(:recipient_id))
|
360
|
+
|
361
|
+
where(["#{sender_col} = ? OR #{recipient_col} = ?",
|
362
|
+
sender_or_recipient_id, sender_or_recipient_id])
|
363
|
+
}
|
364
|
+
|
365
|
+
scope :in_effect, lambda {
|
366
|
+
where(ledger_item_class_info.method(:status) => ['closed', 'cleared'])
|
367
|
+
}
|
368
|
+
|
369
|
+
scope :open_or_pending, lambda {
|
370
|
+
where(ledger_item_class_info.method(:status) => ['open', 'pending'])
|
371
|
+
}
|
372
|
+
|
373
|
+
scope :due_at, lambda { |date|
|
374
|
+
due_date = connection.quote_column_name(ledger_item_class_info.method(:due_date))
|
375
|
+
where(["#{due_date} <= ? OR #{due_date} IS NULL", date])
|
376
|
+
}
|
377
|
+
|
378
|
+
scope :sorted, lambda { |column|
|
379
|
+
column = ledger_item_class_info.method(column).to_s
|
380
|
+
if column_names.include?(column)
|
381
|
+
order("#{connection.quote_column_name(column)}, #{connection.quote_column_name(primary_key)}")
|
382
|
+
else
|
383
|
+
order(connection.quote_column_name(primary_key))
|
384
|
+
end
|
385
|
+
}
|
386
|
+
|
387
|
+
scope :exclude_empty_invoices, lambda {
|
388
|
+
line_items_assoc_id = ledger_item_class_info.method(:line_items).to_sym
|
389
|
+
line_items_refl = reflections[line_items_assoc_id]
|
390
|
+
line_items_table = line_items_refl.quoted_table_name
|
391
|
+
|
392
|
+
# e.g. `ledger_items`.`id`
|
393
|
+
ledger_items_id = quoted_table_name + "." + connection.quote_column_name(primary_key)
|
394
|
+
|
395
|
+
# e.g. `line_items`.`id`
|
396
|
+
line_items_id = line_items_table + "." +
|
397
|
+
connection.quote_column_name(line_items_refl.klass.primary_key)
|
398
|
+
|
399
|
+
# e.g. `line_items`.`ledger_item_id`
|
400
|
+
ledger_item_foreign_key = line_items_table + "." + connection.quote_column_name(
|
401
|
+
line_items_refl.klass.send(:line_item_class_info).method(:ledger_item_id))
|
402
|
+
|
403
|
+
payment_classes = select_matching_subclasses(:is_payment, true).map{|c| c.name}
|
404
|
+
is_payment_class = merge_conditions({ledger_item_class_info.method(:type) => payment_classes})
|
405
|
+
|
406
|
+
joins("LEFT JOIN #{line_items_table} ON #{ledger_item_foreign_key} = #{ledger_items_id}").
|
407
|
+
where("(#{ledger_item_foreign_key} IS NULL) OR #{is_payment_class}")
|
408
|
+
}
|
409
|
+
end
|
410
|
+
|
405
411
|
# Overrides the default constructor of <tt>ActiveRecord::Base</tt> when +acts_as_ledger_item+
|
406
412
|
# is called. If the +uuid+ gem is installed, this constructor creates a new UUID and assigns
|
407
413
|
# it to the +uuid+ property when a new ledger item model object is created.
|
@@ -413,7 +419,7 @@ module Invoicing
|
|
413
419
|
write_attribute(info.method(:uuid), info.uuid_generator.generate)
|
414
420
|
end
|
415
421
|
end
|
416
|
-
|
422
|
+
|
417
423
|
# Calculate sum of net_amount and tax_amount across all line items, and assign it to total_amount;
|
418
424
|
# calculate sum of tax_amount across all line items, and assign it to tax_amount.
|
419
425
|
# Called automatically as a +before_validation+ callback. If the LedgerItem subtype is +payment+
|
@@ -423,25 +429,26 @@ module Invoicing
|
|
423
429
|
return if self.class.is_payment && line_items.empty?
|
424
430
|
|
425
431
|
net_total = tax_total = BigDecimal('0')
|
426
|
-
|
432
|
+
|
427
433
|
line_items.each do |line|
|
428
434
|
info = line.send(:line_item_class_info)
|
429
|
-
|
435
|
+
|
430
436
|
# Make sure ledger_item association is assigned -- the CurrencyValue
|
431
437
|
# getters depend on it to fetch the currency
|
432
438
|
info.set(line, :ledger_item, self)
|
433
439
|
line.valid? # Ensure any before_validation hooks are called
|
434
|
-
|
440
|
+
|
435
441
|
net_amount = info.get(line, :net_amount)
|
436
442
|
tax_amount = info.get(line, :tax_amount)
|
437
443
|
net_total += net_amount unless net_amount.nil?
|
438
444
|
tax_total += tax_amount unless tax_amount.nil?
|
439
445
|
end
|
440
|
-
|
446
|
+
|
441
447
|
ledger_item_class_info.set(self, :total_amount, net_total + tax_total)
|
442
448
|
ledger_item_class_info.set(self, :tax_amount, tax_total)
|
449
|
+
return net_total
|
443
450
|
end
|
444
|
-
|
451
|
+
|
445
452
|
# We don't actually implement anything using +method_missing+ at the moment, but use it to
|
446
453
|
# generate slightly more useful error messages in certain cases.
|
447
454
|
def method_missing(method_id, *args)
|
@@ -461,13 +468,13 @@ module Invoicing
|
|
461
468
|
tax_amount = ledger_item_class_info.get(self, :tax_amount)
|
462
469
|
(total_amount && tax_amount) ? (total_amount - tax_amount) : nil
|
463
470
|
end
|
464
|
-
|
471
|
+
|
465
472
|
# +net_amount+ formatted in human-readable form using the ledger item's currency.
|
466
473
|
def net_amount_formatted
|
467
474
|
format_currency_value(net_amount)
|
468
475
|
end
|
469
|
-
|
470
|
-
|
476
|
+
|
477
|
+
|
471
478
|
# You must overwrite this method in subclasses of +Invoice+, +CreditNote+ and +Payment+ so that it returns
|
472
479
|
# details of the party sending the document. See +sender_id+ above for a detailed interpretation of
|
473
480
|
# sender and receiver.
|
@@ -501,28 +508,28 @@ module Invoicing
|
|
501
508
|
def sender_details
|
502
509
|
raise 'overwrite this method'
|
503
510
|
end
|
504
|
-
|
511
|
+
|
505
512
|
# You must overwrite this method in subclasses of +Invoice+, +CreditNote+ and +Payment+ so that it returns
|
506
513
|
# details of the party receiving the document. See +recipient_id+ above for a detailed interpretation of
|
507
514
|
# sender and receiver. See +sender_details+ for a list of fields to return in the hash.
|
508
515
|
def recipient_details
|
509
516
|
raise 'overwrite this method'
|
510
517
|
end
|
511
|
-
|
518
|
+
|
512
519
|
# Returns +true+ if this document was sent by the user with ID +user_id+. If the argument is +nil+
|
513
520
|
# (indicating yourself), this also returns +true+ if <tt>sender_details[:is_self]</tt>.
|
514
521
|
def sent_by?(user_id)
|
515
522
|
(ledger_item_class_info.get(self, :sender_id) == user_id) ||
|
516
523
|
!!(user_id.nil? && ledger_item_class_info.get(self, :sender_details)[:is_self])
|
517
524
|
end
|
518
|
-
|
525
|
+
|
519
526
|
# Returns +true+ if this document was received by the user with ID +user_id+. If the argument is +nil+
|
520
527
|
# (indicating yourself), this also returns +true+ if <tt>recipient_details[:is_self]</tt>.
|
521
528
|
def received_by?(user_id)
|
522
529
|
(ledger_item_class_info.get(self, :recipient_id) == user_id) ||
|
523
530
|
!!(user_id.nil? && ledger_item_class_info.get(self, :recipient_details)[:is_self])
|
524
531
|
end
|
525
|
-
|
532
|
+
|
526
533
|
# Returns a boolean which specifies whether this transaction should be recorded as a debit (+true+)
|
527
534
|
# or a credit (+false+) on a particular ledger. Unless you know what you are doing, you probably
|
528
535
|
# do not need to touch this method.
|
@@ -560,8 +567,8 @@ module Invoicing
|
|
560
567
|
value = -value if (options[:credit] == :negative) && !debit?(options[:self_id])
|
561
568
|
value
|
562
569
|
end
|
563
|
-
|
564
|
-
|
570
|
+
|
571
|
+
|
565
572
|
module ClassMethods
|
566
573
|
# Returns +true+ if this type of ledger item should be recorded as a debit when the party
|
567
574
|
# viewing the account is the sender of the document, and recorded as a credit when
|
@@ -578,22 +585,22 @@ module Invoicing
|
|
578
585
|
else nil
|
579
586
|
end
|
580
587
|
end
|
581
|
-
|
588
|
+
|
582
589
|
# Returns +true+ if this type of ledger item is a +invoice+ subtype, and +false+ otherwise.
|
583
590
|
def is_invoice
|
584
591
|
ledger_item_class_info.subtype == :invoice
|
585
592
|
end
|
586
|
-
|
593
|
+
|
587
594
|
# Returns +true+ if this type of ledger item is a +credit_note+ subtype, and +false+ otherwise.
|
588
595
|
def is_credit_note
|
589
596
|
ledger_item_class_info.subtype == :credit_note
|
590
597
|
end
|
591
|
-
|
598
|
+
|
592
599
|
# Returns +true+ if this type of ledger item is a +payment+ subtype, and +false+ otherwise.
|
593
600
|
def is_payment
|
594
601
|
ledger_item_class_info.subtype == :payment
|
595
602
|
end
|
596
|
-
|
603
|
+
|
597
604
|
# Returns a summary of the customer or supplier account between two parties identified
|
598
605
|
# by +self_id+ (the party from whose perspective the account is seen, 'you') and +other_id+
|
599
606
|
# ('them', your supplier/customer). The return value is a hash with ISO 4217 currency codes
|
@@ -627,7 +634,7 @@ module Invoicing
|
|
627
634
|
info = ledger_item_class_info
|
628
635
|
self_id = self_id.to_i
|
629
636
|
other_id = [nil, ''].include?(other_id) ? nil : other_id.to_i
|
630
|
-
|
637
|
+
|
631
638
|
if other_id.nil?
|
632
639
|
result = {}
|
633
640
|
# Sum over all others, grouped by currency
|
@@ -641,16 +648,13 @@ module Invoicing
|
|
641
648
|
end
|
642
649
|
end
|
643
650
|
result
|
644
|
-
|
645
651
|
else
|
646
652
|
conditions = {info.method(:sender_id) => [self_id, other_id],
|
647
653
|
info.method(:recipient_id) => [self_id, other_id]}
|
648
|
-
|
649
|
-
account_summaries(self_id, options)[other_id] || {}
|
650
|
-
end
|
654
|
+
where(conditions).account_summaries(self_id, options)[other_id] || {}
|
651
655
|
end
|
652
656
|
end
|
653
|
-
|
657
|
+
|
654
658
|
# Returns a summary account status for all customers or suppliers with which a particular party
|
655
659
|
# has dealings. Takes into account all +closed+ invoices/credit notes and all +cleared+ payments
|
656
660
|
# which have +self_id+ as their +sender_id+ or +recipient_id+. Returns a hash whose keys are the
|
@@ -678,44 +682,38 @@ module Invoicing
|
|
678
682
|
def account_summaries(self_id, options={})
|
679
683
|
info = ledger_item_class_info
|
680
684
|
ext = Invoicing::ConnectionAdapterExt
|
681
|
-
|
682
|
-
|
685
|
+
|
683
686
|
debit_classes = select_matching_subclasses(:debit_when_sent_by_self, true, self.table_name, self.inheritance_column).map{|c| c.name}
|
684
687
|
credit_classes = select_matching_subclasses(:debit_when_sent_by_self, false, self.table_name, self.inheritance_column).map{|c| c.name}
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
688
|
+
|
689
|
+
# rails 3 idiocricies. in case of STI, type of base class is nil. Need special handling
|
690
|
+
debit_when_sent = merge_conditions(inheritance_condition(debit_classes), info.method(:sender_id) => self_id)
|
691
|
+
debit_when_received = merge_conditions(inheritance_condition(credit_classes), info.method(:recipient_id) => self_id)
|
692
|
+
credit_when_sent = merge_conditions(inheritance_condition(credit_classes), info.method(:sender_id) => self_id)
|
693
|
+
credit_when_received = merge_conditions(inheritance_condition(debit_classes), info.method(:recipient_id) => self_id)
|
689
694
|
|
690
695
|
cols = {}
|
691
696
|
[:total_amount, :sender_id, :recipient_id, :status, :currency].each do |col|
|
692
697
|
cols[col] = connection.quote_column_name(info.method(col))
|
693
698
|
end
|
694
|
-
|
699
|
+
|
695
700
|
sender_is_self = merge_conditions({info.method(:sender_id) => self_id})
|
696
701
|
recipient_is_self = merge_conditions({info.method(:recipient_id) => self_id})
|
697
702
|
other_id_column = ext.conditional_function(sender_is_self, cols[:recipient_id], cols[:sender_id])
|
698
|
-
accept_status =
|
703
|
+
accept_status = merge_conditions(info.method(:status) => (options[:with_status] || %w(closed cleared)))
|
699
704
|
filter_conditions = "#{accept_status} AND (#{sender_is_self} OR #{recipient_is_self})"
|
700
705
|
|
701
|
-
sql = "
|
706
|
+
sql = select("#{other_id_column} AS other_id, #{cols[:currency]} AS currency, " +
|
702
707
|
"SUM(#{ext.conditional_function(debit_when_sent, cols[:total_amount], 0)}) AS sales, " +
|
703
708
|
"SUM(#{ext.conditional_function(debit_when_received, cols[:total_amount], 0)}) AS purchase_payments, " +
|
704
709
|
"SUM(#{ext.conditional_function(credit_when_sent, cols[:total_amount], 0)}) AS sale_receipts, " +
|
705
|
-
"SUM(#{ext.conditional_function(credit_when_received, cols[:total_amount], 0)}) AS purchases "
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
sql << " GROUP BY other_id, currency"
|
713
|
-
|
714
|
-
add_order!(sql, nil, scope)
|
715
|
-
add_limit!(sql, {}, scope)
|
716
|
-
add_lock!(sql, {}, scope)
|
717
|
-
|
718
|
-
rows = connection.select_all(sql)
|
710
|
+
"SUM(#{ext.conditional_function(credit_when_received, cols[:total_amount], 0)}) AS purchases ")
|
711
|
+
|
712
|
+
sql = sql.where(filter_conditions)
|
713
|
+
sql = sql.group("other_id, currency")
|
714
|
+
|
715
|
+
# add order, limit, and lock from outside
|
716
|
+
rows = connection.execute(sql.to_sql).to_a
|
719
717
|
|
720
718
|
results = {}
|
721
719
|
rows.each do |row|
|
@@ -723,16 +721,16 @@ module Invoicing
|
|
723
721
|
other_id = row[:other_id].to_i
|
724
722
|
currency = row[:currency].to_sym
|
725
723
|
summary = {:balance => BigDecimal('0'), :currency => currency}
|
726
|
-
|
724
|
+
|
727
725
|
{:sales => 1, :purchases => -1, :sale_receipts => -1, :purchase_payments => 1}.each_pair do |field, factor|
|
728
|
-
summary[field] = BigDecimal(row[field])
|
726
|
+
summary[field] = BigDecimal(row[field].to_s)
|
729
727
|
summary[:balance] += BigDecimal(factor.to_s) * summary[field]
|
730
728
|
end
|
731
|
-
|
729
|
+
|
732
730
|
results[other_id] ||= {}
|
733
731
|
results[other_id][currency] = AccountSummary.new summary
|
734
732
|
end
|
735
|
-
|
733
|
+
|
736
734
|
results
|
737
735
|
end
|
738
736
|
|
@@ -750,7 +748,7 @@ module Invoicing
|
|
750
748
|
sender_recipient_to_ledger_item_ids = {}
|
751
749
|
result_map = {}
|
752
750
|
info = ledger_item_class_info
|
753
|
-
|
751
|
+
|
754
752
|
# Find the most recent occurrence of each ID, first in the sender_id column, then in recipient_id
|
755
753
|
[:sender_id, :recipient_id].each do |column|
|
756
754
|
column = info.method(column)
|
@@ -758,19 +756,19 @@ module Invoicing
|
|
758
756
|
sql = "SELECT MAX(#{primary_key}) AS id, #{quoted_column} AS ref FROM #{quoted_table_name} WHERE "
|
759
757
|
sql << merge_conditions({column => sender_recipient_ids})
|
760
758
|
sql << " GROUP BY #{quoted_column}"
|
761
|
-
|
759
|
+
|
762
760
|
ActiveRecord::Base.connection.select_all(sql).each do |row|
|
763
761
|
sender_recipient_to_ledger_item_ids[row['ref'].to_i] = row['id'].to_i
|
764
762
|
end
|
765
|
-
|
763
|
+
|
766
764
|
sender_recipient_ids -= sender_recipient_to_ledger_item_ids.keys
|
767
765
|
end
|
768
|
-
|
766
|
+
|
769
767
|
# Load all the ledger items needed to get one representative of each name
|
770
768
|
find(sender_recipient_to_ledger_item_ids.values.uniq).each do |ledger_item|
|
771
769
|
sender_id = info.get(ledger_item, :sender_id)
|
772
770
|
recipient_id = info.get(ledger_item, :recipient_id)
|
773
|
-
|
771
|
+
|
774
772
|
if sender_recipient_to_ledger_item_ids.include? sender_id
|
775
773
|
details = info.get(ledger_item, :sender_details)
|
776
774
|
result_map[sender_id] = details[:name]
|
@@ -780,25 +778,48 @@ module Invoicing
|
|
780
778
|
result_map[recipient_id] = details[:name]
|
781
779
|
end
|
782
780
|
end
|
783
|
-
|
781
|
+
|
784
782
|
result_map
|
785
783
|
end
|
786
|
-
|
784
|
+
|
785
|
+
def inheritance_condition(classes)
|
786
|
+
segments = []
|
787
|
+
segments << sanitize_sql(inheritance_column => classes)
|
788
|
+
|
789
|
+
if classes.include?(self.to_s) && self.new.send(inheritance_column).nil?
|
790
|
+
segments << sanitize_sql(type: nil)
|
791
|
+
end
|
792
|
+
|
793
|
+
"(#{segments.join(') OR (')})" unless segments.empty?
|
794
|
+
end
|
795
|
+
|
796
|
+
def merge_conditions(*conditions)
|
797
|
+
segments = []
|
798
|
+
|
799
|
+
conditions.each do |condition|
|
800
|
+
unless condition.blank?
|
801
|
+
sql = sanitize_sql(condition)
|
802
|
+
segments << sql unless sql.blank?
|
803
|
+
end
|
804
|
+
end
|
805
|
+
|
806
|
+
"(#{segments.join(') AND (')})" unless segments.empty?
|
807
|
+
end
|
787
808
|
end # module ClassMethods
|
788
|
-
|
789
|
-
|
809
|
+
|
810
|
+
|
790
811
|
# Very simple class for representing the sum of all sales, purchases and payments on
|
791
812
|
# an account.
|
792
813
|
class AccountSummary #:nodoc:
|
793
814
|
NUM_FIELDS = [:sales, :purchases, :sale_receipts, :purchase_payments, :balance]
|
794
815
|
attr_reader *([:currency] + NUM_FIELDS)
|
795
|
-
|
816
|
+
|
796
817
|
def initialize(hash)
|
797
818
|
@currency = hash[:currency]; @sales = hash[:sales]; @purchases = hash[:purchases]
|
798
819
|
@sale_receipts = hash[:sale_receipts]; @purchase_payments = hash[:purchase_payments]
|
799
820
|
@balance = hash[:balance]
|
800
821
|
end
|
801
|
-
|
822
|
+
|
802
823
|
def method_missing(name, *args)
|
803
824
|
if name.to_s =~ /(.*)_formatted$/
|
804
825
|
::Invoicing::CurrencyValue::Formatter.format_value(currency, send($1))
|
@@ -806,13 +827,13 @@ module Invoicing
|
|
806
827
|
super
|
807
828
|
end
|
808
829
|
end
|
809
|
-
|
830
|
+
|
810
831
|
def +(other)
|
811
832
|
hash = {:currency => currency}
|
812
833
|
NUM_FIELDS.each {|field| hash[field] = send(field) + other.send(field) }
|
813
834
|
AccountSummary.new hash
|
814
835
|
end
|
815
|
-
|
836
|
+
|
816
837
|
def to_s
|
817
838
|
NUM_FIELDS.map do |field|
|
818
839
|
val = send("#{field}_formatted")
|
@@ -820,24 +841,24 @@ module Invoicing
|
|
820
841
|
end.join('; ')
|
821
842
|
end
|
822
843
|
end
|
823
|
-
|
824
|
-
|
844
|
+
|
845
|
+
|
825
846
|
# Stores state in the ActiveRecord class object
|
826
847
|
class ClassInfo < Invoicing::ClassInfo::Base #:nodoc:
|
827
848
|
attr_reader :subtype, :uuid_generator
|
828
|
-
|
849
|
+
|
829
850
|
def initialize(model_class, previous_info, args)
|
830
851
|
super
|
831
852
|
@subtype = all_options[:subtype]
|
832
|
-
|
853
|
+
|
833
854
|
begin # try to load the UUID gem
|
834
855
|
require 'uuid'
|
835
856
|
@uuid_generator = UUID.new
|
836
|
-
rescue LoadError, NameError # silently ignore if gem not found
|
857
|
+
rescue LoadError, NameError # silently ignore if gem not found
|
837
858
|
@uuid_generator = nil
|
838
859
|
end
|
839
860
|
end
|
840
|
-
|
861
|
+
|
841
862
|
# Allow methods generated by +CurrencyValue+ to be renamed as well
|
842
863
|
def method(name)
|
843
864
|
if name.to_s =~ /^(.*)_formatted$/
|