1 #=====================================================================
4 # Based on SQL-Ledger Version 2.1.9
5 # Web http://www.lx-office.org
7 #=====================================================================
8 # SQL-Ledger, Accounting
11 # Author: Dieter Simader
12 # Email: dsimader@sql-ledger.org
13 # Web: http://www.sql-ledger.org
16 # This program is free software; you can redistribute it and/or modify
17 # it under the terms of the GNU General Public License as published by
18 # the Free Software Foundation; either version 2 of the License, or
19 # (at your option) any later version.
21 # This program is distributed in the hope that it will be useful,
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 # GNU General Public License for more details.
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, write to the Free Software
27 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
29 #======================================================================
31 # Inventory Control module
33 #======================================================================
35 use POSIX qw(strftime);
36 use List::Util qw(first max);
37 use List::MoreUtils qw(any);
42 use SL::Helper::Flash qw(flash);
44 use SL::ReportGenerator;
52 our ($form, $locale, %myconfig, $lxdebug, $auth);
54 require "bin/mozilla/io.pl";
55 require "bin/mozilla/common.pl";
56 require "bin/mozilla/reportgenerator.pl";
61 # type=submit $locale->text('Add Part')
62 # type=submit $locale->text('Add Service')
63 # type=submit $locale->text('Add Assembly')
64 # type=submit $locale->text('Edit Part')
65 # type=submit $locale->text('Edit Service')
66 # type=submit $locale->text('Edit Assembly')
67 # $locale->text('Parts')
68 # $locale->text('Services')
69 # $locale->text('Inventory quantity must be zero before you can set this part obsolete!')
70 # $locale->text('Inventory quantity must be zero before you can set this assembly obsolete!')
71 # $locale->text('Part Number missing!')
72 # $locale->text('Service Number missing!')
73 # $locale->text('Assembly Number missing!')
74 # $locale->text('ea');
79 $lxdebug->enter_sub();
81 $auth->assert('part_service_assembly_details');
83 $form->{revers} = 0; # switch for backward sorting
84 $form->{lastsort} = ""; # memory for which table was sort at last time
85 $form->{ndxs_counter} = 0; # counter for added entries to top100
87 $form->{title} = (ucfirst $form->{searchitems}) . "s";
88 $form->{title} =~ s/ys$/ies/;
89 $form->{title} = $locale->text($form->{title});
91 $form->{CUSTOM_VARIABLES} = CVar->get_configs('module' => 'IC');
92 ($form->{CUSTOM_VARIABLES_FILTER_CODE},
93 $form->{CUSTOM_VARIABLES_INCLUSION_CODE}) = CVar->render_search_options('variables' => $form->{CUSTOM_VARIABLES},
94 'include_prefix' => 'l_',
95 'include_value' => 'Y');
99 $form->get_lists('partsgroup' => 'ALL_PARTSGROUPS');
100 print $form->parse_html_template('ic/search');
102 $lxdebug->leave_sub();
106 $::lxdebug->enter_sub();
108 $::auth->assert('part_service_assembly_edit');
110 $::form->{l_soldtotal} = "Y";
111 $::form->{sort} = "soldtotal";
112 $::form->{lastsort} = "soldtotal";
114 $::form->{l_qty} = undef;
115 $::form->{l_linetotal} = undef;
116 $::form->{l_number} = "Y";
117 $::form->{number} = "position";
119 unless ( $::form->{bought}
122 || $::form->{quoted}) {
123 $::form->{bought} = $::form->{sold} = 1;
128 $lxdebug->leave_sub();
133 # Warning, deep magic ahead.
134 # This function parses the requested details, sanity checks them, and converts them into a format thats usable for IC->all_parts
136 # flags coming from the form:
138 # searchitems=part revers=0 lastsort=''
141 # partnumber ean description partsgroup classification serialnumber make model drawing microfiche
142 # transdatefrom transdateto
145 # itemstatus = active | onhand | short | obsolete | orphaned
146 # action = continue | top100
149 # bought sold onorder ordered rfq quoted
150 # l_partnumber l_description l_serialnumber l_unit l_listprice l_sellprice l_lastcost
151 # l_linetotal l_priceupdate l_bin l_rop l_weight l_image l_drawing l_microfiche
152 # l_partsgroup l_subtotal l_soldtotal l_deliverydate l_pricegroups
155 # nextsub revers lastsort sort ndxs_counter
157 sub generate_report {
158 $lxdebug->enter_sub();
160 $auth->assert('part_service_assembly_details');
162 my ($revers, $lastsort, $description);
164 my $cvar_configs = CVar->get_configs('module' => 'IC');
166 $form->{title} = $locale->text('Articles');
169 'bin' => { 'text' => $locale->text('Bin'), },
170 'deliverydate' => { 'text' => $locale->text('deliverydate'), },
171 'description' => { 'text' => $locale->text('Part Description'), },
172 'notes' => { 'text' => $locale->text('Notes'), },
173 'drawing' => { 'text' => $locale->text('Drawing'), },
174 'ean' => { 'text' => $locale->text('EAN'), },
175 'image' => { 'text' => $locale->text('Image'), },
176 'insertdate' => { 'text' => $locale->text('Insert Date'), },
177 'invnumber' => { 'text' => $locale->text('Invoice Number'), },
178 'lastcost' => { 'text' => $locale->text('Last Cost'), },
179 'linetotallastcost' => { 'text' => $locale->text('Extended'), },
180 'linetotallistprice' => { 'text' => $locale->text('Extended'), },
181 'linetotalsellprice' => { 'text' => $locale->text('Extended'), },
182 'listprice' => { 'text' => $locale->text('List Price'), },
183 'microfiche' => { 'text' => $locale->text('Microfiche'), },
184 'name' => { 'text' => $locale->text('Name'), },
185 'onhand' => { 'text' => $locale->text('Stocked Qty'), },
186 'ordnumber' => { 'text' => $locale->text('Order Number'), },
187 'partnumber' => { 'text' => $locale->text('Part Number'), },
188 'partsgroup' => { 'text' => $locale->text('Partsgroup'), },
189 'priceupdate' => { 'text' => $locale->text('Updated'), },
190 'quonumber' => { 'text' => $locale->text('Quotation'), },
191 'rop' => { 'text' => $locale->text('ROP'), },
192 'sellprice' => { 'text' => $locale->text('Sell Price'), },
193 'serialnumber' => { 'text' => $locale->text('Serial Number'), },
194 'soldtotal' => { 'text' => $locale->text('Qty in Selected Records'), },
195 'name' => { 'text' => $locale->text('Name in Selected Records'), },
196 'transdate' => { 'text' => $locale->text('Transdate'), },
197 'unit' => { 'text' => $locale->text('Unit'), },
198 'weight' => { 'text' => $locale->text('Weight'), },
199 'shop' => { 'text' => $locale->text('Shop article'), },
200 'type_and_classific' => { 'text' => $locale->text('Type'), },
201 'projectnumber' => { 'text' => $locale->text('Project Number'), },
202 'projectdescription' => { 'text' => $locale->text('Project Description'), },
205 $revers = $form->{revers};
206 $lastsort = $form->{lastsort};
208 # sorting and direction of sorting
209 # ToDO: change this to the simpler field+direction method
210 if (($form->{lastsort} eq "") && ($form->{sort} eq undef)) {
212 $form->{lastsort} = "partnumber";
213 $form->{sort} = "partnumber";
215 if ($form->{lastsort} eq $form->{sort}) {
216 $form->{revers} = 1 - $form->{revers};
219 $form->{lastsort} = $form->{sort};
223 # special case if we have a serialnumber limit search
224 # serialnumbers are only given in invoices and orders,
225 # so they can only pop up in bought, sold, rfq, and quoted stuff
226 $form->{no_sn_joins} = 'Y' if ( !$form->{bought} && !$form->{sold}
227 && !$form->{rfq} && !$form->{quoted}
228 && ($form->{l_serialnumber} || $form->{serialnumber}));
230 # special case for any checkbox of bought | sold | onorder | ordered | rfq | quoted.
231 # if any of these are ticked the behavior changes slightly for lastcost
232 # since all those are aggregation checks for the legder tables this is an internal switch
233 # refered to as ledgerchecks
234 $form->{ledgerchecks} = 'Y' if ( $form->{bought} || $form->{sold} || $form->{onorder}
235 || $form->{ordered} || $form->{rfq} || $form->{quoted});
237 # if something should be activated if something else is active, enter it here
239 onhand => [ qw(l_onhand) ],
240 short => [ qw(l_onhand) ],
241 onorder => [ qw(l_ordnumber) ],
242 ordered => [ qw(l_ordnumber) ],
243 rfq => [ qw(l_quonumber) ],
244 quoted => [ qw(l_quonumber) ],
245 bought => [ qw(l_invnumber) ],
246 sold => [ qw(l_invnumber) ],
247 ledgerchecks => [ qw(l_name) ],
248 serialnumber => [ qw(l_serialnumber) ],
249 no_sn_joins => [ qw(bought sold) ],
252 # get name of partsgroup if id is given
254 if ($form->{partsgroup_id}) {
255 my $pg = SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load;
256 $pg_name = $pg->{'partsgroup'};
259 # these strings get displayed at the top of the results to indicate the user which switches were used
261 active => $locale->text('Active'),
262 obsolete => $locale->text('Obsolete'),
263 orphaned => $locale->text('Orphaned'),
264 onhand => $locale->text('On Hand'),
265 short => $locale->text('Short'),
266 onorder => $locale->text('On Order'),
267 ordered => $locale->text('Ordered'),
268 rfq => $locale->text('RFQ'),
269 quoted => $locale->text('Quoted'),
270 bought => $locale->text('Bought'),
271 sold => $locale->text('Sold'),
272 transdatefrom => $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1),
273 transdateto => $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1),
274 partnumber => $locale->text('Part Number') . ": '$form->{partnumber}'",
275 partsgroup => $locale->text('Partsgroup') . ": '$form->{partsgroup}'",
276 partsgroup_id => $locale->text('Partsgroup') . ": '$pg_name'",
277 serialnumber => $locale->text('Serial Number') . ": '$form->{serialnumber}'",
278 description => $locale->text('Part Description') . ": '$form->{description}'",
279 make => $locale->text('Make') . ": '$form->{make}'",
280 model => $locale->text('Model') . ": '$form->{model}'",
281 drawing => $locale->text('Drawing') . ": '$form->{drawing}'",
282 microfiche => $locale->text('Microfiche') . ": '$form->{microfiche}'",
283 l_soldtotal => $locale->text('Qty in Selected Records'),
284 ean => $locale->text('EAN') . ": '$form->{ean}'",
285 insertdatefrom => $locale->text('Insert Date') . ": " . $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1),
286 insertdateto => $locale->text('Insert Date') . ": " . $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1),
289 my @itemstatus_keys = qw(active obsolete orphaned onhand short);
290 my @callback_keys = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
291 drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto insertdatefrom insertdateto ean shop all);
293 # calculate dependencies
294 for (@itemstatus_keys, @callback_keys) {
295 next if ($form->{itemstatus} ne $_ && !$form->{$_});
296 map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
299 # generate callback and optionstrings
301 for my $key (@itemstatus_keys, @callback_keys) {
302 next if ($form->{itemstatus} ne $key && !$form->{$key});
303 push @options, $optiontexts{$key};
306 # special case for lastcost
307 if ($form->{ledgerchecks}){
308 # ledgerchecks don't know about sellprice or lastcost. they just return a
309 # price. so rename sellprice to price, and drop lastcost.
310 $column_defs{sellprice}{text} = $locale->text('Price');
311 $form->{l_lastcost} = ""
314 if ($form->{description}) {
315 $description = $form->{description};
316 $description =~ s/\n/<br>/g;
319 if ($form->{l_linetotal}) {
320 $form->{l_qty} = "Y";
321 $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
322 $form->{l_linetotallastcost} = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if $form->{l_lastcost};
323 $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
325 $form->{"l_type_and_classific"} = "Y";
327 if ($form->{l_service} && !$form->{l_assembly} && !$form->{l_part}) {
329 # remove bin, weight and rop from list
330 map { $form->{"l_$_"} = "" } qw(bin weight rop);
332 $form->{l_onhand} = "";
334 # qty is irrelevant unless bought or sold
340 || $form->{quoted}) {
341 # $form->{l_onhand} = "Y";
343 $form->{l_linetotalsellprice} = "";
344 $form->{l_linetotallastcost} = "";
348 # soldtotal doesn't make sense with more than one bsooqr option.
349 # so reset it to sold (the most common option), and issue a warning
351 # also it doesn't make sense without bsooqr. disable and issue a warning too
352 my @bsooqr = qw(sold bought onorder ordered rfq quoted);
353 my $bsooqr_mode = grep { $form->{$_} } @bsooqr;
354 if ($form->{l_subtotal} && 1 < $bsooqr_mode) {
355 my $enabled = first { $form->{$_} } @bsooqr;
356 $form->{$_} = '' for @bsooqr;
357 $form->{$enabled} = 'Y';
359 push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
361 if ($form->{l_soldtotal} && !$bsooqr_mode) {
362 delete $form->{l_soldtotal};
364 flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
366 if ($form->{l_name} && !$bsooqr_mode) {
367 delete $form->{l_name};
369 flash('warning', $::locale->text('Name does not make sense without any bsooqr options'));
371 IC->all_parts(\%myconfig, \%$form);
374 partnumber type_and_classific description notes partsgroup bin onhand rop soldtotal unit listprice
375 linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
376 priceupdate weight image drawing microfiche invnumber ordnumber quonumber
377 transdate name serialnumber deliverydate ean projectnumber projectdescription
381 my $pricegroups = SL::DB::Manager::Pricegroup->get_all_sorted;
382 my @pricegroup_columns;
383 my %column_defs_pricegroups;
384 if ($form->{l_pricegroups}) {
385 @pricegroup_columns = map { "pricegroup_" . $_->id } @{ $pricegroups };
386 %column_defs_pricegroups = map {
387 "pricegroup_" . $_->id => {
388 text => $::locale->text('Pricegroup') . ' ' . $_->pricegroup,
393 push @columns, @pricegroup_columns;
395 my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
396 my @searchable_custom_variables = grep { $_->{searchable} } @{ $cvar_configs };
397 my %column_defs_cvars = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
399 push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
401 %column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
402 map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
403 map { $column_defs{$_}->{align} = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal shop), @pricegroup_columns;
405 my @hidden_variables = (
406 qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups insertdatefrom insertdateto),
407 qw(l_type_and_classific classification_id),
410 map({ "cvar_$_->{name}" } @searchable_custom_variables),
411 map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
412 map({ "l_$_" } @columns),
415 my $callback = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
417 my @sort_full = qw(partnumber description onhand soldtotal deliverydate insertdate shop);
418 my @sort_no_revers = qw(partsgroup bin priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
420 foreach my $col (@sort_full) {
421 $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
423 map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
425 # add order to callback
426 $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
428 my $report = SL::ReportGenerator->new(\%myconfig, $form);
430 my %attachment_basenames = (
431 'part' => $locale->text('part_list'),
432 'service' => $locale->text('service_list'),
433 'assembly' => $locale->text('assembly_list'),
434 'article' => $locale->text('article_list'),
437 $report->set_options('raw_top_info_text' => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
438 'raw_bottom_info_text' => $form->parse_html_template('ic/generate_report_bottom' ,
439 { PART_CLASSIFICATIONS => SL::DB::Manager::PartClassification->get_all_sorted }),
440 'output_format' => 'HTML',
441 'title' => $form->{title},
442 'attachment_basename' => 'article_list' . strftime('_%Y%m%d', localtime time),
444 $report->set_options_from_form();
445 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
447 $report->set_columns(%column_defs);
448 $report->set_column_order(@columns);
450 $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
452 $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
454 CVar->add_custom_variables_to_report('module' => 'IC',
455 'trans_id_field' => 'id',
456 'configs' => $cvar_configs,
457 'column_defs' => \%column_defs,
458 'data' => $form->{parts});
460 CVar->add_custom_variables_to_report('module' => 'IC',
461 'sub_module' => sub { $_[0]->{ioi} },
462 'trans_id_field' => 'ioi_id',
463 'configs' => $cvar_configs,
464 'column_defs' => \%column_defs,
465 'data' => $form->{parts});
467 my @subtotal_columns = qw(sellprice listprice lastcost);
468 my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
469 my %totals = map { $_ => 0 } @subtotal_columns;
471 my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
473 my $defaults = AM->get_defaults();
476 foreach my $ref (@{ $form->{parts} }) {
478 # fresh row, for inserting later
479 my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
481 $ref->{exchangerate} ||= 1;
482 $ref->{price_factor} ||= 1;
483 $ref->{sellprice} *= $ref->{exchangerate} / $ref->{price_factor};
484 $ref->{listprice} *= $ref->{exchangerate} / $ref->{price_factor};
485 $ref->{lastcost} *= $ref->{exchangerate} / $ref->{price_factor};
487 # use this for assemblies
488 my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
490 if ($ref->{assemblyitem}) {
491 $row->{partnumber}{align} = 'right';
492 $row->{soldtotal}{data} = 0;
493 $soldtotal = 0 if ($form->{sold});
496 my $edit_link = build_std_url('script=controller.pl', 'action=Part/edit', 'part.id=' . E($ref->{id}), 'callback');
497 $row->{partnumber}->{link} = $edit_link;
498 $row->{description}->{link} = $edit_link;
500 foreach (qw(sellprice listprice lastcost)) {
501 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}, 2);
502 $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
504 foreach ( @pricegroup_columns ) {
505 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
509 map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
511 $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
513 # 'yes' and 'no' for boolean value shop
514 if ($form->{l_shop}) {
515 $row->{shop}{data} = $row->{shop}{data}? $::locale->text('yes') : $::locale->text('no');
518 if (!$ref->{assemblyitem}) {
519 foreach my $col (@subtotal_columns) {
520 $totals{$col} += $soldtotal * $ref->{$col};
521 $subtotals{$col} += $soldtotal * $ref->{$col};
524 $subtotals{soldtotal} += $soldtotal;
528 if ($ref->{module} eq 'oe') {
529 # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
531 # | ist bestellt | Von Kunden bestellt | -> edit_oe_ord_link
532 # | Anfrage | Angebot | -> edit_oe_quo_link
534 my $edit_oe_ord_link = build_std_url("script=oe.pl", 'action=edit', 'type=' . E($ref->{cv} eq 'vendor' ? 'purchase_order' : 'sales_order'), 'id=' . E($ref->{trans_id}), 'callback');
535 my $edit_oe_quo_link = build_std_url("script=oe.pl", 'action=edit', 'type=' . E($ref->{cv} eq 'vendor' ? 'request_quotation' : 'sales_quotation'), 'id=' . E($ref->{trans_id}), 'callback');
537 $row->{ordnumber}{link} = $edit_oe_ord_link;
538 $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
541 $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback') if ($ref->{invnumber});
544 # set properties of images
545 if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
546 $row->{image}{data} = '';
547 $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
549 map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
551 $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
552 $row->{type_and_classific}{data} = $::request->presenter->type_abbreviation($ref->{part_type}).
553 $::request->presenter->classification_abbreviation($ref->{classification_id});
555 $report->add_data($row);
557 my $next_ref = $form->{parts}[$idx + 1];
559 # insert subtotal rows
560 if (($form->{l_subtotal} eq 'Y') &&
562 (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
563 my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
565 if ( !$form->{l_assembly} || !$form->{bom}) {
566 $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
569 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
570 map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
572 $report->add_data($row);
574 $same_item = $next_ref->{ $form->{sort} };
580 if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
581 my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
583 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
585 $report->add_separator();
586 $report->add_data($row);
589 $report->generate_with_headers();
591 $lxdebug->leave_sub();
592 } #end generate_report
594 sub continue { call_sub($form->{"nextsub"}); }