Valid Flag für Custom Variables in Artikeln.
[kivitendo-erp.git] / SL / CVar.pm
1 package CVar;
2
3 use List::Util qw(first);
4 use Data::Dumper;
5
6 use SL::DBUtils;
7 use SL::MoreCommon qw(listify);
8
9 sub get_configs {
10   $main::lxdebug->enter_sub();
11
12   my $self     = shift;
13   my %params   = @_;
14
15   my $myconfig = \%main::myconfig;
16   my $form     = $main::form;
17
18   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
19
20   my ($where, @values);
21   if ($params{module}) {
22     $where = 'WHERE module = ?';
23     push @values, $params{module};
24   }
25
26   my $query    = qq|SELECT * FROM custom_variable_configs $where ORDER BY sortkey|;
27
28   my $configs  = selectall_hashref_query($form, $dbh, $query, @values);
29
30   foreach my $config (@{ $configs }) {
31     if ($config->{type} eq 'select') {
32       $config->{OPTIONS} = [ map { { 'value' => $_ } } split(m/\#\#/, $config->{options}) ];
33
34     } elsif ($config->{type} eq 'number') {
35       $config->{precision} = $1 if ($config->{options} =~ m/precision=(\d+)/i);
36
37     }
38
39     $self->_unpack_flags($config);
40   }
41
42   $main::lxdebug->leave_sub();
43
44   return $configs;
45 }
46
47 sub get_config {
48   $main::lxdebug->enter_sub();
49
50   my $self     = shift;
51   my %params   = @_;
52
53   Common::check_params(\%params, qw(id));
54
55   my $myconfig = \%main::myconfig;
56   my $form     = $main::form;
57
58   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
59
60   my $query    = qq|SELECT * FROM custom_variable_configs WHERE id = ?|;
61
62   my $config   = selectfirst_hashref_query($form, $dbh, $query, conv_i($params{id})) || { };
63
64   $self->_unpack_flags($config);
65
66   $main::lxdebug->leave_sub();
67
68   return $config;
69 }
70
71 sub _unpack_flags {
72   $main::lxdebug->enter_sub();
73
74   my $self   = shift;
75   my $config = shift;
76
77   foreach my $flag (split m/:/, $config->{flags}) {
78     if ($flag =~ m/(.*?)=(.*)/) {
79       $config->{"flag_${1}"}    = $2;
80     } else {
81       $config->{"flag_${flag}"} = 1;
82     }
83   }
84
85   $main::lxdebug->leave_sub();
86 }
87
88 sub save_config {
89   $main::lxdebug->enter_sub();
90
91   my $self     = shift;
92   my %params   = @_;
93
94   Common::check_params(\%params, qw(module config));
95
96   my $myconfig = \%main::myconfig;
97   my $form     = $main::form;
98
99   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
100
101   my $q_id     = qq|SELECT nextval('custom_variable_configs_id')|;
102   my $h_id     = prepare_query($form, $dbh, $q_id);
103
104   my $q_new    =
105     qq|INSERT INTO custom_variable_configs (name, description, type, default_value, options, searchable, includeable, included_by_default, module, flags, id, sortkey)
106        VALUES                              (?,    ?,           ?,    ?,             ?,       ?,          ?,           ?,                   ?,      ?,     ?,
107          (SELECT COALESCE(MAX(sortkey) + 1, 1) FROM custom_variable_configs))|;
108   my $h_new    = prepare_query($form, $dbh, $q_new);
109
110   my $q_update =
111     qq|UPDATE custom_variable_configs SET
112          name        = ?, description         = ?,
113          type        = ?, default_value       = ?,
114          options     = ?, searchable          = ?,
115          includeable = ?, included_by_default = ?,
116          module      = ?, flags               = ?
117        WHERE id  = ?|;
118   my $h_update = prepare_query($form, $dbh, $q_update);
119
120   my @configs;
121   if ('ARRAY' eq ref $params{config}) {
122     @configs = @{ $params{config} };
123   } else {
124     @configs = ($params{config});
125   }
126
127   foreach my $config (@configs) {
128     my ($h_actual, $q_actual);
129
130     if (!$config->{id}) {
131       do_statement($form, $h_id, $q_id);
132       ($config->{id}) = $h_id->fetchrow_array();
133
134       $h_actual       = $h_new;
135       $q_actual       = $q_new;
136
137     } else {
138       $h_actual       = $h_update;
139       $q_actual       = $q_update;
140     }
141
142     do_statement($form, $h_actual, $q_actual, @{$config}{qw(name description type default_value options)},
143                  $config->{searchable} ? 't' : 'f', $config->{includeable} ? 't' : 'f', $config->{included_by_default} ? 't' : 'f',
144                  $params{module}, $config->{flags}, conv_i($config->{id}));
145   }
146
147   $h_id->finish();
148   $h_new->finish();
149   $h_update->finish();
150
151   $dbh->commit();
152
153   $main::lxdebug->leave_sub();
154 }
155
156 sub delete_config {
157   $main::lxdebug->enter_sub();
158
159   my $self     = shift;
160   my %params   = @_;
161
162   Common::check_params(\%params, qw(id));
163
164   my $myconfig = \%main::myconfig;
165   my $form     = $main::form;
166
167   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
168
169   do_query($form, $dbh, qq|DELETE FROM custom_variables        WHERE config_id = ?|, conv_i($params{id}));
170   do_query($form, $dbh, qq|DELETE FROM custom_variable_configs WHERE id        = ?|, conv_i($params{id}));
171
172   $dbh->commit();
173
174   $main::lxdebug->leave_sub();
175 }
176
177 sub get_custom_variables {
178   $main::lxdebug->enter_sub();
179
180   my $self     = shift;
181   my %params   = @_;
182
183   Common::check_params(\%params, qw(module));
184
185   my $myconfig = \%main::myconfig;
186   my $form     = $main::form;
187
188   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
189
190   my $trans_id = $params{trans_id} ? 'OR (v.trans_id = ?) ' : '';
191
192   my $q_cfg    =
193     qq|SELECT id, name, description, type, default_value, options,
194          date_trunc('seconds', localtimestamp) AS current_timestamp, current_date AS current_date
195        FROM custom_variable_configs
196        WHERE module = ?
197        ORDER BY sortkey|;
198
199   my $q_var    =
200     qq|SELECT text_value, timestamp_value, timestamp_value::date AS date_value, number_value, bool_value
201        FROM custom_variables
202        WHERE (config_id = ?) AND (trans_id = ?)|;
203   $q_var      .= qq| AND (sub_module = ?)| if $params{sub_module};
204   my $h_var    = prepare_query($form, $dbh, $q_var);
205
206   my $custom_variables = selectall_hashref_query($form, $dbh, $q_cfg, $params{module});
207
208   foreach my $cvar (@{ $custom_variables }) {
209     if ($cvar->{type} eq 'textfield') {
210       $cvar->{width}  = 30;
211       $cvar->{height} =  5;
212
213       $cvar->{width}  = $1 if ($cvar->{options} =~ m/width=(\d+)/i);
214       $cvar->{height} = $1 if ($cvar->{options} =~ m/height=(\d+)/i);
215
216     } elsif ($cvar->{type} eq 'text') {
217       $cvar->{maxlength} = $1 if ($cvar->{options} =~ m/maxlength=(\d+)/i);
218
219     } elsif ($cvar->{type} eq 'number') {
220       $cvar->{precision} = $1 if ($cvar->{options} =~ m/precision=(\d+)/i);
221
222     } elsif ($cvar->{type} eq 'select') {
223       $cvar->{OPTIONS} = [ map { { 'value' => $_ } } split(m/\#\#/, $cvar->{options}) ];
224     }
225
226     my $act_var;
227     if ($params{trans_id}) {
228       my @values = (conv_i($cvar->{id}), conv_i($params{trans_id}));
229       push @values, $params{sub_module} if $params{sub_module};
230
231       do_statement($form, $h_var, $q_var, @values);
232       $act_var = $h_var->fetchrow_hashref();
233
234       $act_var->{valid} = $self->get_custom_variables_validity(config_id => $cvar->{id}, trans_id => $params{trans_id});
235     }
236
237     if ($act_var) {
238       $cvar->{value} = $cvar->{type} eq 'date'      ? $act_var->{date_value}
239                      : $cvar->{type} eq 'timestamp' ? $act_var->{timestamp_value}
240                      : $cvar->{type} eq 'number'    ? $act_var->{number_value}
241                      : $cvar->{type} eq 'bool'      ? $act_var->{bool_value}
242                      :                                $act_var->{text_value};
243       $cvar->{valid} = $act_var->{valid};
244     } else {
245       if ($cvar->{type} eq 'date') {
246         if ($cvar->{default_value} eq 'NOW') {
247           $cvar->{value} = $cvar->{current_date};
248         } else {
249           $cvar->{value} = $cvar->{default_value};
250         }
251
252       } elsif ($cvar->{type} eq 'timestamp') {
253         if ($cvar->{default_value} eq 'NOW') {
254           $cvar->{value} = $cvar->{current_timestamp};
255         } else {
256           $cvar->{value} = $cvar->{default_value};
257         }
258
259       } elsif ($cvar->{type} eq 'bool') {
260         $cvar->{value} = $cvar->{default_value} * 1;
261
262       } elsif ($cvar->{type} eq 'number') {
263         $cvar->{value} = $cvar->{default_value} * 1 if ($cvar->{default_value} ne '');
264
265       } else {
266         $cvar->{value} = $cvar->{default_value};
267       }
268     }
269
270     if ($cvar->{type} eq 'number') {
271       $cvar->{value} = $form->format_amount($myconfig, $cvar->{value} * 1, $cvar->{precision});
272     }
273   }
274
275   $h_var->finish();
276
277   $main::lxdebug->leave_sub();
278
279   return $custom_variables;
280 }
281
282 sub save_custom_variables {
283   $main::lxdebug->enter_sub();
284
285   my $self     = shift;
286   my %params   = @_;
287
288   Common::check_params(\%params, qw(module trans_id variables));
289
290   my $myconfig = \%main::myconfig;
291   my $form     = $main::form;
292
293   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
294
295   my @configs  = $params{configs} ? @{ $params{configs} } : grep { $_->{module} eq $params{module} } @{ CVar->get_configs() };
296
297   my $query    =
298     qq|DELETE FROM custom_variables
299        WHERE (trans_id  = ?)
300          AND (config_id IN (SELECT DISTINCT id
301                             FROM custom_variable_configs
302                             WHERE module = ?))|;
303   my @values   = (conv_i($params{trans_id}), $params{module});
304
305   if ($params{sub_module}) {
306     $query .= qq| AND (sub_module = ?)|;
307     push @values, $params{sub_module};
308   }
309
310   do_query($form, $dbh, $query, @values);
311
312   $query  =
313     qq|INSERT INTO custom_variables (config_id, sub_module, trans_id, bool_value, timestamp_value, text_value, number_value)
314        VALUES                       (?,         ?,          ?,        ?,          ?,               ?,          ?)|;
315   my $sth = prepare_query($form, $dbh, $query);
316
317   foreach my $config (@configs) {
318     my @values = (conv_i($config->{id}), "$params{sub_module}", conv_i($params{trans_id}));
319
320     my $value  = $params{variables}->{"$params{name_prefix}cvar_$config->{name}$params{name_postfix}"};
321
322     if (($config->{type} eq 'text') || ($config->{type} eq 'textfield') || ($config->{type} eq 'select')) {
323       push @values, undef, undef, $value, undef;
324
325     } elsif (($config->{type} eq 'date') || ($config->{type} eq 'timestamp')) {
326       push @values, undef, conv_date($value), undef, undef;
327
328     } elsif ($config->{type} eq 'number') {
329       push @values, undef, undef, undef, conv_i($form->parse_amount($myconfig, $value));
330
331     } elsif ($config->{type} eq 'bool') {
332       push @values, $value ? 't' : 'f', undef, undef, undef;
333     }
334
335     do_statement($form, $sth, $query, @values);
336
337     $self->save_custom_variables_validity(trans_id => $params{trans_id}, config_id => $config->{id},
338       validity => ($params{variables}->{"$params{name_prefix}cvar_$config->{name}$params{name_postfix}_valid"} ? 1 : 0)
339     );
340   }
341
342   $sth->finish();
343
344   $dbh->commit();
345
346   $main::lxdebug->leave_sub();
347 }
348
349 sub render_inputs {
350   $main::lxdebug->enter_sub();
351
352   my $self     = shift;
353   my %params   = @_;
354
355   Common::check_params(\%params, qw(variables));
356
357   my $myconfig = \%main::myconfig;
358   my $form     = $main::form;
359
360   my %options  = ( name_prefix       => "$params{name_prefix}",
361                    name_postfix      => "$params{name_postfix}",
362                    hide_non_editable => $params{hide_non_editable},
363                  );
364
365   foreach my $var (@{ $params{variables} }) {
366     $var->{HTML_CODE} = $form->parse_html_template('amcvar/render_inputs', { 'var' => $var, %options });
367     $var->{VALID_BOX} = "<input type=checkbox name='$options{name_prefix}cvar_$var->{name}$options{name_postfix}_valid'@{[!$var->{valid} ? ' checked' : '']}>";
368   }
369
370   $main::lxdebug->leave_sub();
371 }
372
373 sub render_search_options {
374   $main::lxdebug->enter_sub();
375
376   my $self     = shift;
377   my %params   = @_;
378
379   Common::check_params(\%params, qw(variables));
380
381   my $myconfig = \%main::myconfig;
382   my $form     = $main::form;
383
384   $params{include_prefix}   = 'l_' unless defined($params{include_prefix});
385   $params{include_value}  ||= '1';
386
387   my $filter  = $form->parse_html_template('amcvar/search_filter',  \%params);
388   my $include = $form->parse_html_template('amcvar/search_include', \%params);
389
390   $main::lxdebug->leave_sub();
391
392   return ($filter, $include);
393 }
394
395 sub build_filter_query {
396   $main::lxdebug->enter_sub();
397
398   my $self     = shift;
399   my %params   = @_;
400
401   Common::check_params(\%params, qw(module trans_id_field filter));
402
403   my $myconfig = \%main::myconfig;
404   my $form     = $main::form;
405
406   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
407
408   my $configs  = $self->get_configs(%params);
409
410   my (@where, @values);
411
412   foreach my $config (@{ $configs }) {
413     next unless ($config->{searchable});
414
415     my $name = "cvar_$config->{name}";
416
417     my (@sub_values, @sub_where, $not);
418
419     if (($config->{type} eq 'text') || ($config->{type} eq 'textfield')) {
420       next unless ($params{filter}->{$name});
421
422       push @sub_where,  qq|cvar.text_value ILIKE ?|;
423       push @sub_values, '%' . $params{filter}->{$name} . '%'
424
425     } elsif ($config->{type} eq 'select') {
426       next unless ($params{filter}->{$name});
427
428       push @sub_where,  qq|cvar.text_value = ?|;
429       push @sub_values, $params{filter}->{$name};
430
431     } elsif (($config->{type} eq 'date') || ($config->{type} eq 'timestamp')) {
432       my $name_from = "${name}_from";
433       my $name_to   = "${name}_to";
434
435       if ($params{filter}->{$name_from}) {
436         push @sub_where,  qq|cvar.timestamp_value >= ?|;
437         push @sub_values, conv_date($params{filter}->{$name_from});
438       }
439
440       if ($params{filter}->{$name_to}) {
441         push @sub_where,  qq|cvar.timestamp_value <= ?|;
442         push @sub_values, conv_date($params{filter}->{$name_to});
443       }
444
445     } elsif ($config->{type} eq 'number') {
446       next if ($params{filter}->{$name} eq '');
447
448       my $f_op = $params{filter}->{"${name}_qtyop"};
449
450       my $op;
451       if ($f_op eq '==') {
452         $op  = '=';
453
454       } elsif ($f_op eq '=/=') {
455         $not = 'NOT';
456         $op  = '<>';
457
458       } elsif ($f_op eq '<') {
459         $not = 'NOT';
460         $op  = '>=';
461
462       } elsif ($f_op eq '<=') {
463         $not = 'NOT';
464         $op  = '>';
465
466       } elsif (($f_op eq '>') || ($f_op eq '>=')) {
467         $op  = $f_op;
468
469       } else {
470         $op  = '=';
471       }
472
473       push @sub_where,  qq|cvar.number_value $op ?|;
474       push @sub_values, $form->parse_amount($myconfig, $params{filter}->{$name});
475
476     } elsif ($config->{type} eq 'bool') {
477       next unless ($params{filter}->{$name});
478
479       $not = 'NOT' if ($params{filter}->{$name} eq 'no');
480       push @sub_where,  qq|COALESCE(cvar.bool_value, false) = TRUE|;
481     }
482
483     if (@sub_where) {
484       push @sub_where,  qq|cvar.sub_module = ?|;
485       push @sub_values, "$params{sub_module}";
486
487       push @where,
488         qq|$not EXISTS(
489              SELECT cvar.id
490              FROM custom_variables cvar
491              LEFT JOIN custom_variable_configs cvarcfg ON (cvar.config_id = cvarcfg.id)
492              WHERE (cvarcfg.module = ?)
493                AND (cvarcfg.id     = ?)
494                AND (cvar.trans_id  = $params{trans_id_field})
495                AND | . join(' AND ', map { "($_)" } @sub_where) . qq|)|;
496       push @values, $params{module}, conv_i($config->{id}), @sub_values;
497     }
498   }
499
500   my $query = join ' AND ', @where;
501
502   $main::lxdebug->leave_sub();
503
504   return ($query, @values);
505 }
506
507 sub add_custom_variables_to_report {
508   $main::lxdebug->enter_sub();
509
510   my $self      = shift;
511   my %params    = @_;
512
513   Common::check_params(\%params, qw(module trans_id_field column_defs data configs));
514
515   my $myconfig  = \%main::myconfig;
516   my $form      = $main::form;
517   my $locale    = $main::locale;
518
519   my $dbh       = $params{dbh} || $form->get_standard_dbh($myconfig);
520
521   my $configs   = [ grep { $_->{includeable} && $params{column_defs}->{"cvar_$_->{name}"}->{visible} } @{ $params{configs} } ];
522
523   if (!scalar(@{ $params{data} }) || ! scalar(@{ $configs })) {
524     $main::lxdebug->leave_sub();
525     return;
526   }
527
528   # allow sub_module to be a coderef or a fixed value
529   if (ref $params{sub_module} ne 'CODE') {
530     $params{sub_module} = sub { "$params{sub_module}" };
531   }
532
533   my %cfg_map   = map { $_->{id} => $_ } @{ $configs };
534   my @cfg_ids   = keys %cfg_map;
535
536   my $query     =
537     qq|SELECT text_value, timestamp_value, timestamp_value::date AS date_value, number_value, bool_value, config_id
538        FROM custom_variables
539        WHERE (config_id IN (| . join(', ', ('?') x scalar(@cfg_ids)) . qq|))
540          AND (trans_id = ?)
541          AND (sub_module = ?)|;
542   my $sth       = prepare_query($form, $dbh, $query);
543
544   foreach my $row (@{ $params{data} }) {
545     do_statement($form, $sth, $query, @cfg_ids, conv_i($row->{$params{trans_id_field}}), $params{sub_module}->($row));
546
547     while (my $ref = $sth->fetchrow_hashref()) {
548       my $cfg = $cfg_map{$ref->{config_id}};
549
550       $row->{"cvar_$cfg->{name}"} =
551           $cfg->{type} eq 'date'      ? $ref->{date_value}
552         : $cfg->{type} eq 'timestamp' ? $ref->{timestamp_value}
553         : $cfg->{type} eq 'number'    ? $form->format_amount($myconfig, $ref->{number_value} * 1, $cfg->{precision})
554         : $cfg->{type} eq 'bool'      ? ($ref->{bool_value} ? $locale->text('Yes') : $locale->text('No'))
555         :                               $ref->{text_value};
556     }
557   }
558
559   $sth->finish();
560
561   $main::lxdebug->leave_sub();
562 }
563
564 sub get_field_format_list {
565   $main::lxdebug->enter_sub();
566
567   my $self          = shift;
568   my %params        = @_;
569
570   Common::check_params(\%params, qw(module));
571
572   my $myconfig      = \%main::myconfig;
573   my $form          = $main::form;
574
575   my $dbh           = $params{dbh} || $form->get_standard_dbh($myconfig);
576
577   my $configs       = $self->get_configs(%params);
578
579   my $date_fields   = [];
580   my $number_fields = {};
581
582   foreach my $config (@{ $configs }) {
583     my $name = "$params{prefix}cvar_$config->{name}";
584
585     if ($config->{type} eq 'date') {
586       push @{ $date_fields }, $name;
587
588     } elsif ($config->{type} eq 'number') {
589       $number_fields->{$config->{precision}} ||= [];
590       push @{ $number_fields->{$config->{precision}} }, $name;
591     }
592   }
593
594   $main::lxdebug->leave_sub();
595
596   return ($date_fields, $number_fields);
597 }
598
599 =head2 VALIDITY
600
601 Suppose the following scenario:
602
603 You have a lot of parts in your database, and a set of properties cofigured. 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.
604
605 Validity is assumed. If you modify validity, you actually save B<invalidity>.
606 validity is saved as a function of config_id, and the trans_id
607
608 In the naive way, disable an attribute for a specific id (simple)
609
610 =cut
611 sub save_custom_variables_validity {
612   $main::lxdebug->enter_sub();
613
614   my $self     = shift;
615   my %params   = @_;
616
617   Common::check_params(\%params, qw(config_id trans_id validity));
618
619   my $myconfig = \%main::myconfig;
620   my $form     = $main::form;
621
622   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
623
624   my (@where, @values);
625   add_token(\@where, \@values, col => "config_id", val => $params{config_id}, esc => \&conv_i);
626   add_token(\@where, \@values, col => "trans_id",  val => $params{trans_id},  esc => \&conv_i);
627
628   my $where = scalar @where ? "WHERE " . join ' AND ', @where : '';
629   my $query = qq|DELETE FROM custom_variables_validity $where|;
630
631   do_query($form, $dbh, $query, @values);
632
633   $query  =
634     qq|INSERT INTO custom_variables_validity (config_id, trans_id)
635        VALUES                                (?,         ?       )|;
636   my $sth = prepare_query($form, $dbh, $query);
637
638   unless ($params{validity}) {
639     foreach my $config_id (listify $params{config_id}) {
640       foreach my $trans_id (listify $params{trans_id}) {
641         do_statement($form, $sth, $query, conv_i($config_id), conv_i($trans_id));
642       }
643     }
644   }
645
646   $sth->finish();
647
648   $dbh->commit();
649
650   $main::lxdebug->leave_sub();
651 }
652
653 sub get_custom_variables_validity {
654   $main::lxdebug->enter_sub();
655
656   my $self     = shift;
657   my %params   = @_;
658
659   Common::check_params(\%params, qw(config_id trans_id));
660
661   my $myconfig = \%main::myconfig;
662   my $form     = $main::form;
663
664   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
665
666   my $query    = qq|SELECT COUNT(*) FROM custom_variables_validity WHERE config_id = ? AND trans_id = ?|;
667
668   my ($validity) = selectfirst_array_query($form, $dbh, $query, conv_i($params{config_id}), conv_i($params{trans_id}));
669
670   $main::lxdebug->leave_sub();
671
672   return $validity;
673 }
674
675 1;