smerp-quotation 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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