+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{ (?<! NOT\( ) default_value (?! \s*is\s+not\s+null) }{default_value::${conversion}}x if $conversion;
+
+ ($query{not_customized}, $bind_vals{not_customized}) = Rose::DB::Object::QueryBuilder::build_select(
+ dbh => $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,
+ ]
+ );
+}
+