epic-s6ts
[kivitendo-erp.git] / SL / RecordLinks.pm
1 package RecordLinks;
2
3 use utf8;
4 use strict;
5
6 use SL::Common;
7 use SL::DBUtils;
8 use Data::Dumper;
9 use List::Util qw(reduce);
10 use SL::DB;
11
12 sub create_links {
13   $main::lxdebug->enter_sub();
14
15   my $self     = shift;
16   my %params   = @_;
17
18   if ($params{mode} && ($params{mode} eq 'ids')) {
19     Common::check_params_x(\%params, [ qw(from_ids to_ids) ]);
20
21   } else {
22     Common::check_params(\%params, qw(links));
23
24   }
25
26   my @links;
27
28   if ($params{mode} && ($params{mode} eq 'ids')) {
29     my ($from_to, $to_from) = $params{from_ids} ? qw(from to) : qw(to from);
30     my %ids;
31
32     if ('ARRAY' eq ref $params{"${from_to}_ids"}) {
33       $ids{$from_to} = $params{"${from_to}_ids"};
34     } else {
35       $ids{$from_to} = [ grep { $_ } map { $_ * 1 } split m/\s+/, $params{"${from_to}_ids"} ];
36     }
37
38     if (my $num = scalar @{ $ids{$from_to} }) {
39       $ids{$to_from} = [ ($params{"${to_from}_id"}) x $num ];
40       @links         = map { { 'from_table' => $params{from_table},
41                                'from_id'    => $ids{from}->[$_],
42                                'to_table'   => $params{to_table},
43                                'to_id'      => $ids{to}->[$_],      } } (0 .. $num - 1);
44     }
45
46   } else {
47     @links = @{ $params{links} };
48   }
49
50   if (!scalar @links) {
51     $main::lxdebug->leave_sub();
52     return;
53   }
54
55   my $myconfig = \%main::myconfig;
56   my $form     = $main::form;
57
58   SL::DB->client->with_transaction(sub {
59     my $dbh      = $params{dbh} || SL::DB->client->dbh;
60
61     my $query    = qq|INSERT INTO record_links (from_table, from_id, to_table, to_id) VALUES (?, ?, ?, ?)|;
62     my $sth      = prepare_query($form, $dbh, $query);
63
64     foreach my $link (@links) {
65       next if ('HASH' ne ref $link);
66       next if (!$link->{from_table} || !$link->{from_id} || !$link->{to_table} || !$link->{to_id});
67
68       do_statement($form, $sth, $query, $link->{from_table}, conv_i($link->{from_id}), $link->{to_table}, conv_i($link->{to_id}));
69     }
70
71     1;
72   }) or do { die SL::DB->client->error };
73
74   $main::lxdebug->leave_sub();
75 }
76
77 sub get_links {
78   $main::lxdebug->enter_sub();
79
80   my $self     = shift;
81   my %params   = @_;
82
83   Common::check_params(\%params, [ qw(from_table from_id to_table to_id) ]);
84
85   my $myconfig   = \%main::myconfig;
86   my $form       = $main::form;
87
88   my $dbh        = $params{dbh} || $form->get_standard_dbh($myconfig);
89
90   my @conditions = ();
91   my @values     = ();
92
93   foreach my $col (qw(from_table from_id to_table to_id)) {
94     next unless ($params{$col});
95
96     if ('ARRAY' eq ref $params{$col}) {
97       push @conditions, "$col IN (" . join(', ', ('?') x scalar(@{ $params{$col} })) . ")";
98       push @values,     $col =~ m/table/ ? @{ $params{$col} } : map { conv_i($_) } @{ $params{$col} };
99
100     } else {
101       push @conditions, "$col = ?";
102       push @values,     $col =~ m/table/ ? $params{$col} : conv_i($params{$col});
103     }
104   }
105
106   my $query = qq|SELECT from_table, from_id, to_table, to_id
107                  FROM record_links|;
108
109   if (scalar @conditions) {
110     $query .= qq| WHERE | . join(' AND ', map { "($_)" } @conditions);
111   }
112
113   my $links = selectall_hashref_query($form, $dbh, $query, @values);
114
115   $main::lxdebug->leave_sub();
116
117   return wantarray ? @{ $links } : $links;
118 }
119
120 sub get_links_via {
121   $main::lxdebug->enter_sub();
122
123   use SL::MoreCommon;
124   use Data::Dumper;
125
126   my $self     = shift;
127   my %params   = @_;
128
129   Common::check_params(\%params, [ qw(from_table from_id to_table to_id) ]);
130   Common::check_params(\%params, "via");
131
132   my @hops = ref $params{via} eq 'ARRAY'
133            ? @{ $params{via} }
134            :    $params{via};
135   unshift @hops, +{ table => $params{from_table}, id => $params{from_id} };
136   push    @hops, +{ table => $params{to_table},   id => $params{to_id} };
137
138   my $myconfig   = \%main::myconfig;
139   my $form       = $main::form;
140
141   my $last_hop   = shift @hops;
142   my @links;
143   for my $hop (@hops) {
144
145     my @temp_links = $self->get_links(
146       from_table => $last_hop->{table},
147       from_id    => $last_hop->{id},
148       to_table   => $hop->{table},
149       to_id      => $hop->{id},
150     );
151
152     # short circuit if any of these are empty
153     return wantarray ? () : [] unless scalar @temp_links;
154
155     push @links, \@temp_links;
156     $last_hop  =  $hop;
157   }
158
159   my $result = reduce {
160     [
161       grep { $_ }
162       cross {
163         if (   $a->{to_table} eq $b->{from_table}
164             && $a->{to_id}    eq $b->{from_id} ) {
165           +{ from_table => $a->{from_table},
166              from_id    => $a->{from_id},
167              to_table   => $b->{to_table},
168              to_id      => $b->{to_id} }
169           }
170         } @{ $a }, @{ $b }
171     ]
172   } @links;
173
174   $main::lxdebug->leave_sub();
175
176   return wantarray ? @{ $result } : $result;
177 }
178
179 sub delete {
180   $main::lxdebug->enter_sub();
181
182   my $self     = shift;
183   my %params   = @_;
184
185   Common::check_params(\%params, [ qw(from_table from_id to_table to_id) ]);
186
187   my $myconfig   = \%main::myconfig;
188   my $form       = $main::form;
189
190   SL::DB->client->with_transaction(sub {
191     my $dbh        = $params{dbh} || SL::DB->client->dbh;
192
193     # content
194     my (@where_tokens, @where_values);
195
196     for my $col (qw(from_table from_id to_table to_id)) {
197       add_token(\@where_tokens, \@where_values, col => $col, val => $params{$col}) if $params{$col};
198     }
199
200     my $where = @where_tokens ? "WHERE ". join ' AND ', map { "($_)" } @where_tokens : '';
201     my $query = "DELETE FROM record_links $where";
202
203     do_query($form, $dbh, $query, @where_values);
204
205     1;
206   }) or die { SL::DB->client->error };
207
208   $main::lxdebug->leave_sub();
209 }
210
211 1;
212
213 __END__
214
215 =pod
216
217 =encoding utf8
218
219 =head1 NAME
220
221 SL::RecordLinks - Verlinkung von kivitendo Objekten.
222
223 =head1 SYNOPSIS
224
225   use SL::RecordLinks;
226
227   my @links = RecordLinks->get_links(
228     from_table => 'ar',
229     from_id    => 2,
230     to_table   => 'oe',
231   );
232   my @links = RecordLinks->get_links_via(
233     from_table => 'oe',
234     to_id      => '14',
235     via        => [
236       { id => 12 },
237       { id => 13},
238     ],
239   );
240
241   RecordLinks->create_links(
242     mode       => 'ids',
243     from_table => 'ar',
244     from_id    => 1,
245     to_table   => 'oe',
246     to_ids     => [4, 6, 9],
247   )
248   RecordLinks->create_links(@links);
249
250   delete
251
252 =head1 DESCRIPTION
253
254 Transitive RecordLinks mit get_links_via.
255
256 get_links_via erwartet den zusätzlichen parameter via. via ist ein
257 hashref mit den jeweils optionalen Einträgen table und id, die sich
258 genauso verhalten wie die from/to_table/id werte der get_links funktion.
259
260 Alternativ kann via auch ein Array dieser Hashes sein:
261
262   get_links_via(
263     from_table => 'oe',
264     from_id    => 1,
265     to_table   => 'ar',
266     via        => {
267       table      => 'delivery_orders'
268     },
269   )
270
271   get_links_via(
272     from_table => 'oe',
273     to_id      => '14',
274     via        => [
275       { id => 12 },
276       { id => 13},
277     ],
278   )
279
280 Die Einträge in einem via-Array werden exakt in dieser Reihenfolge
281 benutzt und sind nicht optional. Da obige Beispiel würde also die
282 Verknüpfung:
283
284   oe:11 -> ar:12 -> is:13 -> do:14
285
286 finden, nicht aber:
287
288   oe:11 -> ar:13 -> do:14
289
290 =cut