1 use Test::More tests => 66;
10 use Support::TestSetup;
12 use Test::Deep qw(cmp_bag);
13 use List::Util qw(max);
15 use SL::DB::Buchungsgruppe;
21 use SL::DB::DeliveryOrder;
26 my ($customer, $currency_id, $buchungsgruppe, $employee, $vendor, $taxzone);
27 my ($link, $links, $o1, $o2, $d, $i);
30 SL::DB::Manager::DeliveryOrder->delete_all(all => 1);
31 SL::DB::Manager::Order->delete_all(all => 1);
32 SL::DB::Manager::Invoice->delete_all(all => 1);
33 SL::DB::Manager::Part->delete_all(all => 1);
34 SL::DB::Manager::Customer->delete_all(all => 1);
35 SL::DB::Manager::Vendor->delete_all(all => 1);
41 $params{$_} ||= {} for qw(buchungsgruppe unit customer part tax);
45 $buchungsgruppe = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 19%', %{ $params{buchungsgruppe} }) || croak "No accounting group";
46 $employee = SL::DB::Manager::Employee->current || croak "No employee";
47 $taxzone = SL::DB::Manager::TaxZone->find_by( description => 'Inland') || croak "No taxzone";
49 $currency_id = $::instance_conf->get_currency_id;
51 $customer = SL::DB::Customer->new(
52 name => 'Test Customer',
53 currency_id => $currency_id,
54 taxzone_id => $taxzone->id,
55 %{ $params{customer} }
58 $vendor = SL::DB::Vendor->new(
59 name => 'Test Vendor',
60 currency_id => $currency_id,
61 taxzone_id => $taxzone->id,
69 return SL::DB::Order->new(
70 customer_id => $customer->id,
71 currency_id => $currency_id,
72 employee_id => $employee->id,
73 salesman_id => $employee->id,
74 taxzone_id => $taxzone->id,
80 sub new_delivery_order {
83 return SL::DB::DeliveryOrder->new(
84 customer_id => $customer->id,
85 currency_id => $currency_id,
86 employee_id => $employee->id,
87 salesman_id => $employee->id,
88 taxzone_id => $taxzone->id,
96 return SL::DB::Invoice->new(
97 customer_id => $customer->id,
98 currency_id => $currency_id,
99 employee_id => $employee->id,
100 salesman_id => $employee->id,
101 gldate => DateTime->today_local->to_kivitendo,
103 taxzone_id => $taxzone->id,
109 Support::TestSetup::login();
116 $link = $o1->link_to_record($i);
119 is ref $link, 'SL::DB::RecordLink', 'link_to_record returns new link';
120 is $link->from_table, 'oe', 'from_table';
121 is $link->from_id, $o1->id, 'from_id';
122 is $link->to_table, 'ar', 'to_table';
123 is $link->to_id, $i->id, 'to_id';
126 $links = $o1->linked_records;
127 is $links->[0]->id, $i->id, 'simple retrieve';
129 $links = $o1->linked_records(direction => 'to', to => 'Invoice');
130 is $links->[0]->id, $i->id, 'direct retrieve 1';
132 $links = $o1->linked_records(direction => 'to', to => 'SL::DB::Invoice');
133 is $links->[0]->id, $i->id, 'direct retrieve 2 (with SL::DB::)';
135 $links = $o1->linked_records(direction => 'to', to => [ 'Invoice', 'Order' ]);
136 is $links->[0]->id, $i->id, 'direct retrieve 3 (array target)';
138 $links = $o1->linked_records(direction => 'both', both => 'Invoice');
139 is $links->[0]->id, $i->id, 'direct retrieve 4 (direction both)';
141 $links = $i->linked_records(direction => 'from', from => 'Order');
142 is $links->[0]->id, $o1->id, 'direct retrieve 4 (direction from)';
144 # what happens if we delete a linked record?
147 $links = $i->linked_records(direction => 'from', from => 'Order');
148 is @$links, 0, 'no dangling link after delete';
150 # can we distinguish between types?
151 $o1 = new_order(quotation => 1);
153 $o1->link_to_record($o2);
155 $links = $o2->linked_records(direction => 'from', from => 'Order', query => [ quotation => 1 ]);
156 is $links->[0]->id, $o1->id, 'query restricted retrieve 1';
158 $links = $o2->linked_records(direction => 'from', from => 'Order', query => [ quotation => 0 ]);
159 is @$links, 0, 'query restricted retrieve 2';
161 # try bidirectional linking
164 $o1->link_to_record($o2, bidirectional => 1);
166 $links = $o1->linked_records(direction => 'to', to => 'Order');
167 is $links->[0]->id, $o2->id, 'bidi 1';
168 $links = $o1->linked_records(direction => 'from', from => 'Order');
169 is $links->[0]->id, $o2->id, 'bidi 2';
170 $links = $o1->linked_records(direction => 'both', both => 'Order');
171 is $links->[0]->id, $o2->id, 'bidi 3';
173 # funky stuff with both
175 $d = new_delivery_order();
178 $o2->link_to_record($d);
179 $d->link_to_record($i);
180 # at this point the structure is:
182 # o1 <--> o2 ---> d ---> i
185 $links = $d->linked_records(direction => 'both', to => 'Invoice', from => 'Order', sort_by => 'customer_id', sort_dir => 1);
186 is $links->[0]->id, $o2->id, 'both with different from/to 1';
187 is $links->[1]->id, $i->id, 'both with different from/to 2';
189 # what happens if we double link?
191 $o2->link_to_record($d);
193 $links = $o2->linked_records(direction => 'to', to => 'DeliveryOrder');
194 is @$links, 1, 'double link is only added once 1';
196 $d->link_to_record($o2, bidirectional => 1);
197 # at this point the structure is:
199 # o1 <--> o2 <--> d ---> i
202 $links = $o2->linked_records(direction => 'to', to => 'DeliveryOrder');
203 is @$links, 1, 'double link is only added once 2';
205 # doc states that to/from ae optional. test that
206 $links = $o2->linked_records(direction => 'both');
207 is @$links, 2, 'links without from/to get all';
209 # doc states you can limit with direction when giving excess params
210 $links = $d->linked_records(direction => 'to', to => 'Invoice', from => 'Order');
211 is $links->[0]->id, $i->id, 'direction to limit params 1';
212 is @$links, 1, 'direction to limit params 2';
214 # doc says there will be special values set... lets see
215 $links = $o1->linked_records(direction => 'to', to => 'Order');
216 is $links->[0]->{_record_link_direction}, 'to', '_record_link_direction to';
217 is $links->[0]->{_record_link}->to_id, $o2->id, '_record_link to';
219 $links = $o1->linked_records(direction => 'from', from => 'Order');
220 is $links->[0]->{_record_link_direction}, 'from', '_record_link_direction from';
221 is $links->[0]->{_record_link}->to_id, $o1->id, '_record_link from';
223 # check if bidi returns an array of links even if aready existing
224 my @links = $d->link_to_record($o2, bidirectional => 1);
225 # at this point the structure is:
227 # o1 <--> o2 <--> d ---> i
229 is @links, 2, 'bidi returns array of links in array context';
232 $links = $o2->linked_records(direction => 'to', to => 'Invoice', via => 'DeliveryOrder');
233 is $links->[0]->id, $i->id, 'simple case via links (string)';
235 $links = $o2->linked_records(direction => 'to', to => 'Invoice', via => [ 'DeliveryOrder' ]);
236 is $links->[0]->id, $i->id, 'simple case via links (arrayref)';
238 $links = $o1->linked_records(direction => 'to', to => 'Invoice', via => [ 'Order', 'DeliveryOrder' ]);
239 is $links->[0]->id, $i->id, 'simple case via links (2 hops)';
241 # multiple links in the same direction from one object
242 $o1->link_to_record($d);
243 # at this point the structure is:
245 # o1 <--> o2 <--> d ---> i
249 $links = $o2->linked_records(direction => 'to', to => 'Invoice', via => 'DeliveryOrder');
250 is $links->[0]->id, $i->id, 'simple case via links (string)';
253 # o1 must have 2 linked records now:
254 $links = $o1->linked_records(direction => 'to');
255 is @$links, 2, 'more than one link';
257 # as a special funny case, o1 via Order, Order will now yield o2, because it bounces back over itself
258 { local $TODO = 'no idea if this is desired';
259 $links = $o2->linked_records(direction => 'to', to => 'Order', via => [ 'Order', 'Order' ]);
260 is @$links, 2, 'via links with bidirectional hop over starting object';
263 # for sorting, get all don't bother with the links, we'll just take our records
264 my @records = ($o2, $i, $o1, $d);
266 $sorted = SL::DB::Helper::LinkedRecords->sort_linked_records('type', 1, @records);
267 is_deeply $sorted, [$o1, $o2, $d, $i], 'sorting by type';
268 $sorted = SL::DB::Helper::LinkedRecords->sort_linked_records('type', 0, @records);
269 is_deeply $sorted, [$i, $d, $o2, $o1], 'sorting by type desc';
276 $sorted = SL::DB::Helper::LinkedRecords->sort_linked_records('number', 1, @records);
277 is_deeply $sorted, [$d, $o1, $i, $o2], 'sorting by number';
278 $sorted = SL::DB::Helper::LinkedRecords->sort_linked_records('number', 0, @records);
279 is_deeply $sorted, [$o2, $i, $o1, $d], 'sorting by number desc';
281 # again with natural sorting
283 $o1->ordnumber("a3");
285 $o2->ordnumber("a10");
287 $sorted = SL::DB::Helper::LinkedRecords->sort_linked_records('number', 1, @records);
288 is_deeply $sorted, [$d, $o1, $i, $o2], 'sorting naturally by number';
289 $sorted = SL::DB::Helper::LinkedRecords->sort_linked_records('number', 0, @records);
290 is_deeply $sorted, [$o2, $i, $o1, $d], 'sorting naturally by number desc';
292 $o2->transdate(DateTime->new(year => 2010, month => 3, day => 1));
293 $i->transdate(DateTime->new(year => 2014, month => 3, day => 19));
294 $o1->transdate(DateTime->new(year => 2014, month => 5, day => 1));
295 $d->transdate(DateTime->new(year => 2014, month => 5, day => 2));
297 # transdate should be used before itime
298 $sorted = SL::DB::Helper::LinkedRecords->sort_linked_records('date', 1, @records);
299 is_deeply $sorted, [$o2, $i, $o1, $d], 'sorting by transdate';
300 $sorted = SL::DB::Helper::LinkedRecords->sort_linked_records('date', 0, @records);
301 is_deeply $sorted, [$d, $o1, $i, $o2], 'sorting by transdate desc';
303 # now recursive stuff 2, with backlinks
304 $links = $o1->linked_records(direction => 'to', recursive => 1, save_path => 1);
305 is @$links, 4, 'recursive finds all 4 (backlink to self because of bidi o1<->o2)';
307 # because of the link o1->d the longest path should be legth 2. test that
308 is max(map { $_->{_record_link_depth} } @$links), 2, 'longest path is 2';
310 $links = $o2->linked_records(direction => 'to', recursive => 1);
311 is @$links, 4, 'recursive from o2 finds 4';
313 $links = $o1->linked_records(direction => 'from', recursive => 1, save_path => 1);
314 is @$links, 3, 'recursive from o1 finds 3 (not i)';
316 $links = $i->linked_records(direction => 'from', recursive => 1, save_path => 1);
317 is @$links, 3, 'recursive from i finds 3 (not i)';
319 $links = $o1->linked_records(direction => 'both', recursive => 1, save_path => 1);
320 is @$links, 4, 'recursive dir=both does not give duplicates';
332 my $i1 = new_invoice();
333 my $i2 = new_invoice();
335 $o1->link_to_record($i1);
336 $o2->link_to_record($i2);
338 $links = $o1->linked_records(direction => 'to', to => 'Invoice', batch => [ $o1->id, $o2->id ]);
339 is_deeply [ map { $_->id } @$links ], [ $i1->id , $i2->id ], "batch works";
341 $links = $o1->linked_records(direction => 'to', recursive => 1, batch => [ $o1->id, $o2->id ]);
342 cmp_bag [ map { $_->id } @$links ], [ $i1->id , $i2->id ], "batch works recursive";
344 $links = $o1->linked_records(direction => 'to', to => 'Invoice', batch => [ $o1->id, $o2->id ], by_id => 1);
345 # $::lxdebug->dump(0, "links", $links);
346 is @{ $links->{$o1->id} }, 1, "batch by_id 1";
347 is @{ $links->{$o2->id} }, 1, "batch by_id 2";
348 is keys %$links, 2, "batch by_id 3";
349 is $links->{$o1->id}[0]->id, $i1->id, "batch by_id 4";
350 is $links->{$o2->id}[0]->id, $i2->id, "batch by_id 5";
352 $links = $o1->linked_records(direction => 'to', recursive => 1, batch => [ $o1->id, $o2->id ], by_id => 1);
353 is @{ $links->{$o1->id} }, 1, "batch recursive by_id 1";
354 is @{ $links->{$o2->id} }, 1, "batch recursive by_id 2";
355 is keys %$links, 2, "batch recursive by_id 3";
356 is $links->{$o1->id}[0]->id, $i1->id, "batch recursive by_id 4";
357 is $links->{$o2->id}[0]->id, $i2->id, "batch recursive by_id 5";
359 $links = $o1->linked_records(direction => 'both', recursive => 1, batch => [ $o1->id, $o2->id ], by_id => 1);
360 is @{ $links->{$o1->id} }, 1, "batch recursive by_id direction both 1";
361 is @{ $links->{$o2->id} }, 1, "batch recursive by_id direction both 2";
362 is keys %$links, 2, "batch recursive by_id direction both 3";
363 is $links->{$o1->id}[0]->id, $i1->id, "batch recursive by_id direction both 4";
364 is $links->{$o2->id}[0]->id, $i2->id, "batch recursive by_id direction both 5";