@@ -153,6 +153,10 class CustomField < ActiveRecord::Base | |||||
153 | format.query_filter_options(self, query) |
|
153 | format.query_filter_options(self, query) | |
154 | end |
|
154 | end | |
155 |
|
155 | |||
|
156 | def totalable? | |||
|
157 | format.totalable_supported | |||
|
158 | end | |||
|
159 | ||||
156 | # Returns a ORDER BY clause that can used to sort customized |
|
160 | # Returns a ORDER BY clause that can used to sort customized | |
157 | # objects by their value of the custom field. |
|
161 | # objects by their value of the custom field. | |
158 | # Returns nil if the custom field can not be used for sorting. |
|
162 | # Returns nil if the custom field can not be used for sorting. |
@@ -80,7 +80,7 class QueryCustomFieldColumn < QueryColumn | |||||
80 | self.name = "cf_#{custom_field.id}".to_sym |
|
80 | self.name = "cf_#{custom_field.id}".to_sym | |
81 | self.sortable = custom_field.order_statement || false |
|
81 | self.sortable = custom_field.order_statement || false | |
82 | self.groupable = custom_field.group_statement || false |
|
82 | self.groupable = custom_field.group_statement || false | |
83 | self.totalable = ['int', 'float'].include?(custom_field.field_format) |
|
83 | self.totalable = custom_field.totalable? | |
84 | @inline = true |
|
84 | @inline = true | |
85 | @cf = custom_field |
|
85 | @cf = custom_field | |
86 | end |
|
86 | end | |
@@ -692,7 +692,7 class Query < ActiveRecord::Base | |||||
692 | end |
|
692 | end | |
693 | if column.is_a?(QueryCustomFieldColumn) |
|
693 | if column.is_a?(QueryCustomFieldColumn) | |
694 | custom_field = column.custom_field |
|
694 | custom_field = column.custom_field | |
695 |
send "total_for_ |
|
695 | send "total_for_custom_field", custom_field, scope | |
696 | else |
|
696 | else | |
697 | send "total_for_#{column.name}", scope |
|
697 | send "total_for_#{column.name}", scope | |
698 | end |
|
698 | end | |
@@ -710,21 +710,9 class Query < ActiveRecord::Base | |||||
710 | group(group_by_statement) |
|
710 | group(group_by_statement) | |
711 | end |
|
711 | end | |
712 |
|
712 | |||
713 | def total_for_float_custom_field(custom_field, scope) |
|
|||
714 | total_for_custom_field(custom_field, scope) {|t| t.to_f.round(2)} |
|
|||
715 | end |
|
|||
716 |
|
||||
717 | def total_for_int_custom_field(custom_field, scope) |
|
|||
718 | total_for_custom_field(custom_field, scope) {|t| t.to_i} |
|
|||
719 | end |
|
|||
720 |
|
||||
721 | def total_for_custom_field(custom_field, scope, &block) |
|
713 | def total_for_custom_field(custom_field, scope, &block) | |
722 | total = scope.joins(:custom_values). |
|
714 | total = custom_field.format.total_for_scope(custom_field, scope) | |
723 | where(:custom_values => {:custom_field_id => custom_field.id}). |
|
715 | total = map_total(total) {|t| custom_field.format.cast_total_value(custom_field, t)} | |
724 | where.not(:custom_values => {:value => ''}). |
|
|||
725 | sum("CAST(#{CustomValue.table_name}.value AS decimal(30,3))") |
|
|||
726 |
|
||||
727 | total = map_total(total, &block) if block_given? |
|
|||
728 | total |
|
716 | total | |
729 | end |
|
717 | end | |
730 |
|
718 |
@@ -61,6 +61,10 module Redmine | |||||
61 | class_attribute :searchable_supported |
|
61 | class_attribute :searchable_supported | |
62 | self.searchable_supported = false |
|
62 | self.searchable_supported = false | |
63 |
|
63 | |||
|
64 | # Set this to true if field values can be summed up | |||
|
65 | class_attribute :totalable_supported | |||
|
66 | self.totalable_supported = false | |||
|
67 | ||||
64 | # Restricts the classes that the custom field can be added to |
|
68 | # Restricts the classes that the custom field can be added to | |
65 | # Set to nil for no restrictions |
|
69 | # Set to nil for no restrictions | |
66 | class_attribute :customized_class_names |
|
70 | class_attribute :customized_class_names | |
@@ -370,6 +374,7 module Redmine | |||||
370 |
|
374 | |||
371 | class Numeric < Unbounded |
|
375 | class Numeric < Unbounded | |
372 | self.form_partial = 'custom_fields/formats/numeric' |
|
376 | self.form_partial = 'custom_fields/formats/numeric' | |
|
377 | self.totalable_supported = true | |||
373 |
|
378 | |||
374 | def order_statement(custom_field) |
|
379 | def order_statement(custom_field) | |
375 | # Make the database cast values into numeric |
|
380 | # Make the database cast values into numeric | |
@@ -377,6 +382,18 module Redmine | |||||
377 | # CustomValue validations should ensure that it doesn't occur |
|
382 | # CustomValue validations should ensure that it doesn't occur | |
378 | "CAST(CASE #{join_alias custom_field}.value WHEN '' THEN '0' ELSE #{join_alias custom_field}.value END AS decimal(30,3))" |
|
383 | "CAST(CASE #{join_alias custom_field}.value WHEN '' THEN '0' ELSE #{join_alias custom_field}.value END AS decimal(30,3))" | |
379 | end |
|
384 | end | |
|
385 | ||||
|
386 | # Returns totals for the given scope | |||
|
387 | def total_for_scope(custom_field, scope) | |||
|
388 | scope.joins(:custom_values). | |||
|
389 | where(:custom_values => {:custom_field_id => custom_field.id}). | |||
|
390 | where.not(:custom_values => {:value => ''}). | |||
|
391 | sum("CAST(#{CustomValue.table_name}.value AS decimal(30,3))") | |||
|
392 | end | |||
|
393 | ||||
|
394 | def cast_total_value(custom_field, value) | |||
|
395 | cast_single_value(custom_field, value) | |||
|
396 | end | |||
380 | end |
|
397 | end | |
381 |
|
398 | |||
382 | class IntFormat < Numeric |
|
399 | class IntFormat < Numeric | |
@@ -412,6 +429,10 module Redmine | |||||
412 | value.to_f |
|
429 | value.to_f | |
413 | end |
|
430 | end | |
414 |
|
431 | |||
|
432 | def cast_total_value(custom_field, value) | |||
|
433 | value.to_f.round(2) | |||
|
434 | end | |||
|
435 | ||||
415 | def validate_single_value(custom_field, value, customized=nil) |
|
436 | def validate_single_value(custom_field, value, customized=nil) | |
416 | errs = super |
|
437 | errs = super | |
417 | errs << ::I18n.t('activerecord.errors.messages.invalid') unless (Kernel.Float(value) rescue nil) |
|
438 | errs << ::I18n.t('activerecord.errors.messages.invalid') unless (Kernel.Float(value) rescue nil) |
General Comments 0
You need to be logged in to leave comments.
Login now