Factur-X/ZUGFeRD: in »Factur-X/ZUGFeRD« umbenannt
[kivitendo-erp.git] / SL / DB / Helper / CustomVariables.pm
index 55ff121..a6ab5ae 100644 (file)
@@ -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};
@@ -83,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
     );
@@ -165,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 {
@@ -295,7 +311,6 @@ sub make_cvar_custom_filter {
       my $conversion  = $config->type =~ m{^(?:date|timestamp)$}       ? $config->type
                       : $config->type =~ m{^(?:customer|vendor|part)$} ? 'integer'
                       : $config->type eq 'number'                      ? 'numeric'
-                      # : $config->type eq 'bool'                        ? 'boolean'
                       :                                                  '';
 
       ($query{config}, $bind_vals{config}) = Rose::DB::Object::QueryBuilder::build_select(
@@ -310,7 +325,7 @@ sub make_cvar_custom_filter {
         query_is_sql       => 1,
       );
 
-      $query{config} =~ s{ \bdefault_value\b \s*=\s* (?!'') }{default_value::${conversion} = }x if $conversion;
+      $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,
@@ -328,8 +343,8 @@ sub make_cvar_custom_filter {
         # 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 doen't know how to handly bind params in subqueries :(
-        $query{$key} =~ s{\?}{ $config->dbh->quote($_) }xe for @{ $bind_vals{$key} };
+        # 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;
       }
@@ -351,6 +366,32 @@ sub make_cvar_custom_filter {
   );
 }
 
+
+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__
@@ -359,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
 
@@ -420,7 +461,7 @@ passed to import.
 
 =item C<cvars_by_config>
 
-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
 
@@ -511,6 +552,23 @@ If the Manager for the calling C<SL::DB::Object> has included the helper L<SL::D
 
 =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 E<lt>s.schoeling@linet-services.deE<gt>,