Bankerweiterung - Zwischenstand, erster Entwurf
[kivitendo-erp.git] / SL / Presenter / Record.pm
1 package SL::Presenter::Record;
2
3 use strict;
4
5 use parent qw(Exporter);
6
7 use Exporter qw(import);
8 our @EXPORT = qw(grouped_record_list empty_record_list record_list record);
9
10 use SL::Util;
11
12 use Carp;
13 use List::Util qw(first);
14
15 sub _arrayify {
16   my ($array) = @_;
17   return []     if !defined $array;
18   return $array if ref $array;
19   return [ $array ];
20 }
21
22 sub record {
23   my ($self, $record, %params) = @_;
24
25   my %grouped = _group_records( [ $record ] ); # pass $record as arrayref
26   my $type    = (keys %grouped)[0];
27
28   return $self->sales_invoice(   $record, %params) if $type eq 'sales_invoices';
29   return $self->purchase_invoice($record, %params) if $type eq 'purchase_invoices';
30   return $self->ar_transaction(  $record, %params) if $type eq 'ar_transactions';
31   return $self->ap_transaction(  $record, %params) if $type eq 'ap_transactions';
32   return $self->gl_transaction(  $record, %params) if $type eq 'gl_transactions';
33
34   return '';
35 }
36
37 sub grouped_record_list {
38   my ($self, $list, %params) = @_;
39
40   %params    = map { exists $params{$_} ? ($_ => $params{$_}) : () } qw(edit_record_links with_columns object_id object_model);
41
42   my %groups = _sort_grouped_lists(_group_records($list));
43   my $output = '';
44
45   $output .= _requirement_spec_list(       $self, $groups{requirement_specs},        %params) if $groups{requirement_specs};
46   $output .= _sales_quotation_list(        $self, $groups{sales_quotations},         %params) if $groups{sales_quotations};
47   $output .= _sales_order_list(            $self, $groups{sales_orders},             %params) if $groups{sales_orders};
48   $output .= _sales_delivery_order_list(   $self, $groups{sales_delivery_orders},    %params) if $groups{sales_delivery_orders};
49   $output .= _sales_invoice_list(          $self, $groups{sales_invoices},           %params) if $groups{sales_invoices};
50   $output .= _ar_transaction_list(         $self, $groups{ar_transactions},          %params) if $groups{ar_transactions};
51
52   $output .= _request_quotation_list(      $self, $groups{purchase_quotations},      %params) if $groups{purchase_quotations};
53   $output .= _purchase_order_list(         $self, $groups{purchase_orders},          %params) if $groups{purchase_orders};
54   $output .= _purchase_delivery_order_list($self, $groups{purchase_delivery_orders}, %params) if $groups{purchase_delivery_orders};
55   $output .= _purchase_invoice_list(       $self, $groups{purchase_invoices},        %params) if $groups{purchase_invoices};
56   $output .= _ap_transaction_list(         $self, $groups{ap_transactions},          %params) if $groups{ap_transactions};
57
58   $output .= _bank_transactions(           $self, $groups{bank_transactions},        %params) if $groups{bank_transactions};
59
60   $output .= _sepa_collection_list(        $self, $groups{sepa_collections},         %params) if $groups{sepa_collections};
61   $output .= _sepa_transfer_list(          $self, $groups{sepa_transfers},           %params) if $groups{sepa_transfers};
62
63   $output  = $self->render('presenter/record/grouped_record_list', %params, output => $output);
64
65   return $output;
66 }
67
68 sub empty_record_list {
69   my ($self, %params) = @_;
70   return $self->grouped_record_list([], %params);
71 }
72
73 sub record_list {
74   my ($self, $list, %params) = @_;
75
76   my @columns;
77
78   if (ref($params{columns}) eq 'ARRAY') {
79     @columns = map {
80       if (ref($_) eq 'ARRAY') {
81         { title => $_->[0], data => $_->[1], link => $_->[2] }
82       } else {
83         $_;
84       }
85     } @{ delete $params{columns} };
86
87   } else {
88     croak "Wrong type for 'columns' argument: not an array reference";
89   }
90
91   my %with_columns = map { ($_ => 1) } @{ _arrayify($params{with_columns}) };
92   if ($with_columns{record_link_direction}) {
93     push @columns, {
94       title => $::locale->text('Link direction'),
95       data  => sub {
96           $_[0]->{_record_link_depth} > 1
97         ? $::locale->text('Row was linked to another record')
98         : $_[0]->{_record_link_direction} eq 'from'
99         ? $::locale->text('Row was source for current record')
100         : $::locale->text('Row was created from current record') },
101     };
102   }
103
104   my %column_meta   = map { $_->name => $_ } @{ $list->[0]->meta->columns       };
105   my %relationships = map { $_->name => $_ } @{ $list->[0]->meta->relationships };
106
107   my $call = sub {
108     my ($obj, $method, @args) = @_;
109     $obj->$method(@args);
110   };
111
112   my @data;
113   foreach my $obj (@{ $list }) {
114     my @row;
115
116     foreach my $spec (@columns) {
117       my %cell;
118
119       my $method       =  $spec->{column} || $spec->{data};
120       my $meta         =  $column_meta{ $spec->{data} };
121       my $type         =  ref $meta;
122       my $relationship =  $relationships{ $spec->{data} };
123       my $rel_type     =  !$relationship ? '' : $relationship->class;
124       $rel_type        =~ s/^SL::DB:://;
125       $rel_type        =  SL::Util::snakify($rel_type);
126
127       if (ref($spec->{data}) eq 'CODE') {
128         $cell{value} = $spec->{data}->($obj);
129
130       } else {
131         $cell{value} = $rel_type && $self->can($rel_type)                                       ? $self->$rel_type($obj->$method, display => 'table-cell')
132                      : $type eq 'Rose::DB::Object::Metadata::Column::Date'                      ? $call->($obj, $method . '_as_date')
133                      : $type =~ m/^Rose::DB::Object::Metadata::Column::(?:Float|Numeric|Real)$/ ? $::form->format_amount(\%::myconfig, $call->($obj, $method), 2)
134                      : $type eq 'Rose::DB::Object::Metadata::Column::Boolean'                   ? $call->($obj, $method . '_as_bool_yn')
135                      : $type =~ m/^Rose::DB::Object::Metadata::Column::(?:Integer|Serial)$/     ? $spec->{data} * 1
136                      :                                                                            $call->($obj, $method);
137       }
138
139       $cell{alignment} = 'right' if $type =~ m/int|serial|float|real|numeric/;
140
141       push @row, \%cell;
142     }
143
144     push @data, { columns => \@row, record_link => $obj->{_record_link} };
145   }
146
147   my @header =
148     map +{ value     => $columns[$_]->{title},
149            alignment => $data[0]->{columns}->[$_]->{alignment},
150          }, (0..scalar(@columns) - 1);
151
152   return $self->render(
153     'presenter/record/record_list',
154     %params,
155     TABLE_HEADER => \@header,
156     TABLE_ROWS   => \@data,
157   );
158 }
159
160 #
161 # private methods
162 #
163
164 sub _group_records {
165   my ($list) = @_;
166
167   my %matchers = (
168     requirement_specs        => sub { (ref($_[0]) eq 'SL::DB::RequirementSpec')                                         },
169     sales_quotations         => sub { (ref($_[0]) eq 'SL::DB::Order')           &&  $_[0]->is_type('sales_quotation')   },
170     sales_orders             => sub { (ref($_[0]) eq 'SL::DB::Order')           &&  $_[0]->is_type('sales_order')       },
171     sales_delivery_orders    => sub { (ref($_[0]) eq 'SL::DB::DeliveryOrder')   &&  $_[0]->is_sales                     },
172     sales_invoices           => sub { (ref($_[0]) eq 'SL::DB::Invoice')         &&  $_[0]->invoice                      },
173     ar_transactions          => sub { (ref($_[0]) eq 'SL::DB::Invoice')         && !$_[0]->invoice                      },
174     purchase_quotations      => sub { (ref($_[0]) eq 'SL::DB::Order')           &&  $_[0]->is_type('request_quotation') },
175     purchase_orders          => sub { (ref($_[0]) eq 'SL::DB::Order')           &&  $_[0]->is_type('purchase_order')    },
176     purchase_delivery_orders => sub { (ref($_[0]) eq 'SL::DB::DeliveryOrder')   && !$_[0]->is_sales                     },
177     purchase_invoices        => sub { (ref($_[0]) eq 'SL::DB::PurchaseInvoice') &&  $_[0]->invoice                      },
178     ap_transactions          => sub { (ref($_[0]) eq 'SL::DB::PurchaseInvoice') && !$_[0]->invoice                      },
179     sepa_collections         => sub { (ref($_[0]) eq 'SL::DB::SepaExportItem')  &&  $_[0]->ar_id                        },
180     sepa_transfers           => sub { (ref($_[0]) eq 'SL::DB::SepaExportItem')  &&  $_[0]->ap_id                        },
181     gl_transactions          => sub { (ref($_[0]) eq 'SL::DB::GLTransaction')                                           },
182     bank_transactions        => sub { (ref($_[0]) eq 'SL::DB::BankTransaction') &&  $_[0]->id                           },
183   );
184
185   my %groups;
186
187   foreach my $record (@{ $list || [] }) {
188     my $type         = (first { $matchers{$_}->($record) } keys %matchers) || 'other';
189     $groups{$type} ||= [];
190     push @{ $groups{$type} }, $record;
191   }
192
193   return %groups;
194 }
195
196 sub _sort_grouped_lists {
197   my (%groups) = @_;
198
199   foreach my $group (keys %groups) {
200     next unless @{ $groups{$group} };
201     if ($groups{$group}->[0]->can('compare_to')) {
202       $groups{$group} = [ sort { $a->compare_to($b)    } @{ $groups{$group} } ];
203     } else {
204       $groups{$group} = [ sort { $a->date <=> $b->date } @{ $groups{$group} } ];
205     }
206   }
207
208   return %groups;
209 }
210
211 sub _requirement_spec_list {
212   my ($self, $list, %params) = @_;
213
214   return $self->record_list(
215     $list,
216     title   => $::locale->text('Requirement specs'),
217     type    => 'requirement_spec',
218     columns => [
219       [ $::locale->text('Requirement spec number'), sub { $self->requirement_spec($_[0], display => 'table-cell') } ],
220       [ $::locale->text('Customer'),                'customer'                                                      ],
221       [ $::locale->text('Title'),                   'title'                                                         ],
222       [ $::locale->text('Project'),                 'project',                                                      ],
223       [ $::locale->text('Status'),                  sub { $_[0]->status->description }                              ],
224     ],
225     %params,
226   );
227 }
228
229 sub _sales_quotation_list {
230   my ($self, $list, %params) = @_;
231
232   return $self->record_list(
233     $list,
234     title   => $::locale->text('Sales Quotations'),
235     type    => 'sales_quotation',
236     columns => [
237       [ $::locale->text('Quotation Date'),          'transdate'                                                                ],
238       [ $::locale->text('Quotation Number'),        sub { $self->sales_quotation($_[0], display => 'table-cell') }   ],
239       [ $::locale->text('Customer'),                'customer'                                                                 ],
240       [ $::locale->text('Net amount'),              'netamount'                                                                ],
241       [ $::locale->text('Transaction description'), 'transaction_description'                                                  ],
242       [ $::locale->text('Project'),                 'globalproject', ],
243       [ $::locale->text('Closed'),                  'closed'                                                                   ],
244     ],
245     %params,
246   );
247 }
248
249 sub _request_quotation_list {
250   my ($self, $list, %params) = @_;
251
252   return $self->record_list(
253     $list,
254     title   => $::locale->text('Request Quotations'),
255     type    => 'request_quotation',
256     columns => [
257       [ $::locale->text('Quotation Date'),          'transdate'                                                                ],
258       [ $::locale->text('Quotation Number'),        sub { $self->request_quotation($_[0], display => 'table-cell') }   ],
259       [ $::locale->text('Vendor'),                  'vendor'                                                                   ],
260       [ $::locale->text('Net amount'),              'netamount'                                                                ],
261       [ $::locale->text('Transaction description'), 'transaction_description'                                                  ],
262       [ $::locale->text('Project'),                 'globalproject', ],
263       [ $::locale->text('Closed'),                  'closed'                                                                   ],
264     ],
265     %params,
266   );
267 }
268
269 sub _sales_order_list {
270   my ($self, $list, %params) = @_;
271
272   return $self->record_list(
273     $list,
274     title   => $::locale->text('Sales Orders'),
275     type    => 'sales_order',
276     columns => [
277       [ $::locale->text('Order Date'),              'transdate'                                                                ],
278       [ $::locale->text('Order Number'),            sub { $self->sales_order($_[0], display => 'table-cell') }   ],
279       [ $::locale->text('Quotation'),               'quonumber' ],
280       [ $::locale->text('Customer'),                'customer'                                                                 ],
281       [ $::locale->text('Net amount'),              'netamount'                                                                ],
282       [ $::locale->text('Transaction description'), 'transaction_description'                                                  ],
283       [ $::locale->text('Project'),                 'globalproject', ],
284       [ $::locale->text('Closed'),                  'closed'                                                                   ],
285     ],
286     %params,
287   );
288 }
289
290 sub _purchase_order_list {
291   my ($self, $list, %params) = @_;
292
293   return $self->record_list(
294     $list,
295     title   => $::locale->text('Purchase Orders'),
296     type    => 'purchase_order',
297     columns => [
298       [ $::locale->text('Order Date'),              'transdate'                                                                ],
299       [ $::locale->text('Order Number'),            sub { $self->purchase_order($_[0], display => 'table-cell') }   ],
300       [ $::locale->text('Request for Quotation'),   'quonumber' ],
301       [ $::locale->text('Vendor'),                  'vendor'                                                                 ],
302       [ $::locale->text('Net amount'),              'netamount'                                                                ],
303       [ $::locale->text('Transaction description'), 'transaction_description'                                                  ],
304       [ $::locale->text('Project'),                 'globalproject', ],
305       [ $::locale->text('Closed'),                  'closed'                                                                   ],
306     ],
307     %params,
308   );
309 }
310
311 sub _sales_delivery_order_list {
312   my ($self, $list, %params) = @_;
313
314   return $self->record_list(
315     $list,
316     title   => $::locale->text('Sales Delivery Orders'),
317     type    => 'sales_delivery_order',
318     columns => [
319       [ $::locale->text('Delivery Order Date'),     'transdate'                                                                ],
320       [ $::locale->text('Delivery Order Number'),   sub { $self->sales_delivery_order($_[0], display => 'table-cell') } ],
321       [ $::locale->text('Order Number'),            'ordnumber' ],
322       [ $::locale->text('Customer'),                'customer'                                                                 ],
323       [ $::locale->text('Transaction description'), 'transaction_description'                                                  ],
324       [ $::locale->text('Project'),                 'globalproject', ],
325       [ $::locale->text('Delivered'),               'delivered'                                                                ],
326       [ $::locale->text('Closed'),                  'closed'                                                                   ],
327     ],
328     %params,
329   );
330 }
331
332 sub _purchase_delivery_order_list {
333   my ($self, $list, %params) = @_;
334
335   return $self->record_list(
336     $list,
337     title   => $::locale->text('Purchase Delivery Orders'),
338     type    => 'purchase_delivery_order',
339     columns => [
340       [ $::locale->text('Delivery Order Date'),     'transdate'                                                                ],
341       [ $::locale->text('Delivery Order Number'),   sub { $self->purchase_delivery_order($_[0], display => 'table-cell') } ],
342       [ $::locale->text('Order Number'),            'ordnumber' ],
343       [ $::locale->text('Vendor'),                  'vendor'                                                                 ],
344       [ $::locale->text('Transaction description'), 'transaction_description'                                                  ],
345       [ $::locale->text('Project'),                 'globalproject', ],
346       [ $::locale->text('Delivered'),               'delivered'                                                                ],
347       [ $::locale->text('Closed'),                  'closed'                                                                   ],
348     ],
349     %params,
350   );
351 }
352
353 sub _sales_invoice_list {
354   my ($self, $list, %params) = @_;
355
356   return $self->record_list(
357     $list,
358     title   => $::locale->text('Sales Invoices'),
359     type    => 'sales_invoice',
360     columns => [
361       [ $::locale->text('Invoice Date'),            'transdate'               ],
362       [ $::locale->text('Type'),                    sub { $_[0]->displayable_type } ],
363       [ $::locale->text('Invoice Number'),          sub { $self->sales_invoice($_[0], display => 'table-cell') } ],
364       [ $::locale->text('Quotation Number'),        'quonumber' ],
365       [ $::locale->text('Order Number'),            'ordnumber' ],
366       [ $::locale->text('Customer'),                'customer'                ],
367       [ $::locale->text('Net amount'),              'netamount'               ],
368       [ $::locale->text('Paid'),                    'paid'                    ],
369       [ $::locale->text('Transaction description'), 'transaction_description' ],
370     ],
371     %params,
372   );
373 }
374
375 sub _purchase_invoice_list {
376   my ($self, $list, %params) = @_;
377
378   return $self->record_list(
379     $list,
380     title   => $::locale->text('Purchase Invoices'),
381     type    => 'purchase_invoice',
382     columns => [
383       [ $::locale->text('Invoice Date'),                 'transdate'               ],
384       [ $::locale->text('Invoice Number'),               sub { $self->purchase_invoice($_[0], display => 'table-cell') } ],
385       [ $::locale->text('Request for Quotation Number'), 'quonumber' ],
386       [ $::locale->text('Order Number'),                 'ordnumber' ],
387       [ $::locale->text('Vendor'),                       'vendor'                 ],
388       [ $::locale->text('Net amount'),                   'netamount'               ],
389       [ $::locale->text('Paid'),                         'paid'                    ],
390       [ $::locale->text('Transaction description'),      'transaction_description' ],
391     ],
392     %params,
393   );
394 }
395
396 sub _ar_transaction_list {
397   my ($self, $list, %params) = @_;
398
399   return $self->record_list(
400     $list,
401     title   => $::locale->text('AR Transactions'),
402     type    => 'ar_transaction',
403     columns => [
404       [ $::locale->text('Invoice Date'),            'transdate'               ],
405       [ $::locale->text('Type'),                    sub { $_[0]->displayable_type } ],
406       [ $::locale->text('Invoice Number'),          sub { $self->ar_transaction($_[0], display => 'table-cell') } ],
407       [ $::locale->text('Customer'),                'customer'                ],
408       [ $::locale->text('Net amount'),              'netamount'               ],
409       [ $::locale->text('Paid'),                    'paid'                    ],
410       [ $::locale->text('Transaction description'), 'transaction_description' ],
411     ],
412     %params,
413   );
414 }
415
416 sub _ap_transaction_list {
417   my ($self, $list, %params) = @_;
418
419   return $self->record_list(
420     $list,
421     title   => $::locale->text('AP Transactions'),
422     type    => 'ap_transaction',
423     columns => [
424       [ $::locale->text('Invoice Date'),            'transdate'                      ],
425       [ $::locale->text('Invoice Number'),          sub { $self->ap_transaction($_[0 ], display => 'table-cell') } ],
426       [ $::locale->text('Vendor'),                  'vendor'                         ],
427       [ $::locale->text('Net amount'),              'netamount'                      ],
428       [ $::locale->text('Paid'),                    'paid'                           ],
429       [ $::locale->text('Transaction description'), 'transaction_description'        ],
430     ],
431     %params,
432   );
433 }
434
435 sub _bank_transactions {
436   my ($self, $list, %params) = @_;
437
438   return $self->record_list(
439     $list,
440     title   => $::locale->text('Bank transactions'),
441     type    => 'bank_transactions',
442     columns => [
443       [ $::locale->text('Transdate'),            'transdate'                      ],
444       [ $::locale->text('Local Bank Code'),      sub { $self->bank_code($_[0]->local_bank_account) }  ],
445       [ $::locale->text('Local account number'), sub { $self->account_number($_[0]->local_bank_account) }  ],
446       [ $::locale->text('Remote Bank Code'),     'remote_bank_code' ],
447       [ $::locale->text('Remote account number'),'remote_account_number' ],
448       [ $::locale->text('Valutadate'),           'valutadate' ],
449       [ $::locale->text('Amount'),               'amount' ],
450       [ $::locale->text('Currency'),             sub { $_[0]->currency->name } ],
451       [ $::locale->text('Remote name'),          'remote_name' ],
452       [ $::locale->text('Purpose'),              'purpose' ],
453     ],
454     %params,
455   );
456 }
457
458 sub _sepa_export_list {
459   my ($self, $list, %params) = @_;
460
461   my ($source, $destination) = $params{type} eq 'sepa_transfer' ? qw(our vc)                                 : qw(vc our);
462   $params{title}             = $params{type} eq 'sepa_transfer' ? $::locale->text('Bank transfers via SEPA') : $::locale->text('Bank collections via SEPA');
463   $params{with_columns}      = [ grep { $_ ne 'record_link_direction' } @{ $params{with_columns} || [] } ];
464
465   delete $params{edit_record_links};
466
467   return $self->record_list(
468     $list,
469     columns => [
470       [ $::locale->text('Export Number'),    'sepa_export',                                  ],
471       [ $::locale->text('Execution date'),   'execution_date'                                ],
472       [ $::locale->text('Export date'),      sub { $_[0]->sepa_export->itime->to_kivitendo } ],
473       [ $::locale->text('Source BIC'),       "${source}_bic"                                 ],
474       [ $::locale->text('Source IBAN'),      "${source}_iban"                                ],
475       [ $::locale->text('Destination BIC'),  "${destination}_bic"                            ],
476       [ $::locale->text('Destination IBAN'), "${destination}_iban"                           ],
477       [ $::locale->text('Amount'),           'amount'                                        ],
478     ],
479     %params,
480   );
481 }
482
483 sub _sepa_transfer_list {
484   my ($self, $list, %params) = @_;
485   _sepa_export_list($self, $list, %params, type => 'sepa_transfer');
486 }
487
488 sub _sepa_collection_list {
489   my ($self, $list, %params) = @_;
490   _sepa_export_list($self, $list, %params, type => 'sepa_collection');
491 }
492
493 1;
494
495 __END__
496
497 =pod
498
499 =encoding utf8
500
501 =head1 NAME
502
503 SL::Presenter::Record - Presenter module for lists of
504 sales/purchase/general ledger record Rose::DB objects
505
506 =head1 SYNOPSIS
507
508   # Retrieve a number of documents from somewhere, e.g.
509   my $order   = SL::DB::Manager::Order->get_first(where => [ SL::DB::Manager::Order->type_filter('sales_order') ]);
510   my $records = $order->linked_records(destination => 'to');
511
512   # Give HTML representation:
513   my $html = SL::Presenter->get->grouped_record_list($records);
514
515 =head1 OVERVIEW
516
517 TODO
518
519 =head1 FUNCTIONS
520
521 =over 4
522
523 =item C<record>
524
525 Returns a rendered version (actually an instance of
526 L<SL::Presenter::EscapedText>) of a single ar, ap or gl object.
527
528 Example:
529   # fetch the record from a random acc_trans object and print its link (could be ar, ap or gl)
530   my $record = SL::DB::Manager::AccTransaction->get_first()->record;
531   my $html   = SL::Presenter->get->record($record, display => 'inline');
532
533 =item C<grouped_record_list $list, %params>
534
535 =item C<empty_record_list>
536
537 Returns a rendered version (actually an instance of
538 L<SL::Presenter::EscapedText>) of an empty list of records. Is usually
539 only called by L<grouped_record_list> if its list is empty.
540
541 =item C<grouped_record_list $list, %params>
542
543 Given a number of Rose::DB objects in the array reference C<$list>
544 this function first groups them by type. Then it calls L<record_list>
545 with each non-empty type-specific sub-list and the appropriate
546 parameters for outputting a list of those records.
547
548 Returns a rendered version (actually an instance of
549 L<SL::Presenter::EscapedText>) of all the lists.
550
551 The order in which the records are grouped is:
552
553 =over 2
554
555 =item * sales quotations
556
557 =item * sales orders
558
559 =item * sales delivery orders
560
561 =item * sales invoices
562
563 =item * AR transactions
564
565 =item * requests for quotations
566
567 =item * purchase orders
568
569 =item * purchase delivery orders
570
571 =item * purchase invoices
572
573 =item * AP transactions
574
575 =item * SEPA collections
576
577 =item * SEPA transfers
578
579 =back
580
581 Objects of unknown types are skipped.
582
583 Parameters are passed to C<record_list> include C<with_objects> and
584 C<edit_record_links>.
585
586 =item C<record_list $list, %params>
587
588 Returns a rendered version (actually an instance of
589 L<SL::Presenter::EscapedText>) of a list of records. This list
590 consists of a heading and a tabular representation of the list.
591
592 The parameters include:
593
594 =over 2
595
596 =item C<title>
597
598 Mandatory. The title to use in the heading. Must already be
599 translated.
600
601 =item C<columns>
602
603 Mandatory. An array reference of column specs to output. Each column
604 spec can be either an array reference or a hash reference.
605
606 If a column spec is an array reference then the first element is the
607 column's name shown in the table header. It must already be translated.
608
609 The second element can be either a string or a code reference. A
610 string is taken as the name of a function to call on the Rose::DB
611 object for the current row. Its return value is formatted depending on
612 the column's type (e.g. dates are output as the user expects them,
613 floating point numbers are rounded to two decimal places and
614 right-aligned etc). If it is a code reference then that code is called
615 with the object as the first argument. Its return value should be an
616 instance of L<SL::Presenter::EscapedText> and contain the rendered
617 representation of the content to output.
618
619 The third element, if present, can be a link to which the column will
620 be linked.
621
622 If the column spec is a hash reference then the same arguments are
623 expected. The corresponding hash keys are C<title>, C<data> and
624 C<link>.
625
626 =item C<with_columns>
627
628 Can be set by the caller to indicate additional columns to
629 be listed. Currently supported:
630
631 =over 2
632
633 =item C<record_link_destination>
634
635 The record link destination. Requires that the records to be listed have
636 been retrieved via the L<SL::DB::Helper::LinkedRecords> helper.
637
638 =back
639
640 =item C<edit_record_links>
641
642 If trueish additional controls will be rendered that allow the user to
643 remove and add record links. Requires that the records to be listed have
644 been retrieved via the L<SL::DB::Helper::LinkedRecords> helper.
645
646 =back
647
648 =back
649
650 =head1 BUGS
651
652 Nothing here yet.
653
654 =head1 AUTHOR
655
656 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
657
658 =cut