smerp-quotation 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,417 @@
1
+
2
+ require 'acts_as_tree'
3
+
4
+ module Smerp
5
+
6
+ module Quotation
7
+
8
+ module ArModel
9
+ class QuotationItem < Ca::DataStore::Ar::Model
10
+
11
+ include TR::CondUtils
12
+
13
+ include TeLogger::TeLogHelper
14
+ teLogger_tag :quotItmMdl
15
+
16
+ include ActsAsTree
17
+ acts_as_tree
18
+
19
+ belongs_to :quotation
20
+
21
+ belongs_to :quotable, polymorphic: true
22
+ # polymorphic on model of products / resources
23
+ # has_many :quotation_items, as: :quotable
24
+
25
+ belongs_to :quotation_item_group, optional: true
26
+
27
+ before_save :update_total
28
+ after_save :update_group_total, :update_quotation_total
29
+
30
+ before_destroy :update_total
31
+ after_destroy :update_group_total, :update_quotation_total
32
+
33
+ def update_total
34
+
35
+ if children.length == 0
36
+ self.line_total = self.quantity * self.unit_price if self.quantity_changed? or self.unit_price_changed?
37
+ self.line_total_after_discount = self.line_total
38
+ self.line_total_with_tax = self.line_total
39
+ end
40
+
41
+ if self.quantity_changed?
42
+ # Change unit_price for record that has children
43
+ # since unit_price is 'derived' from children's line_total
44
+ if self.quantity > 0
45
+ self.unit_price = self.line_total / self.quantity
46
+ else
47
+ self.unit_price = 0.0
48
+ self.line_total = 0.0
49
+ end
50
+ end
51
+
52
+ res = { local: [], quotation: [] }
53
+ extCals = { local: [], quotation: [] }
54
+ if not_empty?(self.extended_calculators)
55
+ extCals = YAML.load(self.extended_calculators)
56
+ end
57
+
58
+ extCals[:local].each do |ec|
59
+ teLogger.debug "Executing item calculator : #{ec.inspect}"
60
+ ec.calculate(self)
61
+ ec.owner_id = self.id if is_empty?(ec.owner_id) and not self.new_record?
62
+ res[:local] << ec.for_storage
63
+ end
64
+
65
+ if self.new_record?
66
+
67
+ # apply global calculator from quotation
68
+ gExtCals = []
69
+ if not_empty?(self.quotation.extended_calculators)
70
+ gExtCals = YAML.load(self.quotation.extended_calculators)
71
+ end
72
+
73
+ #cals = []
74
+ gExtCals.each do |ec|
75
+
76
+ teLogger.debug "Executing item-quotation calculator : #{ec.inspect}"
77
+ ec.calculate(self)
78
+ ec.owner_id = self.quotation.id if is_empty?(ec.owner_id)
79
+ res[:quotation] << ec.for_storage
80
+
81
+ end
82
+
83
+ else
84
+
85
+ extCals[:quotation].each do |ec|
86
+ teLogger.debug "Executing item-quotation calculator : #{ec.inspect}"
87
+ ec.calculate(self)
88
+ ec.owner_id = self.quotation.id if is_empty?(ec.owner_id)
89
+ res[:quotation] << ec.for_storage
90
+ end
91
+
92
+ end
93
+
94
+
95
+ # after calculate, remove item with zero params
96
+ res[:local].delete_if { |c|
97
+ case c.params
98
+ when Smerp::Common::FinUtils::Percent
99
+ c.params.value == 0
100
+ else
101
+ c.params.to_i == 0
102
+ end
103
+ }
104
+
105
+ res[:quotation].delete_if { |c|
106
+ case c.params
107
+ when Smerp::Common::FinUtils::Percent
108
+ c.params.value == 0
109
+ else
110
+ c.params.to_i == 0
111
+ end
112
+ }
113
+
114
+ self.extended_calculators = YAML.dump(res)
115
+
116
+ end
117
+
118
+ def update_group_total
119
+
120
+ grp = self.quotation_item_group
121
+ if not grp.nil?
122
+ # update total
123
+ grp.group_total = grp.children.sum(:group_total) + grp.quotation_items.sum(:line_total)
124
+ grp.group_discount = grp.children.sum(:group_discount) + grp.quotation_items.sum(:discount)
125
+ grp.group_tax = grp.children.sum(:group_tax) + grp.quotation_items.sum(:tax)
126
+ grp.save
127
+ end
128
+
129
+ end
130
+
131
+ def update_quotation_total
132
+
133
+ if not self.parent.nil?
134
+
135
+ self.parent.reload
136
+
137
+ if self.parent.is_children_linked?
138
+
139
+ teLogger.debug "Parent #{self.parent.inspect} IS children linked"
140
+ #
141
+ # Children of another QuotationItem
142
+ #
143
+ if self.parent.quantity > 0
144
+ # for children item that has a parent
145
+ # update parent line_total
146
+ self.parent.line_total = self.parent.children.sum(:line_total)
147
+
148
+ # update unit_price since line_total is sum of children's line_total
149
+ self.parent.unit_price = self.parent.line_total / self.parent.quantity
150
+
151
+ self.parent.discount = self.parent.children.sum(:discount)
152
+ self.parent.line_total_after_discount = self.parent.children.sum(:line_total_after_discount)
153
+ self.parent.tax = self.parent.children.sum(:tax)
154
+ self.parent.line_total_with_tax = self.parent.children.sum(:line_total_with_tax)
155
+
156
+ teLogger.debug "After update from children : #{self.parent.inspect}"
157
+
158
+ else
159
+
160
+ self.parent.line_total = 0.0
161
+ self.parent.unit_price = 0.0
162
+ self.parent.discount = 0.0
163
+ self.parent.line_total_after_discount = 0.0
164
+ self.parent.tax = 0.0
165
+ self.parent.line_total_with_tax = 0.0
166
+ end
167
+
168
+ self.parent.save
169
+
170
+ else
171
+ teLogger.debug "Parent #{self.parent.inspect} is NOT children linked. Skipping children update to parent."
172
+ end
173
+
174
+ end
175
+
176
+ self.quotation.total = self.quotation.quotation_items.where(["parent_id is null"]).sum(:line_total)
177
+ self.quotation.total_discount = self.quotation.quotation_items.where(["parent_id is null"]).sum(:discount)
178
+ self.quotation.total_after_discount = self.quotation.quotation_items.where(["parent_id is null"]).sum(:line_total_after_discount)
179
+ self.quotation.total_tax = self.quotation.quotation_items.where(["parent_id is null"]).sum(:tax)
180
+ self.quotation.total_with_tax = self.quotation.quotation_items.where(["parent_id is null"]).sum(:line_total_with_tax)
181
+
182
+ self.quotation.save
183
+ end
184
+
185
+
186
+ def recalculate
187
+
188
+ if self.quantity_changed? or self.unit_price_changed?
189
+ self.line_total = self.quantity * self.unit_price
190
+ end
191
+
192
+ if self.discount_changed?
193
+ self.line_total_after_discount = self.line_total - self.discount
194
+ end
195
+
196
+ end
197
+
198
+
199
+ #def children
200
+ # if @_children.nil?
201
+ # @_children = QuotationItem.where(["parent_id = ?",self.id])
202
+ # end
203
+ # @_children
204
+ #end
205
+
206
+ #def parent
207
+ # if self.parent_id.nil?
208
+ # nil
209
+ # else
210
+ # if @_parent.nil?
211
+ # @_parent = QuotationItem.find(self.parent_id)
212
+ # end
213
+ # @_parent
214
+ # end
215
+ #end
216
+
217
+ def break_children_link
218
+
219
+ self.children_link = 0
220
+ # all fields updated by children shall be reset
221
+ self.line_total = 0.0
222
+ self.unit_price = 0.0
223
+ self.discount = 0.0
224
+ self.line_total_after_discount = 0.0
225
+ self.tax = 0.0
226
+ self.line_total_with_tax = 0.0
227
+
228
+ self.save
229
+
230
+ end
231
+
232
+ def ensure_children_link
233
+
234
+ self.children_link = 1
235
+
236
+ if self.children.length > 0 and self.quantity > 0
237
+
238
+ # update from line_total
239
+ self.line_total = self.children.sum(:line_total)
240
+
241
+ # update unit_price since line_total is sum of children's line_total
242
+ self.unit_price = self.line_total / self.quantity
243
+
244
+ self.discount = self.children.sum(:discount)
245
+ self.line_total_after_discount = self.children.sum(:line_total_after_discount)
246
+ self.tax = self.children.sum(:tax)
247
+ self.line_total_with_tax = self.children.sum(:line_total_with_tax)
248
+
249
+ end
250
+
251
+ self.save
252
+
253
+ end
254
+
255
+ def is_children_linked?
256
+ self.children_link == 1
257
+ end
258
+
259
+ def add_ext_cal(cal)
260
+ if self.extended_calculators.nil?
261
+ res = { local: [], quotation: [] }
262
+ else
263
+ res = YAML.load(self.extended_calculators)
264
+ end
265
+
266
+ case cal
267
+ when Hash
268
+ ac = ExtendedCalculator.instance_from_hash(cal)
269
+ when ExtendedCalculator
270
+ ac = cal
271
+ else
272
+ raise ExtendedCalculator::ExtendedCalculatorException, "Unsupported calculator type '#{cal.class}'"
273
+ end
274
+
275
+ ac.owner = :quotation_item
276
+ ac.owner_id = self.id if not self.new_record?
277
+
278
+ res[:local] << ac
279
+
280
+ self.extended_calculators = YAML.dump(res)
281
+ end
282
+
283
+ def update_ext_cal(ecid, opts = { })
284
+ if not self.extended_calculators.nil?
285
+ cal = YAML.load(self.extended_calculators)
286
+ cal[:local].each do |c|
287
+ if c.ecid == ecid
288
+ opts.each do |k,v|
289
+ c.send("#{k}=", v)
290
+ end
291
+ end
292
+ end
293
+
294
+ cal[:quotation].each do |c|
295
+ if c.ecid == ecid
296
+ opts.each do |k,v|
297
+ c.send("#{k}=",v)
298
+ end
299
+ end
300
+ end
301
+
302
+ self.extended_calculators = YAML.dump(cal)
303
+ end
304
+ end
305
+
306
+ def add_quotation_ext_cal(cal)
307
+
308
+ if self.extended_calculators.nil?
309
+ res = { local: [], quotation: [] }
310
+ else
311
+ res = YAML.load(self.extended_calculators)
312
+ end
313
+
314
+ case cal
315
+ when Hash
316
+ ac = ExtendedCalculator.instance_from_hash(cal)
317
+ when ExtendedCalculator
318
+ ac = cal
319
+ else
320
+ raise ExtendedCalculator::ExtendedCalculatorException, "Unsupported calculator type '#{cal.class}'"
321
+ end
322
+
323
+ res[:quotation] << ac
324
+
325
+ self.extended_calculators = YAML.dump(res)
326
+
327
+ end
328
+
329
+ def ext_cals
330
+ if self.extended_calculators.nil?
331
+ []
332
+ else
333
+ YAML.load(self.extended_calculators)
334
+ end
335
+ end
336
+
337
+ def get_item_discount(input_field = nil)
338
+ if not_empty?(self.extended_calculators)
339
+ cal = YAML.load(self.extended_calculators)
340
+ found = nil
341
+ cal[:local].each do |c|
342
+ if c.is_a?(Smerp::Quotation::DiscountCalculator)
343
+ if not_empty?(input_field)
344
+ if c.input_field.to_sym == input_field.to_sym
345
+ found = c
346
+ break
347
+ end
348
+ else
349
+ found = c
350
+ break
351
+ end
352
+ end
353
+ end
354
+
355
+ found
356
+ else
357
+ nil
358
+ end
359
+ end
360
+
361
+ def get_qoutation_discount(input_field = nil)
362
+ if not_empty?(self.extended_calculators)
363
+ cal = YAML.load(self.extended_calculators)
364
+ found = nil
365
+ cal[:quotation].each do |c|
366
+ if c.is_a?(Smerp::Quotation::DiscountCalculator)
367
+ if not_empty?(input_field)
368
+ if c.input_field.to_sym == input_field.to_sym
369
+ found = c
370
+ break
371
+ end
372
+ else
373
+ found = c
374
+ break
375
+ end
376
+ end
377
+ end
378
+
379
+ found
380
+ else
381
+ nil
382
+ end
383
+ end
384
+
385
+ def get_item_tax
386
+ if not_empty?(self.extended_calculators)
387
+ cal = YAML.load(self.extended_calculators)
388
+ found = nil
389
+ cal[:local].each do |c|
390
+ if c.is_a?(Smerp::Quotation::TaxCalculator)
391
+ found = c
392
+ break
393
+ end
394
+ end
395
+
396
+ if found.nil?
397
+ cal[:quotation].each do |c|
398
+ if c.is_a?(Smerp::Quotation::TaxCalculator)
399
+ found = c
400
+ break
401
+ end
402
+ end
403
+ end
404
+
405
+ found
406
+ else
407
+ nil
408
+ end
409
+
410
+ end
411
+
412
+
413
+ end
414
+ end
415
+ end
416
+
417
+ end
@@ -0,0 +1,34 @@
1
+
2
+ require 'acts_as_tree'
3
+
4
+ module Smerp
5
+ module Quotation
6
+
7
+ module ArModel
8
+ class QuotationItemGroup < Ca::DataStore::Ar::Model
9
+ include ActsAsTree
10
+
11
+ acts_as_tree
12
+
13
+ has_many :quotation_items
14
+
15
+ belongs_to :quotation
16
+
17
+ after_save :update_parent
18
+
19
+ def update_parent
20
+ par = self.parent
21
+ if not par.nil?
22
+ par.group_total = par.children.sum(:group_total) + par.quotation_items.sum(:line_total)
23
+ par.group_discount = par.children.sum(:group_discount) + par.quotation_items.sum(:discount)
24
+
25
+ par.group_tax = par.children.sum(:group_tax) + par.quotation_items.sum(:tax)
26
+
27
+ par.save
28
+ end
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+
2
+
3
+ module Smerp
4
+ module Quotation
5
+ module ArModel
6
+
7
+ class TaxCategory < Ca::DataStore::Ar::Model
8
+
9
+ validates_uniqueness_of :name
10
+ validates_uniqueness_of :display_code
11
+
12
+ belongs_to :extended_calculator
13
+
14
+ state_machine initial: :active do
15
+
16
+ event :inactivate do
17
+ transition :active => :inactive
18
+ end
19
+
20
+ event :activate do
21
+ transition :inactive => :active
22
+ end
23
+
24
+ event :deprecate do
25
+ transition :inactive => :deprecated
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,69 @@
1
+
2
+
3
+ module Smerp
4
+ module Quotation
5
+ class DiscountCalculator
6
+ include ExtendedCalculator
7
+ include TR::CondUtils
8
+
9
+ include TeLogger::TeLogHelper
10
+ teLogger_tag :discountCal
11
+
12
+ DiscountedAmount = :discounted_amount
13
+ ValueAfterDiscount = :value_after_discount
14
+
15
+ def calculate(mdl)
16
+
17
+ srcVal = mdl.send(@input_field)
18
+ if not_empty?(srcVal)
19
+
20
+ srcVal = srcVal.to_f
21
+
22
+ res = { input: @input_field, input_value: srcVal }
23
+ case @params
24
+ when Smerp::Common::FinUtils::Percent
25
+ res[DiscountedAmount] = srcVal * @params.rep_value
26
+ when Smerp::Common::FinUtils::RawValue
27
+ res[DiscountedAmount] = @params.rep_value
28
+ else
29
+ res[DiscountedAmount] = @params.to_f
30
+ end
31
+
32
+ res[ValueAfterDiscount] = srcVal - res[DiscountedAmount]
33
+
34
+ if not_empty?(@output_field)
35
+
36
+ case @output_field
37
+ when Array
38
+ @output_field.each do |of|
39
+ of.each do |f, v|
40
+ mdl.send("#{f}=", res[v])
41
+ end
42
+ end
43
+ when Hash
44
+ @output_field.each do |f,v|
45
+ mdl.send("#{f}=", res[v])
46
+ end
47
+ when Symbol
48
+ mdl.send("#{@output_field}=", res[DiscountedAmount])
49
+ end
50
+
51
+ res[:output] = @output_field
52
+ mdl.recalculate
53
+ end
54
+
55
+ @result = res
56
+
57
+ trigger_listener(:after_calculation, mdl)
58
+
59
+ teLogger.debug "Discount calculation log : #{res}"
60
+
61
+ @result
62
+
63
+ end
64
+
65
+ end
66
+
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,33 @@
1
+
2
+
3
+ module Smerp
4
+ module Quotation
5
+ class RoundUpCalculator
6
+ include ExtendedCalculator
7
+ include TR::CondUtils
8
+
9
+ include TeLogger::TeLogHelper
10
+ teLogger_tag :roundUpCal
11
+
12
+ def calculate(mdl)
13
+ srcVal = mdl.send(@input_field)
14
+ if not_empty?(srcVal)
15
+
16
+ @result = srcVal.to_f.round_to_nearest(@params)
17
+ teLogger.debug "Rounding field '#{@input_field}' (#{srcVal}) to nearest #{@params} is #{@result.inspect}"
18
+
19
+ if not_empty?(@output_field)
20
+ mdl.send("#{@output_field}=", @result.rounded_value)
21
+ mdl.recalculate
22
+ end
23
+
24
+ trigger_listener(:after_calculation, mdl)
25
+
26
+ @result
27
+
28
+ end
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,76 @@
1
+
2
+
3
+ module Smerp
4
+ module Quotation
5
+ class TaxCalculator
6
+ include ExtendedCalculator
7
+ include TR::CondUtils
8
+
9
+ include TeLogger::TeLogHelper
10
+ teLogger_tag :taxCal
11
+
12
+ attr_accessor :tax_category_id, :name, :code, :notes
13
+
14
+ TaxAmount = :tax_amount
15
+ ValueWithTax = :value_with_tax
16
+
17
+ def calculate(mdl)
18
+
19
+ srcVal = mdl.send(@input_field)
20
+ if not_empty?(srcVal)
21
+
22
+ srcVal = srcVal.to_f
23
+
24
+ res = { input: @input_field, input_value: srcVal }
25
+ case @params
26
+ when Smerp::Common::FinUtils::Percent
27
+ res[TaxAmount] = srcVal * @params.rep_value
28
+ when Smerp::Common::FinUtils::RawValue
29
+ res[TaxAmount] = @params.rep_value
30
+ else
31
+ res[TaxAmount] = @params.to_f
32
+ end
33
+
34
+ res[ValueWithTax] = srcVal + res[TaxAmount]
35
+
36
+ if not_empty?(@output_field)
37
+
38
+ case @output_field
39
+ when Array
40
+ @output_field.each do |of|
41
+ of.each do |f, v|
42
+ mdl.send("#{f}=", res[v])
43
+ end
44
+ end
45
+
46
+ when Hash
47
+ @output_field.each do |f,v|
48
+ mdl.send("#{f}=", res[v])
49
+ end
50
+
51
+ when Symbol
52
+ mdl.send("#{@output_field}=", res[ValueWithTax])
53
+
54
+ end
55
+
56
+ res[:output] = @output_field
57
+ end
58
+
59
+ teLogger.debug "Tax calculation log : #{res}"
60
+
61
+ res[:tax_category_id] = @tax_category_id
62
+ res[:name] = @name
63
+ res[:code] = @code
64
+ res[:notes] = @notes
65
+
66
+ @result = res
67
+
68
+ trigger_listener(:after_calculation, mdl)
69
+
70
+ @result
71
+ end
72
+
73
+ end
74
+ end
75
+ end
76
+ end