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();
105 sub search_update_prices {
106 $lxdebug->enter_sub();
108 $auth->assert('part_service_assembly_edit');
110 my $pricegroups = IC->get_pricegroups(\%myconfig, \%$form);
112 $form->{title} = $locale->text('Update Prices');
116 print $form->parse_html_template('ic/search_update_prices', { PRICE_ROWS => $pricegroups });
118 $lxdebug->leave_sub();
121 sub confirm_price_update {
122 $lxdebug->enter_sub();
124 $auth->assert('part_service_assembly_edit');
127 my $value_found = undef;
129 foreach my $idx (qw(sellprice listprice), (1..$form->{price_rows})) {
130 my $name = $idx =~ m/\d/ ? $form->{"pricegroup_${idx}"} : $idx eq 'sellprice' ? $locale->text('Sell Price') : $locale->text('List Price');
131 my $type = $idx =~ m/\d/ ? $form->{"pricegroup_type_${idx}"} : $form->{"${idx}_type"};
132 my $value_idx = $idx =~ m/\d/ ? "price_${idx}" : $idx;
133 my $value = $form->parse_amount(\%myconfig, $form->{$value_idx});
135 if ((0 > $value) && ($type eq 'percent')) {
136 push @errors, $locale->text('You cannot adjust the price for pricegroup "#1" by a negative percentage.', $name);
138 } elsif (!$value && ($form->{$value_idx} ne '')) {
139 push @errors, $locale->text('No valid number entered for pricegroup "#1".', $name);
141 } elsif (0 < $value) {
146 push @errors, $locale->text('No prices will be updated because no prices have been entered.') if (!$value_found);
148 my $num_matches = IC->get_num_matches_for_priceupdate();
153 $form->show_generic_error(join('<br>', @errors));
156 $form->{nextsub} = "update_prices";
158 map { delete $form->{$_} } qw(action header);
160 print $form->parse_html_template('ic/confirm_price_update', { HIDDENS => [ map { name => $_, value => $form->{$_} }, keys %$form ],
161 num_matches => $num_matches });
163 $lxdebug->leave_sub();
167 $lxdebug->enter_sub();
169 $auth->assert('part_service_assembly_edit');
171 my $num_updated = IC->update_prices(\%myconfig, \%$form);
173 if (-1 != $num_updated) {
174 $form->redirect($locale->text('#1 prices were updated.', $num_updated));
176 $form->error($locale->text('Could not update prices!'));
179 $lxdebug->leave_sub();
183 $::lxdebug->enter_sub();
185 $::auth->assert('part_service_assembly_edit');
187 $::form->{l_soldtotal} = "Y";
188 $::form->{sort} = "soldtotal";
189 $::form->{lastsort} = "soldtotal";
191 $::form->{l_qty} = undef;
192 $::form->{l_linetotal} = undef;
193 $::form->{l_number} = "Y";
194 $::form->{number} = "position";
196 unless ( $::form->{bought}
199 || $::form->{quoted}) {
200 $::form->{bought} = $::form->{sold} = 1;
205 $lxdebug->leave_sub();
210 # Warning, deep magic ahead.
211 # This function parses the requested details, sanity checks them, and converts them into a format thats usable for IC->all_parts
213 # flags coming from the form:
215 # searchitems=part revers=0 lastsort=''
218 # partnumber ean description partsgroup classification serialnumber make model drawing microfiche
219 # transdatefrom transdateto
222 # itemstatus = active | onhand | short | obsolete | orphaned
223 # action = continue | top100
226 # bought sold onorder ordered rfq quoted
227 # l_partnumber l_description l_serialnumber l_unit l_listprice l_sellprice l_lastcost
228 # l_linetotal l_priceupdate l_bin l_rop l_weight l_image l_drawing l_microfiche
229 # l_partsgroup l_subtotal l_soldtotal l_deliverydate l_pricegroups
232 # nextsub revers lastsort sort ndxs_counter
234 sub generate_report {
235 $lxdebug->enter_sub();
237 $auth->assert('part_service_assembly_details');
239 my ($revers, $lastsort, $description);
241 my $cvar_configs = CVar->get_configs('module' => 'IC');
243 $form->{title} = $locale->text('Articles');
246 'bin' => { 'text' => $locale->text('Bin'), },
247 'deliverydate' => { 'text' => $locale->text('deliverydate'), },
248 'description' => { 'text' => $locale->text('Part Description'), },
249 'notes' => { 'text' => $locale->text('Notes'), },
250 'drawing' => { 'text' => $locale->text('Drawing'), },
251 'ean' => { 'text' => $locale->text('EAN'), },
252 'image' => { 'text' => $locale->text('Image'), },
253 'insertdate' => { 'text' => $locale->text('Insert Date'), },
254 'invnumber' => { 'text' => $locale->text('Invoice Number'), },
255 'lastcost' => { 'text' => $locale->text('Last Cost'), },
256 'linetotallastcost' => { 'text' => $locale->text('Extended'), },
257 'linetotallistprice' => { 'text' => $locale->text('Extended'), },
258 'linetotalsellprice' => { 'text' => $locale->text('Extended'), },
259 'listprice' => { 'text' => $locale->text('List Price'), },
260 'microfiche' => { 'text' => $locale->text('Microfiche'), },
261 'name' => { 'text' => $locale->text('Name'), },
262 'onhand' => { 'text' => $locale->text('Stocked Qty'), },
263 'ordnumber' => { 'text' => $locale->text('Order Number'), },
264 'partnumber' => { 'text' => $locale->text('Part Number'), },
265 'partsgroup' => { 'text' => $locale->text('Partsgroup'), },
266 'priceupdate' => { 'text' => $locale->text('Updated'), },
267 'quonumber' => { 'text' => $locale->text('Quotation'), },
268 'rop' => { 'text' => $locale->text('ROP'), },
269 'sellprice' => { 'text' => $locale->text('Sell Price'), },
270 'serialnumber' => { 'text' => $locale->text('Serial Number'), },
271 'soldtotal' => { 'text' => $locale->text('Qty in Selected Records'), },
272 'name' => { 'text' => $locale->text('Name in Selected Records'), },
273 'transdate' => { 'text' => $locale->text('Transdate'), },
274 'unit' => { 'text' => $locale->text('Unit'), },
275 'weight' => { 'text' => $locale->text('Weight'), },
276 'shop' => { 'text' => $locale->text('Shop article'), },
277 'type_and_classific' => { 'text' => $locale->text('Type'), },
278 'projectnumber' => { 'text' => $locale->text('Project Number'), },
279 'projectdescription' => { 'text' => $locale->text('Project Description'), },
282 $revers = $form->{revers};
283 $lastsort = $form->{lastsort};
285 # sorting and direction of sorting
286 # ToDO: change this to the simpler field+direction method
287 if (($form->{lastsort} eq "") && ($form->{sort} eq undef)) {
289 $form->{lastsort} = "partnumber";
290 $form->{sort} = "partnumber";
292 if ($form->{lastsort} eq $form->{sort}) {
293 $form->{revers} = 1 - $form->{revers};
296 $form->{lastsort} = $form->{sort};
300 # special case if we have a serialnumber limit search
301 # serialnumbers are only given in invoices and orders,
302 # so they can only pop up in bought, sold, rfq, and quoted stuff
303 $form->{no_sn_joins} = 'Y' if ( !$form->{bought} && !$form->{sold}
304 && !$form->{rfq} && !$form->{quoted}
305 && ($form->{l_serialnumber} || $form->{serialnumber}));
307 # special case for any checkbox of bought | sold | onorder | ordered | rfq | quoted.
308 # if any of these are ticked the behavior changes slightly for lastcost
309 # since all those are aggregation checks for the legder tables this is an internal switch
310 # refered to as ledgerchecks
311 $form->{ledgerchecks} = 'Y' if ( $form->{bought} || $form->{sold} || $form->{onorder}
312 || $form->{ordered} || $form->{rfq} || $form->{quoted});
314 # if something should be activated if something else is active, enter it here
316 onhand => [ qw(l_onhand) ],
317 short => [ qw(l_onhand) ],
318 onorder => [ qw(l_ordnumber) ],
319 ordered => [ qw(l_ordnumber) ],
320 rfq => [ qw(l_quonumber) ],
321 quoted => [ qw(l_quonumber) ],
322 bought => [ qw(l_invnumber) ],
323 sold => [ qw(l_invnumber) ],
324 ledgerchecks => [ qw(l_name) ],
325 serialnumber => [ qw(l_serialnumber) ],
326 no_sn_joins => [ qw(bought sold) ],
329 # get name of partsgroup if id is given
331 if ($form->{partsgroup_id}) {
332 my $pg = SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load;
333 $pg_name = $pg->{'partsgroup'};
336 # these strings get displayed at the top of the results to indicate the user which switches were used
338 active => $locale->text('Active'),
339 obsolete => $locale->text('Obsolete'),
340 orphaned => $locale->text('Orphaned'),
341 onhand => $locale->text('On Hand'),
342 short => $locale->text('Short'),
343 onorder => $locale->text('On Order'),
344 ordered => $locale->text('Ordered'),
345 rfq => $locale->text('RFQ'),
346 quoted => $locale->text('Quoted'),
347 bought => $locale->text('Bought'),
348 sold => $locale->text('Sold'),
349 transdatefrom => $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1),
350 transdateto => $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1),
351 partnumber => $locale->text('Part Number') . ": '$form->{partnumber}'",
352 partsgroup => $locale->text('Partsgroup') . ": '$form->{partsgroup}'",
353 partsgroup_id => $locale->text('Partsgroup') . ": '$pg_name'",
354 serialnumber => $locale->text('Serial Number') . ": '$form->{serialnumber}'",
355 description => $locale->text('Part Description') . ": '$form->{description}'",
356 make => $locale->text('Make') . ": '$form->{make}'",
357 model => $locale->text('Model') . ": '$form->{model}'",
358 drawing => $locale->text('Drawing') . ": '$form->{drawing}'",
359 microfiche => $locale->text('Microfiche') . ": '$form->{microfiche}'",
360 l_soldtotal => $locale->text('Qty in Selected Records'),
361 ean => $locale->text('EAN') . ": '$form->{ean}'",
362 insertdatefrom => $locale->text('Insert Date') . ": " . $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1),
363 insertdateto => $locale->text('Insert Date') . ": " . $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1),
366 my @itemstatus_keys = qw(active obsolete orphaned onhand short);
367 my @callback_keys = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
368 drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto insertdatefrom insertdateto ean shop all);
370 # calculate dependencies
371 for (@itemstatus_keys, @callback_keys) {
372 next if ($form->{itemstatus} ne $_ && !$form->{$_});
373 map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
376 # generate callback and optionstrings
378 for my $key (@itemstatus_keys, @callback_keys) {
379 next if ($form->{itemstatus} ne $key && !$form->{$key});
380 push @options, $optiontexts{$key};
383 # special case for lastcost
384 if ($form->{ledgerchecks}){
385 # ledgerchecks don't know about sellprice or lastcost. they just return a
386 # price. so rename sellprice to price, and drop lastcost.
387 $column_defs{sellprice}{text} = $locale->text('Price');
388 $form->{l_lastcost} = ""
391 if ($form->{description}) {
392 $description = $form->{description};
393 $description =~ s/\n/<br>/g;
396 if ($form->{l_linetotal}) {
397 $form->{l_qty} = "Y";
398 $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
399 $form->{l_linetotallastcost} = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if $form->{l_lastcost};
400 $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
402 $form->{"l_type_and_classific"} = "Y";
404 if ($form->{l_service} && !$form->{l_assembly} && !$form->{l_part}) {
406 # remove bin, weight and rop from list
407 map { $form->{"l_$_"} = "" } qw(bin weight rop);
409 $form->{l_onhand} = "";
411 # qty is irrelevant unless bought or sold
417 || $form->{quoted}) {
418 # $form->{l_onhand} = "Y";
420 $form->{l_linetotalsellprice} = "";
421 $form->{l_linetotallastcost} = "";
425 # soldtotal doesn't make sense with more than one bsooqr option.
426 # so reset it to sold (the most common option), and issue a warning
428 # also it doesn't make sense without bsooqr. disable and issue a warning too
429 my @bsooqr = qw(sold bought onorder ordered rfq quoted);
430 my $bsooqr_mode = grep { $form->{$_} } @bsooqr;
431 if ($form->{l_subtotal} && 1 < $bsooqr_mode) {
432 my $enabled = first { $form->{$_} } @bsooqr;
433 $form->{$_} = '' for @bsooqr;
434 $form->{$enabled} = 'Y';
436 push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
438 if ($form->{l_soldtotal} && !$bsooqr_mode) {
439 delete $form->{l_soldtotal};
441 flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
443 if ($form->{l_name} && !$bsooqr_mode) {
444 delete $form->{l_name};
446 flash('warning', $::locale->text('Name does not make sense without any bsooqr options'));
448 IC->all_parts(\%myconfig, \%$form);
451 partnumber type_and_classific description notes partsgroup bin onhand rop soldtotal unit listprice
452 linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
453 priceupdate weight image drawing microfiche invnumber ordnumber quonumber
454 transdate name serialnumber deliverydate ean projectnumber projectdescription
458 my $pricegroups = SL::DB::Manager::Pricegroup->get_all_sorted;
459 my @pricegroup_columns;
460 my %column_defs_pricegroups;
461 if ($form->{l_pricegroups}) {
462 @pricegroup_columns = map { "pricegroup_" . $_->id } @{ $pricegroups };
463 %column_defs_pricegroups = map {
464 "pricegroup_" . $_->id => {
465 text => $::locale->text('Pricegroup') . ' ' . $_->pricegroup,
470 push @columns, @pricegroup_columns;
472 my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
473 my @searchable_custom_variables = grep { $_->{searchable} } @{ $cvar_configs };
474 my %column_defs_cvars = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
476 push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
478 %column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
479 map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
480 map { $column_defs{$_}->{align} = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal shop), @pricegroup_columns;
482 my @hidden_variables = (
483 qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups insertdatefrom insertdateto),
484 qw(l_type_and_classific classification_id),
487 map({ "cvar_$_->{name}" } @searchable_custom_variables),
488 map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
489 map({ "l_$_" } @columns),
492 my $callback = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
494 my @sort_full = qw(partnumber description onhand soldtotal deliverydate insertdate shop);
495 my @sort_no_revers = qw(partsgroup bin priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
497 foreach my $col (@sort_full) {
498 $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
500 map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
502 # add order to callback
503 $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
505 my $report = SL::ReportGenerator->new(\%myconfig, $form);
507 my %attachment_basenames = (
508 'part' => $locale->text('part_list'),
509 'service' => $locale->text('service_list'),
510 'assembly' => $locale->text('assembly_list'),
511 'article' => $locale->text('article_list'),
514 $report->set_options('raw_top_info_text' => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
515 'raw_bottom_info_text' => $form->parse_html_template('ic/generate_report_bottom' ,
516 { PART_CLASSIFICATIONS => SL::DB::Manager::PartClassification->get_all_sorted }),
517 'output_format' => 'HTML',
518 'title' => $form->{title},
519 'attachment_basename' => 'article_list' . strftime('_%Y%m%d', localtime time),
521 $report->set_options_from_form();
522 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
524 $report->set_columns(%column_defs);
525 $report->set_column_order(@columns);
527 $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
529 $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
531 CVar->add_custom_variables_to_report('module' => 'IC',
532 'trans_id_field' => 'id',
533 'configs' => $cvar_configs,
534 'column_defs' => \%column_defs,
535 'data' => $form->{parts});
537 CVar->add_custom_variables_to_report('module' => 'IC',
538 'sub_module' => sub { $_[0]->{ioi} },
539 'trans_id_field' => 'ioi_id',
540 'configs' => $cvar_configs,
541 'column_defs' => \%column_defs,
542 'data' => $form->{parts});
544 my @subtotal_columns = qw(sellprice listprice lastcost);
545 my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
546 my %totals = map { $_ => 0 } @subtotal_columns;
548 my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
550 my $defaults = AM->get_defaults();
553 foreach my $ref (@{ $form->{parts} }) {
555 # fresh row, for inserting later
556 my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
558 $ref->{exchangerate} ||= 1;
559 $ref->{price_factor} ||= 1;
560 $ref->{sellprice} *= $ref->{exchangerate} / $ref->{price_factor};
561 $ref->{listprice} *= $ref->{exchangerate} / $ref->{price_factor};
562 $ref->{lastcost} *= $ref->{exchangerate} / $ref->{price_factor};
564 # use this for assemblies
565 my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
567 if ($ref->{assemblyitem}) {
568 $row->{partnumber}{align} = 'right';
569 $row->{soldtotal}{data} = 0;
570 $soldtotal = 0 if ($form->{sold});
573 my $edit_link = build_std_url('script=controller.pl', 'action=Part/edit', 'part.id=' . E($ref->{id}), 'callback');
574 $row->{partnumber}->{link} = $edit_link;
575 $row->{description}->{link} = $edit_link;
577 foreach (qw(sellprice listprice lastcost)) {
578 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}, 2);
579 $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
581 foreach ( @pricegroup_columns ) {
582 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
586 map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
588 $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
590 # 'yes' and 'no' for boolean value shop
591 if ($form->{l_shop}) {
592 $row->{shop}{data} = $row->{shop}{data}? $::locale->text('yes') : $::locale->text('no');
595 if (!$ref->{assemblyitem}) {
596 foreach my $col (@subtotal_columns) {
597 $totals{$col} += $soldtotal * $ref->{$col};
598 $subtotals{$col} += $soldtotal * $ref->{$col};
601 $subtotals{soldtotal} += $soldtotal;
605 if ($ref->{module} eq 'oe') {
606 # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
608 # | ist bestellt | Von Kunden bestellt | -> edit_oe_ord_link
609 # | Anfrage | Angebot | -> edit_oe_quo_link
611 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');
612 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');
614 $row->{ordnumber}{link} = $edit_oe_ord_link;
615 $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
618 $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback') if ($ref->{invnumber});
621 # set properties of images
622 if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
623 $row->{image}{data} = '';
624 $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
626 map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
628 $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
629 $row->{type_and_classific}{data} = $::request->presenter->type_abbreviation($ref->{part_type}).
630 $::request->presenter->classification_abbreviation($ref->{classification_id});
632 $report->add_data($row);
634 my $next_ref = $form->{parts}[$idx + 1];
636 # insert subtotal rows
637 if (($form->{l_subtotal} eq 'Y') &&
639 (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
640 my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
642 if ( !$form->{l_assembly} || !$form->{bom}) {
643 $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
646 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
647 map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
649 $report->add_data($row);
651 $same_item = $next_ref->{ $form->{sort} };
657 if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
658 my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
660 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
662 $report->add_separator();
663 $report->add_data($row);
666 $report->generate_with_headers();
668 $lxdebug->leave_sub();
669 } #end generate_report
671 sub ajax_autocomplete {
672 $main::lxdebug->enter_sub();
674 my $form = $main::form;
675 my %myconfig = %main::myconfig;
677 $form->{column} = 'description' unless $form->{column} =~ /^partnumber|description$/;
678 $form->{$form->{column}} = $form->{q} || '';
679 $form->{limit} = ($form->{limit} * 1) || 10;
680 $form->{searchitems} ||= '';
682 my @results = IC->all_parts(\%myconfig, $form);
684 print $form->ajax_response_header(),
685 $form->parse_html_template('ic/ajax_autocomplete');
687 $main::lxdebug->leave_sub();
694 delete @{$::form}{qw(action action_add action_back_to_record back_sub description item notes partnumber sellprice taxaccount2 unit vc)};
696 $::auth->restore_form_from_session($::form->{previousform}, clobber => 1);
697 $::form->{rowcount}--;
698 $::form->{action} = 'display_form';
699 $::form->{callback} = $::form->{script} . '?' . join('&', map { $::form->escape($_) . '=' . $::form->escape($::form->{$_}) } sort keys %{ $::form });
703 sub continue { call_sub($form->{"nextsub"}); }
706 my $action = first { $::form->{"action_${_}"} } qw(add back_to_record);
707 $::form->error($::locale->text('No action defined.')) unless $action;
709 $::form->{dispatched_action} = $action;