1 package SL::Presenter::Record;
5 use parent qw(Exporter);
7 use Exporter qw(import);
8 our @EXPORT = qw(grouped_record_list empty_record_list record_list);
11 use List::Util qw(first);
15 return [] if !defined $array;
16 return $array if ref $array;
20 sub grouped_record_list {
21 my ($self, $list, %params) = @_;
23 %params = map { exists $params{$_} ? ($_ => $params{$_}) : () } qw(edit_record_links form_prefix with_columns object_id object_model);
24 $params{form_prefix} ||= 'record_links';
26 my %groups = _group_records($list);
29 $output .= _sales_quotation_list( $self, $groups{sales_quotations}, %params) if $groups{sales_quotations};
30 $output .= _sales_order_list( $self, $groups{sales_orders}, %params) if $groups{sales_orders};
31 $output .= _sales_delivery_order_list( $self, $groups{sales_delivery_orders}, %params) if $groups{sales_delivery_orders};
32 $output .= _sales_invoice_list( $self, $groups{sales_invoices}, %params) if $groups{sales_invoices};
33 $output .= _ar_transaction_list( $self, $groups{ar_transactions}, %params) if $groups{ar_transactions};
35 $output .= _request_quotation_list( $self, $groups{purchase_quotations}, %params) if $groups{purchase_quotations};
36 $output .= _purchase_order_list( $self, $groups{purchase_orders}, %params) if $groups{purchase_orders};
37 $output .= _purchase_delivery_order_list($self, $groups{purchase_delivery_orders}, %params) if $groups{purchase_delivery_orders};
38 $output .= _purchase_invoice_list( $self, $groups{purchase_invoices}, %params) if $groups{purchase_invoices};
39 $output .= _ar_transaction_list( $self, $groups{ar_transactions}, %params) if $groups{ar_transactions};
41 $output = $self->render('presenter/record/grouped_record_list', %params, output => $output, nownow => DateTime->now) if $output;
43 return $output || $self->empty_record_list;
46 sub empty_record_list {
48 return $self->render('presenter/record/empty_record_list');
52 my ($self, $list, %params) = @_;
56 if (ref($params{columns}) eq 'ARRAY') {
58 if (ref($_) eq 'ARRAY') {
59 { title => $_->[0], data => $_->[1], link => $_->[2] }
63 } @{ delete $params{columns} };
66 croak "Wrong type for 'columns' argument: not an array reference";
69 my %with_columns = map { ($_ => 1) } @{ _arrayify($params{with_columns}) };
70 if ($with_columns{record_link_direction}) {
72 title => $::locale->text('Link direction'),
73 data => sub { $_[0]->{_record_link_direction} eq 'from' ? $::locale->text('Row was source for current record') : $::locale->text('Row was created from current record') },
77 my %column_meta = map { $_->name => $_ } @{ $list->[0]->meta->columns };
78 my %relationships = map { $_->name => $_ } @{ $list->[0]->meta->relationships };
81 my ($obj, $method, @args) = @_;
86 foreach my $obj (@{ $list }) {
89 foreach my $spec (@columns) {
92 my $method = $spec->{column} || $spec->{data};
93 my $meta = $column_meta{ $spec->{data} };
95 my $relationship = $relationships{ $spec->{data} };
96 my $rel_type = !$relationship ? '' : lc $relationship->class;
97 $rel_type =~ s/^sl::db:://;
99 if (ref($spec->{data}) eq 'CODE') {
100 $cell{value} = $spec->{data}->($obj);
103 $cell{value} = $rel_type && $self->can($rel_type) ? $self->$rel_type($obj->$method, display => 'table-cell')
104 : $type eq 'Rose::DB::Object::Metadata::Column::Date' ? $call->($obj, $method . '_as_date')
105 : $type =~ m/^Rose::DB::Object::Metadata::Column::(?:Float|Numeric|Real)$/ ? $::form->format_amount(\%::myconfig, $call->($obj, $method), 2)
106 : $type eq 'Rose::DB::Object::Metadata::Column::Boolean' ? $call->($obj, $method . '_as_bool_yn')
107 : $type =~ m/^Rose::DB::Object::Metadata::Column::(?:Integer|Serial)$/ ? $spec->{data} * 1
108 : $call->($obj, $method);
111 $cell{alignment} = 'right' if $type =~ m/int|serial|float|real|numeric/;
116 push @data, { columns => \@row, record_link => $obj->{_record_link} };
120 map +{ value => $columns[$_]->{title},
121 alignment => $data[0]->{columns}->[$_]->{alignment},
122 }, (0..scalar(@columns) - 1);
124 $params{form_prefix} ||= 'record_links';
126 return $self->render(
127 'presenter/record/record_list',
129 TABLE_HEADER => \@header,
130 TABLE_ROWS => \@data,
142 sales_quotations => sub { (ref($_[0]) eq 'SL::DB::Order') && $_[0]->is_type('sales_quotation') },
143 sales_orders => sub { (ref($_[0]) eq 'SL::DB::Order') && $_[0]->is_type('sales_order') },
144 sales_delivery_orders => sub { (ref($_[0]) eq 'SL::DB::DeliveryOrder') && $_[0]->is_sales },
145 sales_invoices => sub { (ref($_[0]) eq 'SL::DB::Invoice') && $_[0]->invoice },
146 ar_transactions => sub { (ref($_[0]) eq 'SL::DB::Invoice') && !$_[0]->invoice },
147 purchase_quotations => sub { (ref($_[0]) eq 'SL::DB::Order') && $_[0]->is_type('request_quotation') },
148 purchase_orders => sub { (ref($_[0]) eq 'SL::DB::Order') && $_[0]->is_type('purchase_order') },
149 purchase_delivery_orders => sub { (ref($_[0]) eq 'SL::DB::DeliveryOrder') && !$_[0]->is_sales },
150 purchase_invoices => sub { (ref($_[0]) eq 'SL::DB::PurchaseInvoice') && $_[0]->invoice },
151 ap_transactions => sub { (ref($_[0]) eq 'SL::DB::PurchaseInvoice') && !$_[0]->invoice },
156 foreach my $record (@{ $list || [] }) {
157 my $type = (first { $matchers{$_}->($record) } keys %matchers) || 'other';
158 $groups{$type} ||= [];
159 push @{ $groups{$type} }, $record;
165 sub _sales_quotation_list {
166 my ($self, $list, %params) = @_;
168 return $self->record_list(
170 title => $::locale->text('Sales Quotations'),
172 [ $::locale->text('Quotation Date'), 'transdate' ],
173 [ $::locale->text('Quotation Number'), sub { $self->sales_quotation($_[0], display => 'table-cell') } ],
174 [ $::locale->text('Customer'), 'customer' ],
175 [ $::locale->text('Net amount'), 'netamount' ],
176 [ $::locale->text('Transaction description'), 'transaction_description' ],
177 [ $::locale->text('Project'), 'globalproject', ],
178 [ $::locale->text('Closed'), 'closed' ],
184 sub _request_quotation_list {
185 my ($self, $list, %params) = @_;
187 return $self->record_list(
189 title => $::locale->text('Request Quotations'),
191 [ $::locale->text('Quotation Date'), 'transdate' ],
192 [ $::locale->text('Quotation Number'), sub { $self->sales_quotation($_[0], display => 'table-cell') } ],
193 [ $::locale->text('Vendor'), 'vendor' ],
194 [ $::locale->text('Net amount'), 'netamount' ],
195 [ $::locale->text('Transaction description'), 'transaction_description' ],
196 [ $::locale->text('Project'), 'globalproject', ],
197 [ $::locale->text('Closed'), 'closed' ],
203 sub _sales_order_list {
204 my ($self, $list, %params) = @_;
206 return $self->record_list(
208 title => $::locale->text('Sales Orders'),
210 [ $::locale->text('Order Date'), 'transdate' ],
211 [ $::locale->text('Order Number'), sub { $self->sales_order($_[0], display => 'table-cell') } ],
212 [ $::locale->text('Quotation'), 'quonumber' ],
213 [ $::locale->text('Customer'), 'customer' ],
214 [ $::locale->text('Net amount'), 'netamount' ],
215 [ $::locale->text('Transaction description'), 'transaction_description' ],
216 [ $::locale->text('Project'), 'globalproject', ],
217 [ $::locale->text('Closed'), 'closed' ],
223 sub _purchase_order_list {
224 my ($self, $list, %params) = @_;
226 return $self->record_list(
228 title => $::locale->text('Purchase Orders'),
230 [ $::locale->text('Order Date'), 'transdate' ],
231 [ $::locale->text('Order Number'), sub { $self->sales_order($_[0], display => 'table-cell') } ],
232 [ $::locale->text('Request for Quotation'), 'quonumber' ],
233 [ $::locale->text('Vendor'), 'vendor' ],
234 [ $::locale->text('Net amount'), 'netamount' ],
235 [ $::locale->text('Transaction description'), 'transaction_description' ],
236 [ $::locale->text('Project'), 'globalproject', ],
237 [ $::locale->text('Closed'), 'closed' ],
243 sub _sales_delivery_order_list {
244 my ($self, $list, %params) = @_;
246 return $self->record_list(
248 title => $::locale->text('Sales Delivery Orders'),
250 [ $::locale->text('Delivery Order Date'), 'transdate' ],
251 [ $::locale->text('Delivery Order Number'), sub { $self->sales_delivery_order($_[0], display => 'table-cell') } ],
252 [ $::locale->text('Order Number'), 'ordnumber' ],
253 [ $::locale->text('Customer'), 'customer' ],
254 [ $::locale->text('Transaction description'), 'transaction_description' ],
255 [ $::locale->text('Project'), 'globalproject', ],
256 [ $::locale->text('Delivered'), 'delivered' ],
257 [ $::locale->text('Closed'), 'closed' ],
263 sub _purchase_delivery_order_list {
264 my ($self, $list, %params) = @_;
266 return $self->record_list(
268 title => $::locale->text('Purchase Delivery Orders'),
270 [ $::locale->text('Delivery Order Date'), 'transdate' ],
271 [ $::locale->text('Delivery Order Number'), sub { $self->sales_delivery_order($_[0], display => 'table-cell') } ],
272 [ $::locale->text('Order Number'), 'ordnumber' ],
273 [ $::locale->text('Vendor'), 'vendor' ],
274 [ $::locale->text('Transaction description'), 'transaction_description' ],
275 [ $::locale->text('Project'), 'globalproject', ],
276 [ $::locale->text('Delivered'), 'delivered' ],
277 [ $::locale->text('Closed'), 'closed' ],
283 sub _sales_invoice_list {
284 my ($self, $list, %params) = @_;
286 return $self->record_list(
288 title => $::locale->text('Sales Invoices'),
290 [ $::locale->text('Invoice Date'), 'transdate' ],
291 [ $::locale->text('Invoice Number'), sub { $self->sales_invoice($_[0], display => 'table-cell') } ],
292 [ $::locale->text('Quotation Number'), 'quonumber' ],
293 [ $::locale->text('Order Number'), 'ordnumber' ],
294 [ $::locale->text('Customer'), 'customer' ],
295 [ $::locale->text('Net amount'), 'netamount' ],
296 [ $::locale->text('Paid'), 'paid' ],
297 [ $::locale->text('Transaction description'), 'transaction_description' ],
303 sub _purchase_invoice_list {
304 my ($self, $list, %params) = @_;
306 return $self->record_list(
308 title => $::locale->text('Purchase Invoices'),
310 [ $::locale->text('Invoice Date'), 'transdate' ],
311 [ $::locale->text('Invoice Number'), sub { $self->sales_invoice($_[0], display => 'table-cell') } ],
312 [ $::locale->text('Request for Quotation Number'), 'quonumber' ],
313 [ $::locale->text('Order Number'), 'ordnumber' ],
314 [ $::locale->text('Vendor'), 'vendor' ],
315 [ $::locale->text('Net amount'), 'netamount' ],
316 [ $::locale->text('Paid'), 'paid' ],
317 [ $::locale->text('Transaction description'), 'transaction_description' ],
323 sub _ar_transaction_list {
324 my ($self, $list, %params) = @_;
326 return $self->record_list(
328 title => $::locale->text('AR Transactions'),
330 [ $::locale->text('Invoice Date'), 'transdate' ],
331 [ $::locale->text('Invoice Number'), sub { $self->ar_transaction($_[0], display => 'table-cell') } ],
332 [ $::locale->text('Customer'), 'customer' ],
333 [ $::locale->text('Net amount'), 'netamount' ],
334 [ $::locale->text('Paid'), 'paid' ],
335 [ $::locale->text('Transaction description'), 'transaction_description' ],
341 sub _ap_transaction_list {
342 my ($self, $list, %params) = @_;
344 return $self->record_list(
346 title => $::locale->text('AP Transactions'),
348 [ $::locale->text('Invoice Date'), 'transdate' ],
349 [ $::locale->text('Invoice Number'), sub { $self->ar_transaction($_[0 ], display => 'table-cell') } ],
350 [ $::locale->text('Vendor'), 'vendor' ],
351 [ $::locale->text('Net amount'), 'netamount' ],
352 [ $::locale->text('Paid'), 'paid' ],
353 [ $::locale->text('Transaction description'), 'transaction_description' ],
369 SL::Presenter::Record - Presenter module for lists of
370 sales/purchase/general ledger record Rose::DB objects
374 # Retrieve a number of documents from somewhere, e.g.
375 my $order = SL::DB::Manager::Order->get_first(where => [ SL::DB::Manager::Order->type_filter('sales_order') ]);
376 my $records = $order->linked_records(destination => 'to');
378 # Give HTML representation:
379 my $html = SL::Presenter->get->grouped_record_list($records);
389 =item C<empty_record_list>
391 Returns a rendered version (actually an instance of
392 L<SL::Presenter::EscapedText>) of an empty list of records. Is usually
393 only called by L<grouped_record_list> if its list is empty.
395 =item C<grouped_record_list $list, %params>
397 Given a number of Rose::DB objects in the array reference C<$list>
398 this function first groups them by type. Then it calls L<record_list>
399 with each non-empty type-specific sub-list and the appropriate
400 parameters for outputting a list of those records.
402 Returns a rendered version (actually an instance of
403 L<SL::Presenter::EscapedText>) of all the lists.
405 The order in which the records are grouped is:
409 =item * sales quotations
413 =item * sales delivery orders
415 =item * sales invoices
417 =item * AR transactions
419 =item * requests for quotations
421 =item * purchase orders
423 =item * purchase delivery orders
425 =item * purchase invoices
427 =item * AP transactions
431 Objects of unknown types are skipped.
433 Parameters are passed to C<record_list> include C<with_objects> and
434 C<edit_record_links>.
436 =item C<record_list $list, %params>
438 Returns a rendered version (actually an instance of
439 L<SL::Presenter::EscapedText>) of a list of records. This list
440 consists of a heading and a tabular representation of the list.
442 The parameters include:
448 Mandatory. The title to use in the heading. Must already be
453 Mandatory. An array reference of column specs to output. Each column
454 spec can be either an array reference or a hash reference.
456 If a column spec is an array reference then the first element is the
457 column's name shown in the table header. It must already be translated.
459 The second element can be either a string or a code reference. A
460 string is taken as the name of a function to call on the Rose::DB
461 object for the current row. Its return value is formatted depending on
462 the column's type (e.g. dates are output as the user expects them,
463 floating point numbers are rounded to two decimal places and
464 right-aligned etc). If it is a code reference then that code is called
465 with the object as the first argument. Its return value should be an
466 instance of L<SL::Presenter::EscapedText> and contain the rendered
467 representation of the content to output.
469 The third element, if present, can be a link to which the column will
472 If the column spec is a hash reference then the same arguments are
473 expected. The corresponding hash keys are C<title>, C<data> and
476 =item C<with_columns>
478 Can be set by the caller to indicate additional columns to
479 list. Currently supported:
483 =item C<record_link_destination>
485 The record link destination. Requires that the records to list have
486 been retrieved via the L<SL::DB::Helper::LinkedRecords> helper.
490 =item C<edit_record_links>
492 If trueish additional controls will be rendered that allow the user to
493 remove and add record links. Requires that the records to list have
494 been retrieved via the L<SL::DB::Helper::LinkedRecords> helper.
506 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>