epic-s6ts
[kivitendo-erp.git] / SL / CVar.pm
1 package CVar;
2
3 use strict;
4
5 use Carp;
6 use List::MoreUtils qw(any);
7 use List::Util qw(first);
8 use Scalar::Util qw(blessed);
9 use Data::Dumper;
10
11 use SL::DBUtils;
12 use SL::MoreCommon qw(listify);
13 use SL::Presenter::Text;
14 use SL::Util qw(trim);
15 use SL::DB;
16
17 sub get_configs {
18   $main::lxdebug->enter_sub();
19
20   my $self     = shift;
21   my %params   = @_;
22
23   my $myconfig = \%main::myconfig;
24   my $form     = $main::form;
25
26   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
27
28   my ($where, @values);
29   if ($params{module}) {
30     $where = 'WHERE module = ?';
31     push @values, $params{module};
32   }
33
34   my $query    = <<SQL;
35     SELECT *, date_trunc('seconds', localtimestamp) AS current_timestamp
36     FROM custom_variable_configs $where ORDER BY sortkey
37 SQL
38
39   $::form->{CVAR_CONFIGS} = {} unless 'HASH' eq ref $::form->{CVAR_CONFIGS};
40   if (!$::form->{CVAR_CONFIGS}->{$params{module}}) {
41     my $configs  = selectall_hashref_query($form, $dbh, $query, @values);
42
43     foreach my $config (@{ $configs }) {
44       if ($config->{type} eq 'select') {
45         $config->{OPTIONS} = [ map { { 'value' => $_ } } split(m/\#\#/, $config->{options}) ];
46
47       } elsif ($config->{type} eq 'number') {
48         $config->{precision} = $1 if ($config->{options} =~ m/precision=(\d+)/i);
49
50       } elsif ($config->{type} =~ m{^(?:html|text)field$}) {
51         $config->{width}  = 30;
52         $config->{height} =  5;
53         $config->{width}  = $1 if ($config->{options} =~ m/width=(\d+)/i);
54         $config->{height} = $1 if ($config->{options} =~ m/height=(\d+)/i);
55
56       } elsif ($config->{type} eq 'text') {
57         $config->{maxlength} = $1 if ($config->{options} =~ m/maxlength=(\d+)/i);
58
59       }
60
61       $self->_unpack_flags($config);
62
63       my $cvar_config = SL::DB::CustomVariableConfig->new(id => $config->{id})->load;
64       @{$config->{'partsgroups'}} = map {$_->id} @{$cvar_config->partsgroups};
65
66     }
67     $::form->{CVAR_CONFIGS}->{$params{module}} = $configs;
68   }
69
70   $main::lxdebug->leave_sub();
71
72   return $::form->{CVAR_CONFIGS}->{$params{module}};
73 }
74
75 sub _unpack_flags {
76   $main::lxdebug->enter_sub();
77
78   my $self   = shift;
79   my $config = shift;
80
81   foreach my $flag (split m/:/, $config->{flags}) {
82     if ($flag =~ m/(.*?)=(.*)/) {
83       $config->{"flag_${1}"}    = $2;
84     } else {
85       $config->{"flag_${flag}"} = 1;
86     }
87   }
88
89   $main::lxdebug->leave_sub();
90 }
91
92 sub get_custom_variables {
93   $main::lxdebug->enter_sub();
94
95   my $self     = shift;
96   my %params   = @_;
97
98   Common::check_params(\%params, qw(module));
99
100   my $myconfig = \%main::myconfig;
101   my $form     = $main::form;
102
103   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
104
105   my $sub_module = $params{sub_module} ? $params{sub_module} : '';
106
107   my $q_var    =
108     qq|SELECT text_value, timestamp_value, timestamp_value::date AS date_value, number_value, bool_value
109        FROM custom_variables
110        WHERE (config_id = ?) AND (trans_id = ?) AND (sub_module = ?)|;
111   my $h_var    = prepare_query($form, $dbh, $q_var);
112
113   my $custom_variables = $self->get_configs(module => $params{module});
114
115   foreach my $cvar (@{ $custom_variables }) {
116     if ($cvar->{type} =~ m{^(?:html|text)field}) {
117       $cvar->{width}  = 30;
118       $cvar->{height} =  5;
119
120       $cvar->{width}  = $1 if ($cvar->{options} =~ m/width=(\d+)/i);
121       $cvar->{height} = $1 if ($cvar->{options} =~ m/height=(\d+)/i);
122
123     } elsif ($cvar->{type} eq 'text') {
124       $cvar->{maxlength} = $1 if ($cvar->{options} =~ m/maxlength=(\d+)/i);
125
126     } elsif ($cvar->{type} eq 'number') {
127       $cvar->{precision} = $1 if ($cvar->{options} =~ m/precision=(\d+)/i);
128
129     } elsif ($cvar->{type} eq 'select') {
130       $cvar->{OPTIONS} = [ map { { 'value' => $_ } } split(m/\#\#/, $cvar->{options}) ];
131     }
132
133     my ($act_var, $valid);
134     if ($params{trans_id}) {
135       my @values = (conv_i($cvar->{id}), conv_i($params{trans_id}), $sub_module);
136
137       do_statement($form, $h_var, $q_var, @values);
138       $act_var = $h_var->fetchrow_hashref();
139
140       $valid = $self->get_custom_variables_validity(config_id => $cvar->{id}, trans_id => $params{trans_id}, sub_module => $params{sub_module});
141     } else {
142       $valid = !$cvar->{flag_defaults_to_invalid};
143     }
144
145     if ($act_var) {
146       $cvar->{value} = $cvar->{type} eq 'date'      ? $act_var->{date_value}
147                      : $cvar->{type} eq 'timestamp' ? $act_var->{timestamp_value}
148                      : $cvar->{type} eq 'number'    ? $act_var->{number_value}
149                      : $cvar->{type} eq 'customer'  ? $act_var->{number_value}
150                      : $cvar->{type} eq 'vendor'    ? $act_var->{number_value}
151                      : $cvar->{type} eq 'part'      ? $act_var->{number_value}
152                      : $cvar->{type} eq 'bool'      ? $act_var->{bool_value}
153                      :                                $act_var->{text_value};
154       $cvar->{valid} = $valid;
155     } else {
156       $cvar->{valid} = $valid // 1;
157
158       if ($cvar->{type} eq 'date') {
159         if ($cvar->{default_value} eq 'NOW') {
160           $cvar->{value} = $cvar->{current_date};
161         } else {
162           $cvar->{value} = $cvar->{default_value};
163         }
164
165       } elsif ($cvar->{type} eq 'timestamp') {
166         if ($cvar->{default_value} eq 'NOW') {
167           $cvar->{value} = $cvar->{current_timestamp};
168         } else {
169           $cvar->{value} = $cvar->{default_value};
170         }
171
172       } elsif ($cvar->{type} eq 'bool') {
173         $cvar->{value} = $cvar->{default_value} * 1;
174
175       } elsif ($cvar->{type} eq 'number') {
176         $cvar->{value} = $cvar->{default_value} * 1 if ($cvar->{default_value} ne '');
177
178       } else {
179         $cvar->{value} = $cvar->{default_value};
180       }
181     }
182
183     if ($cvar->{type} eq 'number') {
184       $cvar->{value} = $form->format_amount($myconfig, $cvar->{value} * 1, $cvar->{precision});
185     } elsif ($cvar->{type} eq 'customer') {
186       require SL::DB::Customer;
187       $cvar->{value} = SL::DB::Manager::Customer->find_by(id => $cvar->{value} * 1);
188     } elsif ($cvar->{type} eq 'vendor') {
189       require SL::DB::Vendor;
190       $cvar->{value} = SL::DB::Manager::Vendor->find_by(id => $cvar->{value} * 1);
191     } elsif ($cvar->{type} eq 'part') {
192       require SL::DB::Part;
193       $cvar->{value} = SL::DB::Manager::Part->find_by(id => $cvar->{value} * 1);
194     }
195   }
196
197   $h_var->finish();
198
199   $main::lxdebug->leave_sub();
200
201   return $custom_variables;
202 }
203
204 sub save_custom_variables {
205   my ($self, %params) = @_;
206   $main::lxdebug->enter_sub();
207
208   my $rc = SL::DB->client->with_transaction(\&_save_custom_variables, $self, %params);
209
210   $::lxdebug->leave_sub;
211   return $rc;
212 }
213
214 sub _save_custom_variables {
215   my $self     = shift;
216   my %params   = @_;
217
218   Common::check_params(\%params, qw(module trans_id variables));
219
220   my $myconfig = \%main::myconfig;
221   my $form     = $main::form;
222
223   my $dbh      = $params{dbh} || SL::DB->client->dbh;
224
225   my @configs  = $params{configs} ? @{ $params{configs} } : grep { $_->{module} eq $params{module} } @{ CVar->get_configs() };
226
227   my $query    =
228     qq|DELETE FROM custom_variables
229        WHERE (trans_id  = ?)
230          AND (config_id IN (SELECT DISTINCT id
231                             FROM custom_variable_configs
232                             WHERE module = ?))|;
233   my @values   = (conv_i($params{trans_id}), $params{module});
234
235   if ($params{sub_module}) {
236     $query .= qq| AND (sub_module = ?)|;
237     push @values, $params{sub_module};
238   }
239
240   do_query($form, $dbh, $query, @values);
241
242   $query  =
243     qq|INSERT INTO custom_variables (config_id, sub_module, trans_id, bool_value, timestamp_value, text_value, number_value)
244        VALUES                       (?,         ?,          ?,        ?,          ?,               ?,          ?)|;
245   my $sth = prepare_query($form, $dbh, $query);
246
247   foreach my $config (@configs) {
248     if ($params{save_validity}) {
249       my $valid_index = "$params{name_prefix}cvar_$config->{name}$params{name_postfix}_valid";
250       my $new_valid   = $params{variables}{$valid_index} || $params{always_valid} ? 1 : 0;
251       my $old_valid   = $self->get_custom_variables_validity(trans_id => $params{trans_id}, config_id => $config->{id});
252
253       $self->save_custom_variables_validity(trans_id  => $params{trans_id},
254                                             config_id => $config->{id},
255                                             validity  => $new_valid,
256                                            );
257
258       if (!$new_valid || !$old_valid) {
259         # When activating a cvar (old_valid == 0 && new_valid == 1)
260         # the input to hold the variable's value wasn't actually
261         # rendered, meaning saving the value now would only save an
262         # empty value/the value 0. This means that the next time the
263         # form is rendered, an existing value is found and used
264         # instead of the variable's default value from the
265         # configuration. Therefore don't save the values in such
266         # cases.
267         next;
268       }
269     }
270
271     my @values = (conv_i($config->{id}), "$params{sub_module}", conv_i($params{trans_id}));
272
273     my $value  = $params{variables}->{"$params{name_prefix}cvar_$config->{name}$params{name_postfix}"};
274
275     if (any { $config->{type} eq $_ } qw(text textfield htmlfield select)) {
276       push @values, undef, undef, $value, undef;
277
278     } elsif (($config->{type} eq 'date') || ($config->{type} eq 'timestamp')) {
279       push @values, undef, conv_date($value), undef, undef;
280
281     } elsif ($config->{type} eq 'number') {
282       push @values, undef, undef, undef, conv_i($form->parse_amount($myconfig, $value));
283
284     } elsif ($config->{type} eq 'bool') {
285       push @values, $value ? 't' : 'f', undef, undef, undef;
286     } elsif (any { $config->{type} eq $_ } qw(customer vendor part)) {
287       push @values, undef, undef, undef, $value * 1;
288     }
289
290     do_statement($form, $sth, $query, @values);
291   }
292
293   $sth->finish();
294
295   return 1;
296 }
297
298 sub render_inputs {
299   $main::lxdebug->enter_sub(2);
300
301   my $self     = shift;
302   my %params   = @_;
303
304   Common::check_params(\%params, qw(variables));
305
306   my $myconfig = \%main::myconfig;
307   my $form     = $main::form;
308
309   my %options  = ( name_prefix           => "$params{name_prefix}",
310                    name_postfix          => "$params{name_postfix}",
311                    hide_non_editable     => $params{hide_non_editable},
312                    show_disabled_message => $params{show_disabled_message},
313                  );
314
315   # should this cvar be filtered by partsgroups?
316   foreach my $var (@{ $params{variables} }) {
317     if ($var->{flag_partsgroup_filter}) {
318       if (!$params{partsgroup_id} || (!grep {$params{partsgroup_id} == $_} @{ $var->{partsgroups} })) {
319         $var->{partsgroup_filtered} = 1;
320       }
321     }
322
323     $var->{HTML_CODE} = $form->parse_html_template('amcvar/render_inputs',     { var => $var, %options });
324     $var->{VALID_BOX} = $form->parse_html_template('amcvar/render_checkboxes', { var => $var, %options });
325   }
326
327   $main::lxdebug->leave_sub(2);
328 }
329
330 sub render_search_options {
331   $main::lxdebug->enter_sub();
332
333   my $self     = shift;
334   my %params   = @_;
335
336   Common::check_params(\%params, qw(variables));
337
338   my $myconfig = \%main::myconfig;
339   my $form     = $main::form;
340
341   $params{hidden_cvar_filters} = $myconfig->{hide_cvar_search_options};
342
343   $params{include_prefix}   = 'l_' unless defined($params{include_prefix});
344   $params{include_value}  ||= '1';
345   $params{filter_prefix}  ||= '';
346
347   my $filter  = $form->parse_html_template('amcvar/search_filter',  \%params);
348   my $include = $form->parse_html_template('amcvar/search_include', \%params);
349
350   $main::lxdebug->leave_sub();
351
352   return ($filter, $include);
353 }
354
355 sub build_filter_query {
356   $main::lxdebug->enter_sub();
357
358   my $self     = shift;
359   my %params   = @_;
360
361   Common::check_params(\%params, qw(module trans_id_field filter));
362
363   my $myconfig = \%main::myconfig;
364   my $form     = $main::form;
365
366   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
367
368   my $configs  = $self->get_configs(%params);
369
370   my (@where, @values);
371
372   foreach my $config (@{ $configs }) {
373     next unless ($config->{searchable});
374
375     my $name = "cvar_$config->{name}";
376
377     my (@sub_values, @sub_where, $not);
378
379     if (any { $config->{type} eq $_ } qw(text textfield htmlfield)) {
380       next unless ($params{filter}->{$name});
381
382       push @sub_where,  qq|cvar.text_value ILIKE ?|;
383       push @sub_values, like($params{filter}->{$name});
384
385     } elsif ($config->{type} eq 'select') {
386       next unless ($params{filter}->{$name});
387
388       push @sub_where,  qq|cvar.text_value = ?|;
389       push @sub_values, $params{filter}->{$name};
390
391     } elsif (($config->{type} eq 'date') || ($config->{type} eq 'timestamp')) {
392       my $name_from = "${name}_from";
393       my $name_to   = "${name}_to";
394
395       if ($params{filter}->{$name_from}) {
396         push @sub_where,  qq|cvar.timestamp_value >= ?|;
397         push @sub_values, conv_date($params{filter}->{$name_from});
398       }
399
400       if ($params{filter}->{$name_to}) {
401         push @sub_where,  qq|cvar.timestamp_value <= ?|;
402         push @sub_values, conv_date($params{filter}->{$name_to});
403       }
404
405     } elsif ($config->{type} eq 'number') {
406       next if ($params{filter}->{$name} eq '');
407
408       my $f_op = $params{filter}->{"${name}_qtyop"};
409
410       my $op;
411       if ($f_op eq '==') {
412         $op  = '=';
413
414       } elsif ($f_op eq '=/=') {
415         $not = 'NOT';
416         $op  = '<>';
417
418       } elsif ($f_op eq '<') {
419         $not = 'NOT';
420         $op  = '>=';
421
422       } elsif ($f_op eq '<=') {
423         $not = 'NOT';
424         $op  = '>';
425
426       } elsif (($f_op eq '>') || ($f_op eq '>=')) {
427         $op  = $f_op;
428
429       } else {
430         $op  = '=';
431       }
432
433       push @sub_where,  qq|cvar.number_value $op ?|;
434       push @sub_values, $form->parse_amount($myconfig, trim($params{filter}->{$name}));
435
436     } elsif ($config->{type} eq 'bool') {
437       next unless ($params{filter}->{$name});
438
439       $not = 'NOT' if ($params{filter}->{$name} eq 'no');
440       push @sub_where,  qq|COALESCE(cvar.bool_value, false) = TRUE|;
441     } elsif (any { $config->{type} eq $_ } qw(customer vendor)) {
442       next unless $params{filter}->{$name};
443
444       my $table = $config->{type};
445       push @sub_where, qq|cvar.number_value * 1 IN (SELECT id FROM $table WHERE name ILIKE ?)|;
446       push @sub_values, like($params{filter}->{$name});
447     } elsif ($config->{type} eq 'part') {
448       next unless $params{filter}->{$name};
449
450       push @sub_where, qq|cvar.number_value * 1 IN (SELECT id FROM parts WHERE partnumber ILIKE ?)|;
451       push @sub_values, like($params{filter}->{$name});
452     }
453
454     if (@sub_where) {
455       add_token(\@sub_where, \@sub_values, col => 'cvar.sub_module', val => $params{sub_module} || '');
456
457       push @where,
458         qq|$not EXISTS(
459              SELECT cvar.id
460              FROM custom_variables cvar
461              LEFT JOIN custom_variable_configs cvarcfg ON (cvar.config_id = cvarcfg.id)
462              WHERE (cvarcfg.module = ?)
463                AND (cvarcfg.id     = ?)
464                AND (cvar.trans_id  = $params{trans_id_field})
465                AND | . join(' AND ', map { "($_)" } @sub_where) . qq|)|;
466       push @values, $params{module}, conv_i($config->{id}), @sub_values;
467     }
468   }
469
470   my $query = join ' AND ', @where;
471
472   $main::lxdebug->leave_sub();
473
474   return ($query, @values);
475 }
476
477 sub add_custom_variables_to_report {
478   $main::lxdebug->enter_sub();
479
480   my $self      = shift;
481   my %params    = @_;
482
483   Common::check_params(\%params, qw(module trans_id_field column_defs data configs));
484
485   my $myconfig  = \%main::myconfig;
486   my $form      = $main::form;
487   my $locale    = $main::locale;
488
489   my $dbh       = $params{dbh} || $form->get_standard_dbh($myconfig);
490
491   my $configs   = [ grep { $_->{includeable} && $params{column_defs}->{"cvar_$_->{name}"}->{visible} } @{ $params{configs} } ];
492
493   if (!scalar(@{ $params{data} }) || ! scalar(@{ $configs })) {
494     $main::lxdebug->leave_sub();
495     return;
496   }
497
498   # allow sub_module to be a coderef or a fixed value
499   if (ref $params{sub_module} ne 'CODE') {
500     my $sub_module = "$params{sub_module}";
501     $params{sub_module} = sub { $sub_module };
502   }
503
504   my %cfg_map   = map { $_->{id} => $_ } @{ $configs };
505   my @cfg_ids   = keys %cfg_map;
506
507   my $query     =
508     qq|SELECT text_value, timestamp_value, timestamp_value::date AS date_value, number_value, bool_value, config_id
509        FROM custom_variables
510        WHERE (config_id IN (| . join(', ', ('?') x scalar(@cfg_ids)) . qq|))
511          AND (trans_id = ?)
512          AND (sub_module = ?)|;
513   my $sth       = prepare_query($form, $dbh, $query);
514
515   foreach my $row (@{ $params{data} }) {
516     do_statement($form, $sth, $query, @cfg_ids, conv_i($row->{$params{trans_id_field}}), $params{sub_module}->($row));
517
518     while (my $ref = $sth->fetchrow_hashref()) {
519       my $cfg = $cfg_map{$ref->{config_id}};
520
521       $row->{"cvar_$cfg->{name}"} =
522           $cfg->{type} eq 'date'      ? $ref->{date_value}
523         : $cfg->{type} eq 'timestamp' ? $ref->{timestamp_value}
524         : $cfg->{type} eq 'number'    ? $form->format_amount($myconfig, $ref->{number_value} * 1, $cfg->{precision})
525         : $cfg->{type} eq 'customer'  ? (SL::DB::Manager::Customer->find_by(id => 1*$ref->{number_value}) || SL::DB::Customer->new)->name
526         : $cfg->{type} eq 'vendor'    ? (SL::DB::Manager::Vendor->find_by(id => 1*$ref->{number_value})   || SL::DB::Vendor->new)->name
527         : $cfg->{type} eq 'part'      ? (SL::DB::Manager::Part->find_by(id => 1*$ref->{number_value})     || SL::DB::Part->new)->partnumber
528         : $cfg->{type} eq 'bool'      ? ($ref->{bool_value} ? $locale->text('Yes') : $locale->text('No'))
529         : $cfg->{type} eq 'htmlfield' ? SL::Presenter::Text::stripped_html($ref->{text_value})
530         :                               $ref->{text_value};
531     }
532   }
533
534   $sth->finish();
535
536   $main::lxdebug->leave_sub();
537 }
538
539 sub get_field_format_list {
540   $main::lxdebug->enter_sub();
541
542   my $self          = shift;
543   my %params        = @_;
544
545   Common::check_params(\%params, qw(module));
546
547   my $myconfig      = \%main::myconfig;
548   my $form          = $main::form;
549
550   my $dbh           = $params{dbh} || $form->get_standard_dbh($myconfig);
551
552   my $configs       = $self->get_configs(%params);
553
554   my $date_fields   = [];
555   my $number_fields = {};
556
557   foreach my $config (@{ $configs }) {
558     my $name = "$params{prefix}cvar_$config->{name}";
559
560     if ($config->{type} eq 'date') {
561       push @{ $date_fields }, $name;
562
563     } elsif ($config->{type} eq 'number') {
564       $number_fields->{$config->{precision}} ||= [];
565       push @{ $number_fields->{$config->{precision}} }, $name;
566     }
567   }
568
569   $main::lxdebug->leave_sub();
570
571   return ($date_fields, $number_fields);
572 }
573
574 sub save_custom_variables_validity {
575   my ($self, %params) = @_;
576   $main::lxdebug->enter_sub();
577
578   my $rc = SL::DB->client->with_transaction(\&_save_custom_variables_validity, $self, %params);
579
580   $::lxdebug->leave_sub;
581   return $rc;
582 }
583
584 sub _save_custom_variables_validity {
585   my $self     = shift;
586   my %params   = @_;
587
588   Common::check_params(\%params, qw(config_id trans_id validity));
589
590   my $myconfig = \%main::myconfig;
591   my $form     = $main::form;
592
593   my $dbh      = $params{dbh} || SL::DB->client->dbh;
594
595   my (@where, @values);
596   add_token(\@where, \@values, col => "config_id", val => $params{config_id}, esc => \&conv_i);
597   add_token(\@where, \@values, col => "trans_id",  val => $params{trans_id},  esc => \&conv_i);
598
599   my $where = scalar @where ? "WHERE " . join ' AND ', @where : '';
600   my $query = qq|DELETE FROM custom_variables_validity $where|;
601
602   do_query($form, $dbh, $query, @values);
603
604   $query  =
605     qq|INSERT INTO custom_variables_validity (config_id, trans_id)
606        VALUES                                (?,         ?       )|;
607   my $sth = prepare_query($form, $dbh, $query);
608
609   unless ($params{validity}) {
610     foreach my $config_id (listify($params{config_id})) {
611       foreach my $trans_id (listify($params{trans_id})) {
612         do_statement($form, $sth, $query, conv_i($config_id), conv_i($trans_id));
613       }
614     }
615   }
616
617   $sth->finish();
618
619   return 1;
620 }
621
622 my %_validity_sub_module_mapping = (
623   orderitems           => { table => 'orderitems',           result_column => 'parts_id', trans_id_column => 'id', },
624   delivery_order_items => { table => 'delivery_order_items', result_column => 'parts_id', trans_id_column => 'id', },
625   invoice              => { table => 'invoice',              result_column => 'parts_id', trans_id_column => 'id', },
626 );
627
628 sub get_custom_variables_validity {
629   my $self     = shift;
630   my %params   = @_;
631
632   Common::check_params(\%params, qw(config_id trans_id));
633
634   my $myconfig = \%main::myconfig;
635   my $form     = $main::form;
636
637   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
638
639   my $query;
640
641   if ($params{sub_module}) {
642     my %mapping = %{ $_validity_sub_module_mapping{ $params{sub_module} } || croak("Invalid sub_module '" . $params{sub_module} . "'") };
643     $query = <<SQL;
644       SELECT cvv.id
645       FROM $mapping{table} mt
646       LEFT JOIN custom_variables_validity cvv ON (cvv.trans_id = mt.$mapping{result_column})
647       WHERE (cvv.config_id                = ?)
648         AND (mt.$mapping{trans_id_column} = ?)
649       LIMIT 1
650 SQL
651   } else {
652     $query = <<SQL;
653       SELECT id
654       FROM custom_variables_validity
655       WHERE (config_id = ?)
656         AND (trans_id  = ?)
657       LIMIT 1
658 SQL
659   }
660
661   my ($invalid) = selectfirst_array_query($form, $dbh, $query, conv_i($params{config_id}), conv_i($params{trans_id}));
662
663   return !$invalid;
664 }
665
666 sub custom_variables_validity_by_trans_id {
667   $main::lxdebug->enter_sub(2);
668
669   my $self     = shift;
670   my %params   = @_;
671
672   return sub { 0 } unless $params{trans_id};
673
674   my $myconfig = \%main::myconfig;
675   my $form     = $main::form;
676
677   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
678
679   my $query    = qq|SELECT DISTINCT config_id FROM custom_variables_validity WHERE trans_id = ?|;
680
681   my %invalids = map { +($_->{config_id} => 1) } selectall_hashref_query($form, $dbh, $query, $params{trans_id});
682
683   $main::lxdebug->leave_sub(2);
684
685   return sub { !$invalids{+shift} };
686 }
687
688 sub parse {
689   my ($self, $value, $config) = @_;
690
691   return $::form->parse_amount(\%::myconfig, $value)          if $config->{type} eq 'number';
692   return DateTime->from_lxoffice($value)                      if $config->{type} eq 'date';
693   return !ref $value ? SL::DB::Manager::Customer->find_by(id => $value * 1) : $value  if $config->{type} eq 'customer';
694   return !ref $value ? SL::DB::Manager::Vendor->find_by(id => $value * 1)   : $value  if $config->{type} eq 'vendor';
695   return !ref $value ? SL::DB::Manager::Part->find_by(id => $value * 1)     : $value  if $config->{type} eq 'part';
696   return $value;
697 }
698
699 sub format_to_template {
700   my ($self, $value, $config) = @_;
701   # stupid template expects everything formated. except objects
702   # do not use outside of print routines for legacy templates
703
704   return $::form->format_amount(\%::myconfig, $value) if $config->{type} eq 'number';
705   return $value->to_lxoffice if $config->{type} eq 'date' && blessed $value && $value->can('to_lxoffice');
706   return $value;
707 }
708
709 sub get_non_editable_ic_cvars {
710   $main::lxdebug->enter_sub(2);
711   my $self     = shift;
712   my %params   = @_;
713
714   Common::check_params(\%params, qw(form dbh row sub_module may_converted_from));
715   my $form               = $params{form};
716   my $dbh                = $params{dbh};
717   my $row                = $params{row};
718   my $sub_module         = $params{sub_module};
719   my $may_converted_from = $params{may_converted_from};
720
721   my $cvars;
722   if (! $form->{"${sub_module}_id_${row}"}) {
723     my $conv_from = 0;
724     foreach (@{ $may_converted_from }) {
725       if ($form->{"converted_from_${_}_id_$row"}) {
726         $cvars = CVar->get_custom_variables(dbh        => $dbh,
727                                             module     => 'IC',
728                                             sub_module => $_,
729                                             trans_id   => $form->{"converted_from_${_}_id_$row"},
730                                            );
731         $conv_from = 1;
732         last;
733       }
734     }
735     # get values for CVars from master data for new items
736     if (!$conv_from) {
737       $cvars = CVar->get_custom_variables(dbh      => $dbh,
738                                           module   => 'IC',
739                                           trans_id => $form->{"id_$row"},
740                                          );
741     }
742   } else {
743     # get values for CVars from custom_variables for existing items
744     $cvars = CVar->get_custom_variables(dbh        => $dbh,
745                                         module     => 'IC',
746                                         sub_module => $sub_module,
747                                         trans_id   => $form->{"${sub_module}_id_${row}"},
748                                        );
749   }
750   # map only non-editable CVars to form
751   foreach (@{ $cvars }) {
752     next if $_->{flag_editable};
753     $form->{"ic_cvar_$_->{name}_$row"} = $_->{value}
754   }
755
756   $main::lxdebug->leave_sub(2);
757 }
758
759 1;
760
761 __END__
762
763 =head1 NAME
764
765 SL::CVar.pm - Custom Variables module
766
767 =head1 SYNOPSIS
768
769   # dealing with configs
770
771   my $all_configs = CVar->get_configs()
772
773   # dealing with custom vars
774
775   CVar->get_custom_variables(module => 'ic')
776
777 =head2 VALIDITY
778
779 Suppose the following scenario:
780
781 You have a lot of parts in your database, and a set of properties configured. Now not every part has every of these properties, some combinations will just make no sense. In order to clean up your inputs a bit, you want to mark certain combinations as invalid, blocking them from modification and possibly display.
782
783 Validity is assumed. If you modify validity, you actually save B<invalidity>.
784 Invalidity is saved as a function of config_id, and the trans_id
785
786 In the naive way, disable an attribute for a specific id (simple)
787
788 =cut