X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FDB%2FHelper%2FCustomVariables.pm;h=a6ab5ae18859f8bf7e8035069c42ea8c38a06c3e;hb=549f187d3a2b1d15f96c4556714666ed954447bb;hp=713685519fa50962b319e8a0a582c98ffd6ab225;hpb=fa7fc7eeb3ca718914affee06c0629a08d571288;p=kivitendo-erp.git diff --git a/SL/DB/Helper/CustomVariables.pm b/SL/DB/Helper/CustomVariables.pm index 713685519..a6ab5ae18 100644 --- a/SL/DB/Helper/CustomVariables.pm +++ b/SL/DB/Helper/CustomVariables.pm @@ -4,6 +4,7 @@ use strict; use Carp; use Data::Dumper; use List::Util qw(first); +use List::UtilsBy qw(partition_by); use constant META_CVARS => 'cvars_config'; @@ -12,7 +13,7 @@ sub import { my $caller_package = caller; # TODO: if module is empty, module overloading needs to take effect - # certain stuff may have more than one overload, odr even more than one type + # certain stuff may have more than one overload, or even more than one type defined $caller_package or croak 'need to be included from a caller reference'; $params{module} ||= _calc_modules_from_overloads(%params) if $params{overloads}; @@ -28,6 +29,7 @@ sub import { make_cvar_by_name($caller_package, %params); make_cvar_as_hashref($caller_package, %params); make_cvar_value_parser($caller_package, %params); + make_cvar_custom_filter($caller_package, %params); } sub save_meta_info { @@ -82,17 +84,22 @@ sub make_cvar_by_configs { my $configs = _all_configs(%params); my $cvars = $self->custom_variables; my %cvars_by_config = map { $_->config_id => $_ } @$cvars; + my $invalids = _all_invalids($self->${\ $self->meta->primary_key_columns->[0]->name }, $configs, %params); + my %invalids_by_config = map { $_->config_id => 1 } @$invalids; my @return = map( { + my $cvar; if ( $cvars_by_config{$_->id} ) { - $cvars_by_config{$_->id}; + $cvar = $cvars_by_config{$_->id}; } else { - my $cvar = _new_cvar($self, %params, config => $_); + $cvar = _new_cvar($self, %params, config => $_); $self->add_custom_variables($cvar); - $cvar; } + $cvar->{is_valid} = !$invalids_by_config{$_->id}; + $cvar->{config} = $_; + $cvar; } @$configs ); @@ -164,7 +171,17 @@ sub _all_configs { require SL::DB::CustomVariableConfig; - SL::DB::Manager::CustomVariableConfig->get_all_sorted($params{module} ? (query => [ module => $params{module} ]) : ()); + my $cache = $::request->cache("::SL::DB::Helper::CustomVariables::object_cache"); + + if (!$cache->{all}) { + my $configs = SL::DB::Manager::CustomVariableConfig->get_all_sorted; + $cache->{all} = $configs; + $cache->{module} = { partition_by { $_->module } @$configs }; + } + + return $params{module} && !ref $params{module} ? $cache->{module}{$params{module}} + : $params{module} && ref $params{module} ? [ map { @{ $cache->{module}{$_} // [] } } @{ $params{module} } ] + : $cache->{all}; } sub _overload_by_module { @@ -223,6 +240,158 @@ sub _get_primary_key_column { return $column_name; } +sub make_cvar_custom_filter { + my ($caller_package, %params) = @_; + + my $manager = $caller_package->meta->convention_manager->auto_manager_class_name; + + return unless $manager->can('filter'); + + $manager->add_filter_specs( + cvar => sub { + my ($key, $value, $prefix, $config_id) = @_; + my $config = SL::DB::Manager::CustomVariableConfig->find_by(id => $config_id); + + if (!$config) { + die "invalid config_id in $caller_package\::cvar custom filter: $config_id"; + } + + if ($config->module != $params{module}) { + die "invalid config_id in $caller_package\::cvar custom filter: expected module $params{module} - got @{[ $config->module ]}"; + } + + my @filter; + if ($config->type eq 'bool') { + @filter = $value ? ($config->value_col => 1) : (or => [ $config->value_col => undef, $config->value_col => 0 ]); + } else { + @filter = ($config->value_col => $value); + } + + my (%query, %bind_vals); + ($query{customized}, $bind_vals{customized}) = Rose::DB::Object::QueryBuilder::build_select( + dbh => $config->dbh, + select => 'trans_id', + tables => [ 'custom_variables' ], + columns => { custom_variables => [ qw(trans_id config_id text_value number_value bool_value timestamp_value sub_module) ] }, + query => [ + config_id => $config_id, + sub_module => $params{sub_module}, + @filter, + ], + query_is_sql => 1, + ); + + if ($config->type eq 'bool') { + if ($value) { + @filter = ( + '!default_value' => undef, + '!default_value' => '', + default_value => '1', + ); + + } else { + @filter = ( + or => [ + default_value => '0', + default_value => '', + default_value => undef, + ], + ); + } + + } else { + @filter = ( + '!default_value' => undef, + '!default_value' => '', + default_value => $value, + ); + } + + + my $conversion = $config->type =~ m{^(?:date|timestamp)$} ? $config->type + : $config->type =~ m{^(?:customer|vendor|part)$} ? 'integer' + : $config->type eq 'number' ? 'numeric' + : ''; + + ($query{config}, $bind_vals{config}) = Rose::DB::Object::QueryBuilder::build_select( + dbh => $config->dbh, + select => 'id', + tables => [ 'custom_variable_configs' ], + columns => { custom_variable_configs => [ qw(id default_value) ] }, + query => [ + id => $config->id, + @filter, + ], + query_is_sql => 1, + ); + + $query{config} =~ s{ (? $config->dbh, + select => 'trans_id', + tables => [ 'custom_variables' ], + columns => { custom_variables => [ qw(trans_id config_id sub_module) ] }, + query => [ + config_id => $config_id, + sub_module => $params{sub_module}, + ], + query_is_sql => 1, + ); + + foreach my $key (keys %query) { + # remove rose aliases. query builder sadly is not reentrant, and will reuse the same aliases. :( + $query{$key} =~ s{\bt\d+(?:\.)?\b}{}g; + + # manually inline the values. again, rose doesn't know how to handle bind params in subqueries :( + $query{$key} =~ s{\?}{ $config->dbh->quote(shift @{ $bind_vals{$key} }) }xeg; + + $query{$key} =~ s{\n}{ }g; + } + + my $qry_config = "EXISTS (" . $query{config} . ")"; + + my @result = ( + 'or' => [ + $prefix . 'id' => [ \$query{customized} ], + and => [ + "!${prefix}id" => [ \$query{not_customized} ], + \$qry_config, + ] + ], + ); + + return @result; + } + ); +} + + +sub _all_invalids { + my ($trans_id, $configs, %params) = @_; + + require SL::DB::CustomVariableValidity; + + # easy 1: no trans_id, all valid by default. + return [] unless $trans_id; + + # easy 2: no module in params? no validity + return [] unless $params{module}; + + my %wanted_modules = ref $params{module} ? map { $_ => 1 } @{ $params{module} } : ($params{module} => 1); + my @module_configs = grep { $wanted_modules{$_->module} } @$configs; + + return [] unless @module_configs; + + # nor find all entries for that and return + SL::DB::Manager::CustomVariableValidity->get_all( + query => [ + config_id => [ map { $_->id } @module_configs ], + trans_id => $trans_id, + ] + ); +} + 1; __END__ @@ -231,7 +400,7 @@ __END__ =head1 NAME -SL::DB::Helper::CustomVariables - Mixin to provide custom variables relations +SL::DB::Helper::CustomVariables - Mixin to provide custom variable relations =head1 SYNOPSIS @@ -292,7 +461,7 @@ passed to import. =item C -Thi will return a list of CVars with the following changes over the standard accessor: +This will return a list of CVars with the following changes over the standard accessor: =over 4 @@ -371,6 +540,35 @@ some way then you have to call this function manually. For example: =back +=head1 INSTALLED MANAGER METHODS + +=over 4 + +=item Custom filter for GetModels + +If the Manager for the calling C has included the helper L, a custom filter for cvars will be added to the specs, with the following syntax: + + filter.cvar.$config_id + +=back + +=head1 BUGS AND CAVEATS + +=over 4 + +=item * Conditional method export + +Prolonged use has shown that users expect all methods to be present or none. +Future versions of this will likely remove the optional aliasing. + +=item * Semantics need to be updated + +There are a few transitions that are currently neither supported nor well +defined, most of them happening when the config of a cvar gets changed, but +whose instances have already been saved. This needs to be cleaned up. + +=back + =head1 AUTHOR Sven Schöling Es.schoeling@linet-services.deE,