SL::DB::Helper::TransNumberGenerator: alle beteiligten Spalten aufführen
[kivitendo-erp.git] / SL / DB / Helper / CustomVariables.pm
index 839c490..97837a2 100644 (file)
@@ -27,6 +27,8 @@ sub import {
   make_cvar_by_configs($caller_package, %params);
   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 {
@@ -144,14 +146,26 @@ sub make_cvar_as_hashref {
   }
 }
 
+sub make_cvar_value_parser {
+  my ($caller_package) = @_;
+  no strict 'refs';
+  *{ $caller_package . '::parse_custom_variable_values' } =  sub {
+    my ($self) = @_;
+
+    $_->parse_value for @{ $self->custom_variables || [] };
+
+    return $self;
+  };
+
+  $caller_package->before_save('parse_custom_variable_values');
+}
+
 sub _all_configs {
   my (%params) = @_;
 
   require SL::DB::CustomVariableConfig;
 
-  $params{module}
-    ? SL::DB::Manager::CustomVariableConfig->get_all(query => [ module => $params{module} ])
-    : SL::DB::Manager::CustomVariableConfig->get_all;
+  SL::DB::Manager::CustomVariableConfig->get_all_sorted($params{module} ? (query => [ module => $params{module} ]) : ());
 }
 
 sub _overload_by_module {
@@ -183,7 +197,7 @@ sub _new_cvar {
   # value needs config
   $inherited_value
    ? $cvar->value($inherited_value)
-   : $cvar->value($params{config}->default_value);
+   : $cvar->value($params{config}->type_dependent_default_value);
   return $cvar;
 }
 
@@ -210,6 +224,132 @@ 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{ (?<! 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 doen't know how to handly 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;
+    }
+  );
+}
+
 1;
 
 __END__
@@ -267,6 +407,11 @@ Secondary classes may currently not have cvars of their own.
 This is a Rose::DB::Object::Relationship accessor, generated for cvars. Use it
 like any other OneToMany relationship.
 
+Note that unlike L</cvars_by_config> this accessor only returns
+variables that have already been created for this object. No variables
+will be autovivified for configs for which no variable has been
+created yet.
+
 =item C<cvars [ CUSTOM_VARIABLES ]>
 
 Alias to C<custom_variables>. Will only be installed if C<cvars_alias> was
@@ -307,10 +452,67 @@ Returns the CVar object for this object which matches the given internal name.
 Useful for print templates. If the requested cvar is not present, it will be
 vivified with the same rules as in C<cvars_by_config>.
 
+=item C<parse_custom_variable_values>
+
+When you want to edit custom variables in a form then you have
+unparsed values from the user. These should be written to the
+variable's C<unparsed_value> field.
+
+This function then processes all variables and parses their
+C<unparsed_value> field into the proper field. It returns C<$self> for
+easy chaining.
+
+This is automatically called in a C<before_save> hook so you don't
+have to do it manually if you save directly after assigning the
+values.
+
+In an HTML form you could e.g. use something like the following:
+
+  [%- FOREACH var = SELF.project.cvars_by_config.as_list %]
+    [% HTML.escape(var.config.description) %]:
+    [% L.hidden_tag('project.custom_variables[+].config_id', var.config.id) %]
+    [% PROCESS 'common/render_cvar_input.html' var_name='project.custom_variables[].unparsed_value' %]
+  [%- END %]
+
+Later in the controller when you want to save this project you don't
+have to do anything special:
+
+  my $project = SL::DB::Project->new;
+  my $params  = $::form->{project} || {};
+
+  $project->assign_attributes(%{ $params });
+
+  $project->parse_custom_variable_values->save;
+
+However, if you need access to a variable's value before saving in
+some way then you have to call this function manually. For example:
+
+  my $project = SL::DB::Project->new;
+  my $params  = $::form->{project} || {};
+
+  $project->assign_attributes(%{ $params });
+
+  $project->parse_custom_variable_values;
+
+  print STDERR "CVar[0] value: " . $project->custom_variables->[0]->value . "\n";
+
+=back
+
+=head1 INSTALLED MANAGER METHODS
+
+=over 4
+
+=item Custom filter for GetModels
+
+If the Manager for the calling C<SL::DB::Object> has included the helper L<SL::DB::Helper::Filtered>, a custom filter for cvars will be added to the specs, with the following syntax:
+
+  filter.cvar.$config_id
+
 =back
 
 =head1 AUTHOR
 
-Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
+Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>,
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
 
 =cut