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., 675 Mass Ave, Cambridge, MA 02139, USA.
28 #======================================================================
30 # Inventory Control module
32 #======================================================================
34 use POSIX qw(strftime);
35 use List::Util qw(first max);
36 use List::MoreUtils qw(any);
41 use SL::Helper::Flash qw(flash);
43 use SL::ReportGenerator;
51 our ($form, $locale, %myconfig, $lxdebug, $auth);
53 require "bin/mozilla/io.pl";
54 require "bin/mozilla/common.pl";
55 require "bin/mozilla/reportgenerator.pl";
60 # type=submit $locale->text('Add Part')
61 # type=submit $locale->text('Add Service')
62 # type=submit $locale->text('Add Assembly')
63 # type=submit $locale->text('Edit Part')
64 # type=submit $locale->text('Edit Service')
65 # type=submit $locale->text('Edit Assembly')
66 # $locale->text('Parts')
67 # $locale->text('Services')
68 # $locale->text('Inventory quantity must be zero before you can set this part obsolete!')
69 # $locale->text('Inventory quantity must be zero before you can set this assembly obsolete!')
70 # $locale->text('Part Number missing!')
71 # $locale->text('Service Number missing!')
72 # $locale->text('Assembly Number missing!')
73 # $locale->text('ea');
78 $lxdebug->enter_sub();
80 $auth->assert('part_service_assembly_details');
82 $form->{revers} = 0; # switch for backward sorting
83 $form->{lastsort} = ""; # memory for which table was sort at last time
84 $form->{ndxs_counter} = 0; # counter for added entries to top100
86 my %is_xyz = map { +"is_$_" => ($form->{searchitems} eq $_) } qw(part service assembly);
88 $form->{title} = (ucfirst $form->{searchitems}) . "s";
89 $form->{title} = $locale->text($form->{title});
90 $form->{title} = $locale->text('Assemblies') if ($is_xyz{is_assembly});
92 $form->{CUSTOM_VARIABLES} = CVar->get_configs('module' => 'IC');
93 ($form->{CUSTOM_VARIABLES_FILTER_CODE},
94 $form->{CUSTOM_VARIABLES_INCLUSION_CODE}) = CVar->render_search_options('variables' => $form->{CUSTOM_VARIABLES},
95 'include_prefix' => 'l_',
96 'include_value' => 'Y');
100 $form->get_lists('partsgroup' => 'ALL_PARTSGROUPS');
101 print $form->parse_html_template('ic/search', { %is_xyz, });
103 $lxdebug->leave_sub();
106 sub search_update_prices {
107 $lxdebug->enter_sub();
109 $auth->assert('part_service_assembly_edit');
111 my $pricegroups = IC->get_pricegroups(\%myconfig, \%$form);
113 $form->{title} = $locale->text('Update Prices');
117 print $form->parse_html_template('ic/search_update_prices', { PRICE_ROWS => $pricegroups });
119 $lxdebug->leave_sub();
122 sub confirm_price_update {
123 $lxdebug->enter_sub();
125 $auth->assert('part_service_assembly_edit');
128 my $value_found = undef;
130 foreach my $idx (qw(sellprice listprice), (1..$form->{price_rows})) {
131 my $name = $idx =~ m/\d/ ? $form->{"pricegroup_${idx}"} : $idx eq 'sellprice' ? $locale->text('Sell Price') : $locale->text('List Price');
132 my $type = $idx =~ m/\d/ ? $form->{"pricegroup_type_${idx}"} : $form->{"${idx}_type"};
133 my $value_idx = $idx =~ m/\d/ ? "price_${idx}" : $idx;
134 my $value = $form->parse_amount(\%myconfig, $form->{$value_idx});
136 if ((0 > $value) && ($type eq 'percent')) {
137 push @errors, $locale->text('You cannot adjust the price for pricegroup "#1" by a negative percentage.', $name);
139 } elsif (!$value && ($form->{$value_idx} ne '')) {
140 push @errors, $locale->text('No valid number entered for pricegroup "#1".', $name);
142 } elsif (0 < $value) {
147 push @errors, $locale->text('No prices will be updated because no prices have been entered.') if (!$value_found);
149 my $num_matches = IC->get_num_matches_for_priceupdate();
154 $form->show_generic_error(join('<br>', @errors), 'back_button' => 1);
157 $form->{nextsub} = "update_prices";
159 map { delete $form->{$_} } qw(action header);
161 print $form->parse_html_template('ic/confirm_price_update', { HIDDENS => [ map { name => $_, value => $form->{$_} }, keys %$form ],
162 num_matches => $num_matches });
164 $lxdebug->leave_sub();
168 $lxdebug->enter_sub();
170 $auth->assert('part_service_assembly_edit');
172 my $num_updated = IC->update_prices(\%myconfig, \%$form);
174 if (-1 != $num_updated) {
175 $form->redirect($locale->text('#1 prices were updated.', $num_updated));
177 $form->error($locale->text('Could not update prices!'));
180 $lxdebug->leave_sub();
184 $::lxdebug->enter_sub();
186 $::auth->assert('part_service_assembly_edit');
188 $::form->{l_soldtotal} = "Y";
189 $::form->{sort} = "soldtotal";
190 $::form->{lastsort} = "soldtotal";
192 $::form->{l_qty} = undef;
193 $::form->{l_linetotal} = undef;
194 $::form->{l_number} = "Y";
195 $::form->{number} = "position";
197 unless ( $::form->{bought}
200 || $::form->{quoted}) {
201 $::form->{bought} = $::form->{sold} = 1;
206 $lxdebug->leave_sub();
211 # Warning, deep magic ahead.
212 # This function parses the requested details, sanity checks them, and converts them into a format thats usable for IC->all_parts
214 # flags coming from the form:
216 # searchitems=part revers=0 lastsort=''
219 # partnumber ean description partsgroup serialnumber make model drawing microfiche
220 # transdatefrom transdateto
223 # itemstatus = active | onhand | short | obsolete | orphaned
224 # action = continue | top100
227 # bought sold onorder ordered rfq quoted
228 # l_partnumber l_description l_serialnumber l_unit l_listprice l_sellprice l_lastcost
229 # l_linetotal l_priceupdate l_bin l_rop l_weight l_image l_drawing l_microfiche
230 # l_partsgroup l_subtotal l_soldtotal l_deliverydate l_pricegroups
233 # nextsub revers lastsort sort ndxs_counter
235 sub generate_report {
236 $lxdebug->enter_sub();
238 $auth->assert('part_service_assembly_details');
240 my ($revers, $lastsort, $description);
242 my $cvar_configs = CVar->get_configs('module' => 'IC');
245 '' => $locale->text('Articles'),
246 part => $locale->text('Parts'),
247 service => $locale->text('Services'),
248 assembly => $locale->text('Assemblies'),
249 assortment => $locale->text('Assortments'),
252 $form->{title} = $titles{$form->{searchitems}};
255 'bin' => { 'text' => $locale->text('Bin'), },
256 'deliverydate' => { 'text' => $locale->text('deliverydate'), },
257 'description' => { 'text' => $locale->text('Part Description'), },
258 'notes' => { 'text' => $locale->text('Notes'), },
259 'drawing' => { 'text' => $locale->text('Drawing'), },
260 'ean' => { 'text' => $locale->text('EAN'), },
261 'image' => { 'text' => $locale->text('Image'), },
262 'insertdate' => { 'text' => $locale->text('Insert Date'), },
263 'invnumber' => { 'text' => $locale->text('Invoice Number'), },
264 'lastcost' => { 'text' => $locale->text('Last Cost'), },
265 'linetotallastcost' => { 'text' => $locale->text('Extended'), },
266 'linetotallistprice' => { 'text' => $locale->text('Extended'), },
267 'linetotalsellprice' => { 'text' => $locale->text('Extended'), },
268 'listprice' => { 'text' => $locale->text('List Price'), },
269 'microfiche' => { 'text' => $locale->text('Microfiche'), },
270 'name' => { 'text' => $locale->text('Name'), },
271 'onhand' => { 'text' => $locale->text('Stocked Qty'), },
272 'ordnumber' => { 'text' => $locale->text('Order Number'), },
273 'partnumber' => { 'text' => $locale->text('Part Number'), },
274 'partsgroup' => { 'text' => $locale->text('Group'), },
275 'priceupdate' => { 'text' => $locale->text('Updated'), },
276 'quonumber' => { 'text' => $locale->text('Quotation'), },
277 'rop' => { 'text' => $locale->text('ROP'), },
278 'sellprice' => { 'text' => $locale->text('Sell Price'), },
279 'serialnumber' => { 'text' => $locale->text('Serial Number'), },
280 'soldtotal' => { 'text' => $locale->text('Qty in Selected Records'), },
281 'name' => { 'text' => $locale->text('Name in Selected Records'), },
282 'transdate' => { 'text' => $locale->text('Transdate'), },
283 'unit' => { 'text' => $locale->text('Unit'), },
284 'weight' => { 'text' => $locale->text('Weight'), },
285 'shop' => { 'text' => $locale->text('Shop article'), },
286 'projectnumber' => { 'text' => $locale->text('Project Number'), },
287 'projectdescription' => { 'text' => $locale->text('Project Description'), },
290 $revers = $form->{revers};
291 $lastsort = $form->{lastsort};
293 # sorting and direction of sorting
294 # ToDO: change this to the simpler field+direction method
295 if (($form->{lastsort} eq "") && ($form->{sort} eq undef)) {
297 $form->{lastsort} = "partnumber";
298 $form->{sort} = "partnumber";
300 if ($form->{lastsort} eq $form->{sort}) {
301 $form->{revers} = 1 - $form->{revers};
304 $form->{lastsort} = $form->{sort};
308 # special case if we have a serialnumber limit search
309 # serialnumbers are only given in invoices and orders,
310 # so they can only pop up in bought, sold, rfq, and quoted stuff
311 $form->{no_sn_joins} = 'Y' if ( !$form->{bought} && !$form->{sold}
312 && !$form->{rfq} && !$form->{quoted}
313 && ($form->{l_serialnumber} || $form->{serialnumber}));
315 # special case for any checkbox of bought | sold | onorder | ordered | rfq | quoted.
316 # if any of these are ticked the behavior changes slightly for lastcost
317 # since all those are aggregation checks for the legder tables this is an internal switch
318 # refered to as ledgerchecks
319 $form->{ledgerchecks} = 'Y' if ( $form->{bought} || $form->{sold} || $form->{onorder}
320 || $form->{ordered} || $form->{rfq} || $form->{quoted});
322 # if something should be activated if something else is active, enter it here
324 onhand => [ qw(l_onhand) ],
325 short => [ qw(l_onhand) ],
326 onorder => [ qw(l_ordnumber) ],
327 ordered => [ qw(l_ordnumber) ],
328 rfq => [ qw(l_quonumber) ],
329 quoted => [ qw(l_quonumber) ],
330 bought => [ qw(l_invnumber) ],
331 sold => [ qw(l_invnumber) ],
332 ledgerchecks => [ qw(l_name) ],
333 serialnumber => [ qw(l_serialnumber) ],
334 no_sn_joins => [ qw(bought sold) ],
337 # get name of partsgroup if id is given
339 if ($form->{partsgroup_id}) {
340 my $pg = SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load;
341 $pg_name = $pg->{'partsgroup'};
344 # these strings get displayed at the top of the results to indicate the user which switches were used
346 active => $locale->text('Active'),
347 obsolete => $locale->text('Obsolete'),
348 orphaned => $locale->text('Orphaned'),
349 onhand => $locale->text('On Hand'),
350 short => $locale->text('Short'),
351 onorder => $locale->text('On Order'),
352 ordered => $locale->text('Ordered'),
353 rfq => $locale->text('RFQ'),
354 quoted => $locale->text('Quoted'),
355 bought => $locale->text('Bought'),
356 sold => $locale->text('Sold'),
357 transdatefrom => $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1),
358 transdateto => $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1),
359 partnumber => $locale->text('Part Number') . ": '$form->{partnumber}'",
360 partsgroup => $locale->text('Group') . ": '$form->{partsgroup}'",
361 partsgroup_id => $locale->text('Group') . ": '$pg_name'",
362 serialnumber => $locale->text('Serial Number') . ": '$form->{serialnumber}'",
363 description => $locale->text('Part Description') . ": '$form->{description}'",
364 make => $locale->text('Make') . ": '$form->{make}'",
365 model => $locale->text('Model') . ": '$form->{model}'",
366 drawing => $locale->text('Drawing') . ": '$form->{drawing}'",
367 microfiche => $locale->text('Microfiche') . ": '$form->{microfiche}'",
368 l_soldtotal => $locale->text('Qty in Selected Records'),
369 ean => $locale->text('EAN') . ": '$form->{ean}'",
370 insertdatefrom => $locale->text('Insert Date') . ": " . $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1),
371 insertdateto => $locale->text('Insert Date') . ": " . $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1),
374 my @itemstatus_keys = qw(active obsolete orphaned onhand short);
375 my @callback_keys = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
376 drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto insertdatefrom insertdateto ean shop all);
378 # calculate dependencies
379 for (@itemstatus_keys, @callback_keys) {
380 next if ($form->{itemstatus} ne $_ && !$form->{$_});
381 map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
384 # generate callback and optionstrings
386 for my $key (@itemstatus_keys, @callback_keys) {
387 next if ($form->{itemstatus} ne $key && !$form->{$key});
388 push @options, $optiontexts{$key};
391 # special case for lastcost
392 if ($form->{ledgerchecks}){
393 # ledgerchecks don't know about sellprice or lastcost. they just return a
394 # price. so rename sellprice to price, and drop lastcost.
395 $column_defs{sellprice}{text} = $locale->text('Price');
396 $form->{l_lastcost} = ""
399 if ($form->{description}) {
400 $description = $form->{description};
401 $description =~ s/\n/<br>/g;
404 if ($form->{l_linetotal}) {
405 $form->{l_qty} = "Y";
406 $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
407 $form->{l_linetotallastcost} = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if $form->{l_lastcost};
408 $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
411 if ($form->{searchitems} eq 'service') {
413 # remove bin, weight and rop from list
414 map { $form->{"l_$_"} = "" } qw(bin weight rop);
416 $form->{l_onhand} = "";
418 # qty is irrelevant unless bought or sold
424 || $form->{quoted}) {
425 # $form->{l_onhand} = "Y";
427 $form->{l_linetotalsellprice} = "";
428 $form->{l_linetotallastcost} = "";
432 # soldtotal doesn't make sense with more than one bsooqr option.
433 # so reset it to sold (the most common option), and issue a warning
435 # also it doesn't make sense without bsooqr. disable and issue a warning too
436 my @bsooqr = qw(sold bought onorder ordered rfq quoted);
437 my $bsooqr_mode = grep { $form->{$_} } @bsooqr;
438 if ($form->{l_subtotal} && 1 < $bsooqr_mode) {
439 my $enabled = first { $form->{$_} } @bsooqr;
440 $form->{$_} = '' for @bsooqr;
441 $form->{$enabled} = 'Y';
443 push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
445 if ($form->{l_soldtotal} && !$bsooqr_mode) {
446 delete $form->{l_soldtotal};
448 flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
450 if ($form->{l_name} && !$bsooqr_mode) {
451 delete $form->{l_name};
453 flash('warning', $::locale->text('Name does not make sense without any bsooqr options'));
455 IC->all_parts(\%myconfig, \%$form);
458 partnumber description notes partsgroup bin onhand rop soldtotal unit listprice
459 linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
460 priceupdate weight image drawing microfiche invnumber ordnumber quonumber
461 transdate name serialnumber deliverydate ean projectnumber projectdescription
465 my $pricegroups = SL::DB::Manager::Pricegroup->get_all_sorted;
466 my @pricegroup_columns;
467 my %column_defs_pricegroups;
468 if ($form->{l_pricegroups}) {
469 @pricegroup_columns = map { "pricegroup_" . $_->id } @{ $pricegroups };
470 %column_defs_pricegroups = map {
471 "pricegroup_" . $_->id => {
472 text => $::locale->text('Pricegroup') . ' ' . $_->pricegroup,
477 push @columns, @pricegroup_columns;
479 my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
480 my @searchable_custom_variables = grep { $_->{searchable} } @{ $cvar_configs };
481 my %column_defs_cvars = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
483 push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
485 %column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
486 map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
487 map { $column_defs{$_}->{align} = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal shop), @pricegroup_columns;
489 my @hidden_variables = (
490 qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups insertdatefrom insertdateto),
493 map({ "cvar_$_->{name}" } @searchable_custom_variables),
494 map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
495 map({ "l_$_" } @columns),
498 my $callback = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
500 my @sort_full = qw(partnumber description onhand soldtotal deliverydate insertdate shop);
501 my @sort_no_revers = qw(partsgroup bin priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
503 foreach my $col (@sort_full) {
504 $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
506 map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
508 # add order to callback
509 $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
511 my $report = SL::ReportGenerator->new(\%myconfig, $form);
513 my %attachment_basenames = (
514 'part' => $locale->text('part_list'),
515 'service' => $locale->text('service_list'),
516 'assembly' => $locale->text('assembly_list'),
519 $report->set_options('raw_top_info_text' => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
520 'raw_bottom_info_text' => $form->parse_html_template('ic/generate_report_bottom'),
521 'output_format' => 'HTML',
522 'title' => $form->{title},
523 'attachment_basename' => $attachment_basenames{$form->{searchitems}} . strftime('_%Y%m%d', localtime time),
525 $report->set_options_from_form();
526 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
528 $report->set_columns(%column_defs);
529 $report->set_column_order(@columns);
531 $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
533 $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
535 CVar->add_custom_variables_to_report('module' => 'IC',
536 'trans_id_field' => 'id',
537 'configs' => $cvar_configs,
538 'column_defs' => \%column_defs,
539 'data' => $form->{parts});
541 CVar->add_custom_variables_to_report('module' => 'IC',
542 'sub_module' => sub { $_[0]->{ioi} },
543 'trans_id_field' => 'ioi_id',
544 'configs' => $cvar_configs,
545 'column_defs' => \%column_defs,
546 'data' => $form->{parts});
548 my @subtotal_columns = qw(sellprice listprice lastcost);
549 my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
550 my %totals = map { $_ => 0 } @subtotal_columns;
552 my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
554 my $defaults = AM->get_defaults();
557 foreach my $ref (@{ $form->{parts} }) {
559 # fresh row, for inserting later
560 my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
562 $ref->{exchangerate} ||= 1;
563 $ref->{price_factor} ||= 1;
564 $ref->{sellprice} *= $ref->{exchangerate} / $ref->{price_factor};
565 $ref->{listprice} *= $ref->{exchangerate} / $ref->{price_factor};
566 $ref->{lastcost} *= $ref->{exchangerate} / $ref->{price_factor};
568 # use this for assemblies
569 my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
571 if ($ref->{assemblyitem}) {
572 $row->{partnumber}{align} = 'right';
573 $row->{soldtotal}{data} = 0;
574 $soldtotal = 0 if ($form->{sold});
577 my $edit_link = build_std_url('script=controller.pl', 'action=Part/edit', 'part.id=' . E($ref->{id}), 'callback');
578 $row->{partnumber}->{link} = $edit_link;
579 $row->{description}->{link} = $edit_link;
581 foreach (qw(sellprice listprice lastcost)) {
582 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}, 2);
583 $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
585 foreach ( @pricegroup_columns ) {
586 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
590 map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
592 $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
594 # 'yes' and 'no' for boolean value shop
595 if ($form->{l_shop}) {
596 $row->{shop}{data} = $row->{shop}{data}? $::locale->text('yes') : $::locale->text('no');
599 if (!$ref->{assemblyitem}) {
600 foreach my $col (@subtotal_columns) {
601 $totals{$col} += $soldtotal * $ref->{$col};
602 $subtotals{$col} += $soldtotal * $ref->{$col};
605 $subtotals{soldtotal} += $soldtotal;
609 if ($ref->{module} eq 'oe') {
610 # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
612 # | ist bestellt | Von Kunden bestellt | -> edit_oe_ord_link
613 # | Anfrage | Angebot | -> edit_oe_quo_link
615 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');
616 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');
618 $row->{ordnumber}{link} = $edit_oe_ord_link;
619 $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
622 $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback') if ($ref->{invnumber});
625 # set properties of images
626 if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
627 $row->{image}{data} = '';
628 $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
630 map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
632 $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
634 $report->add_data($row);
636 my $next_ref = $form->{parts}[$idx + 1];
638 # insert subtotal rows
639 if (($form->{l_subtotal} eq 'Y') &&
641 (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
642 my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
644 if (($form->{searchitems} ne 'assembly') || !$form->{bom}) {
645 $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
648 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
649 map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
651 $report->add_data($row);
653 $same_item = $next_ref->{ $form->{sort} };
659 if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
660 my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
662 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
664 $report->add_separator();
665 $report->add_data($row);
668 $report->generate_with_headers();
670 $lxdebug->leave_sub();
671 } #end generate_report
673 sub ajax_autocomplete {
674 $main::lxdebug->enter_sub();
676 my $form = $main::form;
677 my %myconfig = %main::myconfig;
679 $form->{column} = 'description' unless $form->{column} =~ /^partnumber|description$/;
680 $form->{$form->{column}} = $form->{q} || '';
681 $form->{limit} = ($form->{limit} * 1) || 10;
682 $form->{searchitems} ||= '';
684 my @results = IC->all_parts(\%myconfig, $form);
686 print $form->ajax_response_header(),
687 $form->parse_html_template('ic/ajax_autocomplete');
689 $main::lxdebug->leave_sub();
696 delete @{$::form}{qw(action action_add action_back_to_record back_sub description item notes partnumber sellprice taxaccount2 unit vc)};
698 $::auth->restore_form_from_session($::form->{previousform}, clobber => 1);
699 $::form->{rowcount}--;
700 $::form->{action} = 'display_form';
701 $::form->{callback} = $::form->{script} . '?' . join('&', map { $::form->escape($_) . '=' . $::form->escape($::form->{$_}) } sort keys %{ $::form });
705 sub continue { call_sub($form->{"nextsub"}); }
708 my $action = first { $::form->{"action_${_}"} } qw(add back_to_record);
709 $::form->error($::locale->text('No action defined.')) unless $action;
711 $::form->{dispatched_action} = $action;