Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 964cc586

Von Sven Schöling vor mehr als 10 Jahren hinzugefügt

  • ID 964cc586548d39408468696e96c4aa0be1c547d3
  • Vorgänger 218aeb17
  • Nachfolger 931c8a63

LinkedRecords: Test und Doku Update

Unterschiede anzeigen:

SL/DB/Helper/LinkedRecords.pm
193 193

  
194 194
  my $today     = DateTime->today_local;
195 195
  my $date_xtor = sub {
196
      $_[0]->can('transdate_as_date') ? $_[0]->transdate_as_date
197
    : $_[0]->can('itime_as_date')     ? $_[0]->itime_as_date
196
      $_[0]->can('transdate_as_date') ? $_[0]->transdate
197
    : $_[0]->can('itime_as_date')     ? $_[0]->itime->clone->truncate(to => 'day')
198 198
    :                                   $today;
199 199
  };
200 200
  my $date_comparator = sub {
......
234 234

  
235 235
SL::DB::Helper::LinkedRecords - Mixin for retrieving linked records via the table C<record_links>
236 236

  
237
SYNOPSIS
238

  
239
  # In SL::DB::<Object>
240
  use SL::DB::Helper::LinkedRecords;
241

  
242
  # later in consumer code
243
  # retrieve all links
244
  my @linked_objects = $order->linked_records(
245
    direction => 'both',
246
  );
247

  
248
  # only links to Invoices
249
  my @linked_objects = $order->linked_records(
250
    direction => 'to',
251
    to        => 'Invoice',
252
  );
253

  
254
  # more than one target
255
  my @linked_objects = $order->linked_records(
256
    direction => 'to',
257
    to        => [ 'Invoice', 'Order' ],
258
  );
259

  
260
  # more than one direction
261
  my @linked_objects = $order->linked_records(
262
    direction => 'both',
263
    both      => 'Invoice',
264
  );
265

  
266
  # more than one direction and different targets
267
  my @linked_objects = $order->linked_records(
268
    direction => 'both',
269
    to        => 'Invoice',
270
    from      => 'Order',
271
  );
272

  
273
  # transitive over known classes
274
  my @linked_objects = $order->linked_records(
275
    direction => 'to',
276
    to        => 'Invoice',
277
    via       => 'DeliveryOrder',
278
  );
279

  
280
  # add a new link
281
  $order->link_to_record($invoice);
282
  $order->link_to_record($purchase_order, bidirectional => 1);
283

  
284

  
237 285
=head1 FUNCTIONS
238 286

  
239 287
=over 4
240 288

  
241 289
=item C<linked_records %params>
242 290

  
243
Retrieves records linked from or to C<$self> via the table
244
C<record_links>. The mandatory parameter C<direction> (either C<from>,
245
C<to> or C<both>) determines whether the function retrieves records
246
that link to C<$self> (for C<direction> = C<to>) or that are linked
247
from C<$self> (for C<direction> = C<from>). For C<direction = both>
248
all records linked from or to C<$self> are returned.
249

  
250
The optional parameter C<from> or C<to> (same as C<direction>)
251
contains the package names of Rose models for table limitation (the
252
prefix C<SL::DB::> is optional). It can be a single model name as a
253
single scalar or multiple model names in an array reference in which
254
case all links matching any of the model names will be returned.
255

  
256
The optional parameter C<via> can be used to retrieve all documents
257
that may have intermediate documents inbetween. It is an array
258
reference of Rose package names for the models that may be
259
intermediate link targets. One example is retrieving all invoices for
260
a given quotation no matter whether or not orders and delivery orders
261
have been created. If C<via> is given then C<from> or C<to> (depending
262
on C<direction>) must be given as well, and it must then not be an
291
Retrieves records linked from or to C<$self> via the table C<record_links>. The
292
mandatory parameter C<direction> (either C<from>, C<to> or C<both>) determines
293
whether the function retrieves records that link to C<$self> (for C<direction>
294
= C<to>) or that are linked from C<$self> (for C<direction> = C<from>). For
295
C<direction = both> all records linked from or to C<$self> are returned.
296

  
297
The optional parameter C<from> or C<to> (same as C<direction>) contains the
298
package names of Rose models for table limitation (the prefix C<SL::DB::> is
299
optional). It can be a single model name as a single scalar or multiple model
300
names in an array reference in which case all links matching any of the model
301
names will be returned.
302

  
303
The optional parameter C<via> can be used to retrieve all documents that may
304
have intermediate documents inbetween. It is an array reference of Rose package
305
names for the models that may be intermediate link targets. One example is
306
retrieving all invoices for a given quotation no matter whether or not orders
307
and delivery orders have been created. If C<via> is given then C<from> or C<to>
308
(depending on C<direction>) must be given as well, and it must then not be an
263 309
array reference.
264 310

  
265 311
Examples:
......
267 313
If you only need invoices created directly from an order C<$order> (no
268 314
delivery orders inbetween) then the call could look like this:
269 315

  
270
  my $invoices = $order->linked_records(direction => 'to',
271
                                        to        => 'Invoice');
316
  my $invoices = $order->linked_records(
317
    direction => 'to',
318
    to        => 'Invoice',
319
  );
272 320

  
273 321
Retrieving all invoices from a quotation no matter whether or not
274 322
orders or delivery orders where created:
275 323

  
276
  my $invoices = $quotation->linked_records(direction => 'to',
277
                                            to        => 'Invoice',
278
                                            via       => [ 'Order', 'DeliveryOrder' ]);
324
  my $invoices = $quotation->linked_records(
325
    direction => 'to',
326
    to        => 'Invoice',
327
    via       => [ 'Order', 'DeliveryOrder' ],
328
  );
279 329

  
280 330
The optional parameter C<query> can be used to limit the records
281 331
returned. The following call limits the earlier example to invoices
282 332
created today:
283 333

  
284
  my $invoices = $order->linked_records(direction => 'to',
285
                                        to        => 'Invoice',
286
                                        query     => [ transdate => DateTime->today_local ]);
334
  my $invoices = $order->linked_records(
335
    direction => 'to',
336
    to        => 'Invoice',
337
    query     => [ transdate => DateTime->today_local ],
338
  );
287 339

  
288 340
The optional parameters C<$params{sort_by}> and C<$params{sort_dir}>
289 341
can be used in order to sort the result. If C<$params{sort_by}> is
......
329 381
created with the roles of C<from> and C<to> reversed. This link will
330 382
also only be created if it doesn't exist already.
331 383

  
332
In scalar contenxt returns either the existing link or the newly
384
In scalar context returns either the existing link or the newly
333 385
created one as an instance of C<SL::DB::RecordLink>. In array context
334 386
it returns an array of links (one entry if C<$params{bidirectional}>
335 387
is falsish and two entries if it is trueish).
......
358 410

  
359 411
=item * C<date>
360 412

  
361
Sort by the date the record was created or applies to.
413
Sort by the transdate of the record was created or applies to.
362 414

  
363
=back
415
Note: If the latter has a default setting it will always mask the creation time.
364 416

  
365
Returns a hash reference.
417
=back
366 418

  
367
Can be called both as a class or as an instance function.
419
Returns an array reference.
368 420

  
369
This function is not exported.
421
Can only be called both as a class function since it is noe exported.
370 422

  
371 423
=back
372 424

  
t/db_helper/record_links.t
1
use Test::More;
2

  
3
use strict;
4

  
5
use lib 't';
6
use utf8;
7

  
8
use Carp;
9
use Data::Dumper;
10
use Support::TestSetup;
11
use Test::Exception;
12

  
13
use SL::DB::Buchungsgruppe;
14
use SL::DB::Currency;
15
use SL::DB::Customer;
16
use SL::DB::Employee;
17
use SL::DB::Invoice;
18
use SL::DB::Order;
19
use SL::DB::DeliveryOrder;
20
use SL::DB::Part;
21
use SL::DB::Unit;
22

  
23
my ($customer, $currency_id, $buchungsgruppe, $employee, $vendor);
24
my ($link, $links, $o1, $o2, $d, $i);
25

  
26
sub reset_state {
27
  my %params = @_;
28

  
29
  $params{$_} ||= {} for qw(buchungsgruppe unit customer part tax);
30

  
31
  SL::DB::Manager::DeliveryOrder->delete_all(all => 1);
32
  SL::DB::Manager::Order->delete_all(all => 1);
33
  SL::DB::Manager::Invoice->delete_all(all => 1);
34
  SL::DB::Manager::Customer->delete_all(all => 1);
35
  SL::DB::Manager::Vendor->delete_all(all => 1);
36

  
37
  $buchungsgruppe  = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 19%', %{ $params{buchungsgruppe} }) || croak "No accounting group";
38
  $employee        = SL::DB::Manager::Employee->current                                                                    || croak "No employee";
39

  
40
  $currency_id     = $::instance_conf->get_currency_id;
41

  
42
  $customer     = SL::DB::Customer->new(
43
    name        => 'Test Customer',
44
    currency_id => $currency_id,
45
    %{ $params{customer} }
46
  )->save;
47

  
48
  $vendor     = SL::DB::Vendor->new(
49
    name        => 'Test Vendor',
50
    currency_id => $currency_id,
51
    %{ $params{vendor} }
52
  )->save;
53
}
54

  
55
sub new_order {
56
  my %params  = @_;
57

  
58
  return SL::DB::Order->new(
59
    customer_id => $customer->id,
60
    currency_id => $currency_id,
61
    employee_id => $employee->id,
62
    salesman_id => $employee->id,
63
    taxzone_id  => 0,
64
    quotation   => 0,
65
    %params,
66
  )->save;
67
}
68

  
69
sub new_delivery_order {
70
  my %params  = @_;
71

  
72
  return SL::DB::DeliveryOrder->new(
73
    customer_id => $customer->id,
74
    currency_id => $currency_id,
75
    employee_id => $employee->id,
76
    salesman_id => $employee->id,
77
    taxzone_id  => 0,
78
    %params,
79
  )->save;
80
}
81

  
82
sub new_invoice {
83
  my %params  = @_;
84

  
85
  return SL::DB::Invoice->new(
86
    customer_id => $customer->id,
87
    currency_id => $currency_id,
88
    employee_id => $employee->id,
89
    salesman_id => $employee->id,
90
    gldate      => DateTime->today_local->to_kivitendo,
91
    taxzone_id  => 0,
92
    invoice     => 1,
93
    type        => 'invoice',
94
    %params,
95
  )->save;
96
}
97

  
98
Support::TestSetup::login();
99

  
100
reset_state();
101

  
102

  
103
$o1 = new_order();
104
$i  = new_invoice();
105

  
106
$link = $o1->link_to_record($i);
107

  
108
# try to add a link
109
is ref $link, 'SL::DB::RecordLink', 'link_to_record returns new link';
110
is $link->from_table, 'oe', 'from_table';
111
is $link->from_id, $o1->id, 'from_id';
112
is $link->to_table, 'ar', 'to_table';
113
is $link->to_id, $i->id, 'to_id';
114

  
115
# retrieve link
116
$links = $o1->linked_records(direction => 'to', to => 'Invoice');
117
is $links->[0]->id, $i->id, 'direct retrieve 1';
118

  
119
$links = $o1->linked_records(direction => 'to', to => 'SL::DB::Invoice');
120
is $links->[0]->id, $i->id, 'direct retrieve 2 (with SL::DB::)';
121

  
122
$links = $o1->linked_records(direction => 'to', to => [ 'Invoice', 'Order' ]);
123
is $links->[0]->id, $i->id, 'direct retrieve 3 (array target)';
124

  
125
$links = $o1->linked_records(direction => 'both', both => 'Invoice');
126
is $links->[0]->id, $i->id, 'direct retrieve 4 (direction both)';
127

  
128
$links = $i->linked_records(direction => 'from', from => 'Order');
129
is $links->[0]->id, $o1->id, 'direct retrieve 4 (direction from)';
130

  
131
# what happens if we delete a linked record?
132
$o1->delete;
133

  
134
$links = $i->linked_records(direction => 'from', from => 'Order');
135
is @$links, 0, 'no dangling link after delete';
136

  
137
# can we distinguish between types?
138
$o1 = new_order(quotation => 1);
139
$o2 = new_order();
140
$o1->link_to_record($o2);
141

  
142
$links = $o2->linked_records(direction => 'from', from => 'Order', query => [ quotation => 1 ]);
143
is $links->[0]->id, $o1->id, 'query restricted retrieve 1';
144

  
145
$links = $o2->linked_records(direction => 'from', from => 'Order', query => [ quotation => 0 ]);
146
is @$links, 0, 'query restricted retrieve 2';
147

  
148
# try bidirectional linking
149
$o1 = new_order();
150
$o2 = new_order();
151
$o1->link_to_record($o2, bidirectional => 1);
152

  
153
$links = $o1->linked_records(direction => 'to', to => 'Order');
154
is $links->[0]->id, $o2->id, 'bidi 1';
155
$links = $o1->linked_records(direction => 'from', from => 'Order');
156
is $links->[0]->id, $o2->id, 'bidi 2';
157
$links = $o1->linked_records(direction => 'both', both => 'Order');
158
is $links->[0]->id, $o2->id, 'bidi 3';
159

  
160
# funky stuff with both
161
#
162
$d = new_delivery_order();
163
$i = new_invoice();
164

  
165
$o2->link_to_record($d);
166
$d->link_to_record($i);
167

  
168

  
169
$links = $d->linked_records(direction => 'both', to => 'Invoice', from => 'Order', sort_by => 'customer_id', sort_dir => 1);
170
is $links->[0]->id, $o2->id, 'both with different from/to 1';
171
is $links->[1]->id, $i->id,  'both with different from/to 2';
172

  
173
# what happens if we double link?
174
#
175
$o2->link_to_record($d);
176

  
177
$links = $o2->linked_records(direction => 'to', to => 'DeliveryOrder');
178
is @$links, 1, 'double link is only added once 1';
179

  
180
$d->link_to_record($o2, bidirectional => 1);
181

  
182
$links = $o2->linked_records(direction => 'to', to => 'DeliveryOrder');
183
is @$links, 1, 'double link is only added once 2';
184

  
185
# doc states that to/from ae optional. test that
186
$links = $o2->linked_records(direction => 'both');
187
is @$links, 2, 'links without from/to get all';
188

  
189
# doc says there will be special values set... lets see
190
$links = $o1->linked_records(direction => 'to', to => 'Order');
191
is $links->[0]->{_record_link_direction}, 'to',  '_record_link_direction to';
192
is $links->[0]->{_record_link}->to_id, $o2->id,  '_record_link to';
193

  
194
$links = $o1->linked_records(direction => 'from', from => 'Order');
195
is $links->[0]->{_record_link_direction}, 'from',  '_record_link_direction from';
196
is $links->[0]->{_record_link}->to_id, $o1->id,  '_record_link from';
197

  
198
# check if bidi returns an array of links
199
{ local $TODO = 'does not work as advertised';
200
my @links = $d->link_to_record($o2, bidirectional => 1);
201
is @links, 2, 'bidi returns array of links in array context';
202
}
203

  
204
#  via
205
$links = $o2->linked_records(direction => 'to', to => 'Invoice', via => 'DeliveryOrder');
206
is $links->[0]->id, $i->id,  'simple case via links (string)';
207

  
208
$links = $o2->linked_records(direction => 'to', to => 'Invoice', via => [ 'DeliveryOrder' ]);
209
is $links->[0]->id, $i->id,  'simple case via links (arrayref)';
210

  
211
$links = $o1->linked_records(direction => 'to', to => 'Invoice', via => [ 'Order', 'DeliveryOrder' ]);
212
is $links->[0]->id, $i->id,  'simple case via links (2 hops)';
213

  
214
# multiple links in the same direction from one object
215
$o1->link_to_record($d);
216
$links = $o2->linked_records(direction => 'to', to => 'Invoice', via => 'DeliveryOrder');
217
is $links->[0]->id, $i->id,  'simple case via links (string)';
218

  
219
# at this point the structure is:
220
#
221
#   o1 <--> o2 ---> d ---> i
222
#     \____________,^
223
#
224

  
225
# o1 must have 2 linked records now:
226
$links = $o1->linked_records(direction => 'to');
227
is @$links, 2,  'more than one link';
228

  
229
# as a special funny case, o1 via Order, Order will now yield o2, because it bounces back over itself
230
{ local $TODO = 'no idea if this is desired';
231
$links = $o2->linked_records(direction => 'to', to => 'Order', via => [ 'Order', 'Order' ]);
232
is @$links, 2,  'via links with bidirectional hop over starting object';
233
}
234

  
235
# for sorting, get all don't bother with the links, we'll just take our records
236
my @records = ($o2, $i, $o1, $d);
237
my $sorted;
238
$sorted = SL::DB::Helper::LinkedRecords->sort_linked_records('type', 1, @records);
239
is_deeply $sorted, [$o1, $o2, $d, $i], 'sorting by type';
240
$sorted = SL::DB::Helper::LinkedRecords->sort_linked_records('type', 0, @records);
241
is_deeply $sorted, [$i, $d, $o2, $o1], 'sorting by type desc';
242

  
243
$d->donumber(1);
244
$o1->ordnumber(2);
245
$i->invnumber(3);
246
$o2->ordnumber(4);
247

  
248
$sorted = SL::DB::Helper::LinkedRecords->sort_linked_records('number', 1, @records);
249
is_deeply $sorted, [$d, $o1, $i, $o2], 'sorting by number';
250
$sorted = SL::DB::Helper::LinkedRecords->sort_linked_records('number', 0, @records);
251
is_deeply $sorted, [$o2, $i, $o1, $d], 'sorting by number desc';
252

  
253
# again with natural sorting
254
$d->donumber("a1");
255
$o1->ordnumber("a3");
256
$i->invnumber("a7");
257
$o2->ordnumber("a10");
258

  
259
$sorted = SL::DB::Helper::LinkedRecords->sort_linked_records('number', 1, @records);
260
is_deeply $sorted, [$d, $o1, $i, $o2], 'sorting naturally by number';
261
$sorted = SL::DB::Helper::LinkedRecords->sort_linked_records('number', 0, @records);
262
is_deeply $sorted, [$o2, $i, $o1, $d], 'sorting naturally by number desc';
263

  
264
$o2->transdate(DateTime->new(year => 2010, month => 3, day => 1));
265
$i->transdate(DateTime->new(year => 2014, month => 3, day => 19));
266
$o1->transdate(DateTime->new(year => 2014, month => 5, day => 1));
267
$d->transdate(DateTime->new(year => 2014, month => 5, day => 2));
268

  
269
# transdate should be used before itime
270
$sorted = SL::DB::Helper::LinkedRecords->sort_linked_records('date', 1, @records);
271
is_deeply $sorted, [$o2, $i, $o1, $d], 'sorting by transdate';
272
$sorted = SL::DB::Helper::LinkedRecords->sort_linked_records('date', 0, @records);
273
is_deeply $sorted, [$d, $o1, $i, $o2], 'sorting by transdate desc';
274

  
275
done_testing();
276

  
277
1;

Auch abrufbar als: Unified diff