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 my %is_xyz = map { +"is_$_" => ($form->{searchitems} eq $_) } qw(part service assembly);
89 $form->{title} = (ucfirst $form->{searchitems}) . "s";
90 $form->{title} = $locale->text($form->{title});
91 $form->{title} = $locale->text('Assemblies') if ($is_xyz{is_assembly});
93 $form->{CUSTOM_VARIABLES} = CVar->get_configs('module' => 'IC');
94 ($form->{CUSTOM_VARIABLES_FILTER_CODE},
95 $form->{CUSTOM_VARIABLES_INCLUSION_CODE}) = CVar->render_search_options('variables' => $form->{CUSTOM_VARIABLES},
96 'include_prefix' => 'l_',
97 'include_value' => 'Y');
101 $form->get_lists('partsgroup' => 'ALL_PARTSGROUPS');
102 print $form->parse_html_template('ic/search', { %is_xyz, });
104 $lxdebug->leave_sub();
107 sub search_update_prices {
108 $lxdebug->enter_sub();
110 $auth->assert('part_service_assembly_edit');
112 my $pricegroups = IC->get_pricegroups(\%myconfig, \%$form);
114 $form->{title} = $locale->text('Update Prices');
118 print $form->parse_html_template('ic/search_update_prices', { PRICE_ROWS => $pricegroups });
120 $lxdebug->leave_sub();
123 sub confirm_price_update {
124 $lxdebug->enter_sub();
126 $auth->assert('part_service_assembly_edit');
129 my $value_found = undef;
131 foreach my $idx (qw(sellprice listprice), (1..$form->{price_rows})) {
132 my $name = $idx =~ m/\d/ ? $form->{"pricegroup_${idx}"} : $idx eq 'sellprice' ? $locale->text('Sell Price') : $locale->text('List Price');
133 my $type = $idx =~ m/\d/ ? $form->{"pricegroup_type_${idx}"} : $form->{"${idx}_type"};
134 my $value_idx = $idx =~ m/\d/ ? "price_${idx}" : $idx;
135 my $value = $form->parse_amount(\%myconfig, $form->{$value_idx});
137 if ((0 > $value) && ($type eq 'percent')) {
138 push @errors, $locale->text('You cannot adjust the price for pricegroup "#1" by a negative percentage.', $name);
140 } elsif (!$value && ($form->{$value_idx} ne '')) {
141 push @errors, $locale->text('No valid number entered for pricegroup "#1".', $name);
143 } elsif (0 < $value) {
148 push @errors, $locale->text('No prices will be updated because no prices have been entered.') if (!$value_found);
150 my $num_matches = IC->get_num_matches_for_priceupdate();
155 $form->show_generic_error(join('<br>', @errors), 'back_button' => 1);
158 $form->{nextsub} = "update_prices";
160 map { delete $form->{$_} } qw(action header);
162 print $form->parse_html_template('ic/confirm_price_update', { HIDDENS => [ map { name => $_, value => $form->{$_} }, keys %$form ],
163 num_matches => $num_matches });
165 $lxdebug->leave_sub();
169 $lxdebug->enter_sub();
171 $auth->assert('part_service_assembly_edit');
173 my $num_updated = IC->update_prices(\%myconfig, \%$form);
175 if (-1 != $num_updated) {
176 $form->redirect($locale->text('#1 prices were updated.', $num_updated));
178 $form->error($locale->text('Could not update prices!'));
181 $lxdebug->leave_sub();
185 $::lxdebug->enter_sub();
187 $::auth->assert('part_service_assembly_edit');
189 $::form->{l_soldtotal} = "Y";
190 $::form->{sort} = "soldtotal";
191 $::form->{lastsort} = "soldtotal";
193 $::form->{l_qty} = undef;
194 $::form->{l_linetotal} = undef;
195 $::form->{l_number} = "Y";
196 $::form->{number} = "position";
198 unless ( $::form->{bought}
201 || $::form->{quoted}) {
202 $::form->{bought} = $::form->{sold} = 1;
207 $lxdebug->leave_sub();
212 # Warning, deep magic ahead.
213 # This function parses the requested details, sanity checks them, and converts them into a format thats usable for IC->all_parts
215 # flags coming from the form:
217 # searchitems=part revers=0 lastsort=''
220 # partnumber ean description partsgroup serialnumber make model drawing microfiche
221 # transdatefrom transdateto
224 # itemstatus = active | onhand | short | obsolete | orphaned
225 # action = continue | top100
228 # bought sold onorder ordered rfq quoted
229 # l_partnumber l_description l_serialnumber l_unit l_listprice l_sellprice l_lastcost
230 # l_linetotal l_priceupdate l_bin l_rop l_weight l_image l_drawing l_microfiche
231 # l_partsgroup l_subtotal l_soldtotal l_deliverydate l_pricegroups
234 # nextsub revers lastsort sort ndxs_counter
236 sub generate_report {
237 $lxdebug->enter_sub();
239 $auth->assert('part_service_assembly_details');
241 my ($revers, $lastsort, $description);
243 my $cvar_configs = CVar->get_configs('module' => 'IC');
246 '' => $locale->text('Articles'),
247 part => $locale->text('Parts'),
248 service => $locale->text('Services'),
249 assembly => $locale->text('Assemblies'),
250 assortment => $locale->text('Assortments'),
253 $form->{title} = $titles{$form->{searchitems}};
256 'bin' => { 'text' => $locale->text('Bin'), },
257 'deliverydate' => { 'text' => $locale->text('deliverydate'), },
258 'description' => { 'text' => $locale->text('Part Description'), },
259 'notes' => { 'text' => $locale->text('Notes'), },
260 'drawing' => { 'text' => $locale->text('Drawing'), },
261 'ean' => { 'text' => $locale->text('EAN'), },
262 'image' => { 'text' => $locale->text('Image'), },
263 'insertdate' => { 'text' => $locale->text('Insert Date'), },
264 'invnumber' => { 'text' => $locale->text('Invoice Number'), },
265 'lastcost' => { 'text' => $locale->text('Last Cost'), },
266 'linetotallastcost' => { 'text' => $locale->text('Extended'), },
267 'linetotallistprice' => { 'text' => $locale->text('Extended'), },
268 'linetotalsellprice' => { 'text' => $locale->text('Extended'), },
269 'listprice' => { 'text' => $locale->text('List Price'), },
270 'microfiche' => { 'text' => $locale->text('Microfiche'), },
271 'name' => { 'text' => $locale->text('Name'), },
272 'onhand' => { 'text' => $locale->text('Stocked Qty'), },
273 'ordnumber' => { 'text' => $locale->text('Order Number'), },
274 'partnumber' => { 'text' => $locale->text('Part Number'), },
275 'partsgroup' => { 'text' => $locale->text('Partsgroup'), },
276 'priceupdate' => { 'text' => $locale->text('Updated'), },
277 'quonumber' => { 'text' => $locale->text('Quotation'), },
278 'rop' => { 'text' => $locale->text('ROP'), },
279 'sellprice' => { 'text' => $locale->text('Sell Price'), },
280 'serialnumber' => { 'text' => $locale->text('Serial Number'), },
281 'soldtotal' => { 'text' => $locale->text('Qty in Selected Records'), },
282 'name' => { 'text' => $locale->text('Name in Selected Records'), },
283 'transdate' => { 'text' => $locale->text('Transdate'), },
284 'unit' => { 'text' => $locale->text('Unit'), },
285 'weight' => { 'text' => $locale->text('Weight'), },
286 'shop' => { 'text' => $locale->text('Shop article'), },
287 'projectnumber' => { 'text' => $locale->text('Project Number'), },
288 'projectdescription' => { 'text' => $locale->text('Project Description'), },
291 $revers = $form->{revers};
292 $lastsort = $form->{lastsort};
294 # sorting and direction of sorting
295 # ToDO: change this to the simpler field+direction method
296 if (($form->{lastsort} eq "") && ($form->{sort} eq undef)) {
298 $form->{lastsort} = "partnumber";
299 $form->{sort} = "partnumber";
301 if ($form->{lastsort} eq $form->{sort}) {
302 $form->{revers} = 1 - $form->{revers};
305 $form->{lastsort} = $form->{sort};
309 # special case if we have a serialnumber limit search
310 # serialnumbers are only given in invoices and orders,
311 # so they can only pop up in bought, sold, rfq, and quoted stuff
312 $form->{no_sn_joins} = 'Y' if ( !$form->{bought} && !$form->{sold}
313 && !$form->{rfq} && !$form->{quoted}
314 && ($form->{l_serialnumber} || $form->{serialnumber}));
316 # special case for any checkbox of bought | sold | onorder | ordered | rfq | quoted.
317 # if any of these are ticked the behavior changes slightly for lastcost
318 # since all those are aggregation checks for the legder tables this is an internal switch
319 # refered to as ledgerchecks
320 $form->{ledgerchecks} = 'Y' if ( $form->{bought} || $form->{sold} || $form->{onorder}
321 || $form->{ordered} || $form->{rfq} || $form->{quoted});
323 # if something should be activated if something else is active, enter it here
325 onhand => [ qw(l_onhand) ],
326 short => [ qw(l_onhand) ],
327 onorder => [ qw(l_ordnumber) ],
328 ordered => [ qw(l_ordnumber) ],
329 rfq => [ qw(l_quonumber) ],
330 quoted => [ qw(l_quonumber) ],
331 bought => [ qw(l_invnumber) ],
332 sold => [ qw(l_invnumber) ],
333 ledgerchecks => [ qw(l_name) ],
334 serialnumber => [ qw(l_serialnumber) ],
335 no_sn_joins => [ qw(bought sold) ],
338 # get name of partsgroup if id is given
340 if ($form->{partsgroup_id}) {
341 my $pg = SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load;
342 $pg_name = $pg->{'partsgroup'};
345 # these strings get displayed at the top of the results to indicate the user which switches were used
347 active => $locale->text('Active'),
348 obsolete => $locale->text('Obsolete'),
349 orphaned => $locale->text('Orphaned'),
350 onhand => $locale->text('On Hand'),
351 short => $locale->text('Short'),
352 onorder => $locale->text('On Order'),
353 ordered => $locale->text('Ordered'),
354 rfq => $locale->text('RFQ'),
355 quoted => $locale->text('Quoted'),
356 bought => $locale->text('Bought'),
357 sold => $locale->text('Sold'),
358 transdatefrom => $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1),
359 transdateto => $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1),
360 partnumber => $locale->text('Part Number') . ": '$form->{partnumber}'",
361 partsgroup => $locale->text('Partsgroup') . ": '$form->{partsgroup}'",
362 partsgroup_id => $locale->text('Partsgroup') . ": '$pg_name'",
363 serialnumber => $locale->text('Serial Number') . ": '$form->{serialnumber}'",
364 description => $locale->text('Part Description') . ": '$form->{description}'",
365 make => $locale->text('Make') . ": '$form->{make}'",
366 model => $locale->text('Model') . ": '$form->{model}'",
367 drawing => $locale->text('Drawing') . ": '$form->{drawing}'",
368 microfiche => $locale->text('Microfiche') . ": '$form->{microfiche}'",
369 l_soldtotal => $locale->text('Qty in Selected Records'),
370 ean => $locale->text('EAN') . ": '$form->{ean}'",
371 insertdatefrom => $locale->text('Insert Date') . ": " . $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1),
372 insertdateto => $locale->text('Insert Date') . ": " . $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1),
375 my @itemstatus_keys = qw(active obsolete orphaned onhand short);
376 my @callback_keys = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
377 drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto insertdatefrom insertdateto ean shop all);
379 # calculate dependencies
380 for (@itemstatus_keys, @callback_keys) {
381 next if ($form->{itemstatus} ne $_ && !$form->{$_});
382 map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
385 # generate callback and optionstrings
387 for my $key (@itemstatus_keys, @callback_keys) {
388 next if ($form->{itemstatus} ne $key && !$form->{$key});
389 push @options, $optiontexts{$key};
392 # special case for lastcost
393 if ($form->{ledgerchecks}){
394 # ledgerchecks don't know about sellprice or lastcost. they just return a
395 # price. so rename sellprice to price, and drop lastcost.
396 $column_defs{sellprice}{text} = $locale->text('Price');
397 $form->{l_lastcost} = ""
400 if ($form->{description}) {
401 $description = $form->{description};
402 $description =~ s/\n/<br>/g;
405 if ($form->{l_linetotal}) {
406 $form->{l_qty} = "Y";
407 $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
408 $form->{l_linetotallastcost} = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if $form->{l_lastcost};
409 $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
412 if ($form->{searchitems} eq 'service') {
414 # remove bin, weight and rop from list
415 map { $form->{"l_$_"} = "" } qw(bin weight rop);
417 $form->{l_onhand} = "";
419 # qty is irrelevant unless bought or sold
425 || $form->{quoted}) {
426 # $form->{l_onhand} = "Y";
428 $form->{l_linetotalsellprice} = "";
429 $form->{l_linetotallastcost} = "";
433 # soldtotal doesn't make sense with more than one bsooqr option.
434 # so reset it to sold (the most common option), and issue a warning
436 # also it doesn't make sense without bsooqr. disable and issue a warning too
437 my @bsooqr = qw(sold bought onorder ordered rfq quoted);
438 my $bsooqr_mode = grep { $form->{$_} } @bsooqr;
439 if ($form->{l_subtotal} && 1 < $bsooqr_mode) {
440 my $enabled = first { $form->{$_} } @bsooqr;
441 $form->{$_} = '' for @bsooqr;
442 $form->{$enabled} = 'Y';
444 push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
446 if ($form->{l_soldtotal} && !$bsooqr_mode) {
447 delete $form->{l_soldtotal};
449 flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
451 if ($form->{l_name} && !$bsooqr_mode) {
452 delete $form->{l_name};
454 flash('warning', $::locale->text('Name does not make sense without any bsooqr options'));
456 IC->all_parts(\%myconfig, \%$form);
459 partnumber description notes partsgroup bin onhand rop soldtotal unit listprice
460 linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
461 priceupdate weight image drawing microfiche invnumber ordnumber quonumber
462 transdate name serialnumber deliverydate ean projectnumber projectdescription
466 my $pricegroups = SL::DB::Manager::Pricegroup->get_all_sorted;
467 my @pricegroup_columns;
468 my %column_defs_pricegroups;
469 if ($form->{l_pricegroups}) {
470 @pricegroup_columns = map { "pricegroup_" . $_->id } @{ $pricegroups };
471 %column_defs_pricegroups = map {
472 "pricegroup_" . $_->id => {
473 text => $::locale->text('Pricegroup') . ' ' . $_->pricegroup,
478 push @columns, @pricegroup_columns;
480 my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
481 my @searchable_custom_variables = grep { $_->{searchable} } @{ $cvar_configs };
482 my %column_defs_cvars = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
484 push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
486 %column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
487 map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
488 map { $column_defs{$_}->{align} = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal shop), @pricegroup_columns;
490 my @hidden_variables = (
491 qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups insertdatefrom insertdateto),
494 map({ "cvar_$_->{name}" } @searchable_custom_variables),
495 map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
496 map({ "l_$_" } @columns),
499 my $callback = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
501 my @sort_full = qw(partnumber description onhand soldtotal deliverydate insertdate shop);
502 my @sort_no_revers = qw(partsgroup bin priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
504 foreach my $col (@sort_full) {
505 $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
507 map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
509 # add order to callback
510 $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
512 my $report = SL::ReportGenerator->new(\%myconfig, $form);
514 my %attachment_basenames = (
515 'part' => $locale->text('part_list'),
516 'service' => $locale->text('service_list'),
517 'assembly' => $locale->text('assembly_list'),
520 $report->set_options('raw_top_info_text' => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
521 'raw_bottom_info_text' => $form->parse_html_template('ic/generate_report_bottom'),
522 'output_format' => 'HTML',
523 'title' => $form->{title},
524 'attachment_basename' => $attachment_basenames{$form->{searchitems}} . strftime('_%Y%m%d', localtime time),
526 $report->set_options_from_form();
527 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
529 $report->set_columns(%column_defs);
530 $report->set_column_order(@columns);
532 $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
534 $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
536 CVar->add_custom_variables_to_report('module' => 'IC',
537 'trans_id_field' => 'id',
538 'configs' => $cvar_configs,
539 'column_defs' => \%column_defs,
540 'data' => $form->{parts});
542 CVar->add_custom_variables_to_report('module' => 'IC',
543 'sub_module' => sub { $_[0]->{ioi} },
544 'trans_id_field' => 'ioi_id',
545 'configs' => $cvar_configs,
546 'column_defs' => \%column_defs,
547 'data' => $form->{parts});
549 my @subtotal_columns = qw(sellprice listprice lastcost);
550 my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
551 my %totals = map { $_ => 0 } @subtotal_columns;
553 my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
555 my $defaults = AM->get_defaults();
558 foreach my $ref (@{ $form->{parts} }) {
560 # fresh row, for inserting later
561 my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
563 $ref->{exchangerate} ||= 1;
564 $ref->{price_factor} ||= 1;
565 $ref->{sellprice} *= $ref->{exchangerate} / $ref->{price_factor};
566 $ref->{listprice} *= $ref->{exchangerate} / $ref->{price_factor};
567 $ref->{lastcost} *= $ref->{exchangerate} / $ref->{price_factor};
569 # use this for assemblies
570 my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
572 if ($ref->{assemblyitem}) {
573 $row->{partnumber}{align} = 'right';
574 $row->{soldtotal}{data} = 0;
575 $soldtotal = 0 if ($form->{sold});
578 my $edit_link = build_std_url('script=controller.pl', 'action=Part/edit', 'part.id=' . E($ref->{id}), 'callback');
579 $row->{partnumber}->{link} = $edit_link;
580 $row->{description}->{link} = $edit_link;
582 foreach (qw(sellprice listprice lastcost)) {
583 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}, 2);
584 $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
586 foreach ( @pricegroup_columns ) {
587 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
591 map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
593 $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
595 # 'yes' and 'no' for boolean value shop
596 if ($form->{l_shop}) {
597 $row->{shop}{data} = $row->{shop}{data}? $::locale->text('yes') : $::locale->text('no');
600 if (!$ref->{assemblyitem}) {
601 foreach my $col (@subtotal_columns) {
602 $totals{$col} += $soldtotal * $ref->{$col};
603 $subtotals{$col} += $soldtotal * $ref->{$col};
606 $subtotals{soldtotal} += $soldtotal;
610 if ($ref->{module} eq 'oe') {
611 # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
613 # | ist bestellt | Von Kunden bestellt | -> edit_oe_ord_link
614 # | Anfrage | Angebot | -> edit_oe_quo_link
616 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');
617 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');
619 $row->{ordnumber}{link} = $edit_oe_ord_link;
620 $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
623 $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback') if ($ref->{invnumber});
626 # set properties of images
627 if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
628 $row->{image}{data} = '';
629 $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
631 map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
633 $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
635 $report->add_data($row);
637 my $next_ref = $form->{parts}[$idx + 1];
639 # insert subtotal rows
640 if (($form->{l_subtotal} eq 'Y') &&
642 (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
643 my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
645 if (($form->{searchitems} ne 'assembly') || !$form->{bom}) {
646 $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
649 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
650 map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
652 $report->add_data($row);
654 $same_item = $next_ref->{ $form->{sort} };
660 if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
661 my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
663 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
665 $report->add_separator();
666 $report->add_data($row);
669 $report->generate_with_headers();
671 $lxdebug->leave_sub();
672 } #end generate_report
674 sub ajax_autocomplete {
675 $main::lxdebug->enter_sub();
677 my $form = $main::form;
678 my %myconfig = %main::myconfig;
680 $form->{column} = 'description' unless $form->{column} =~ /^partnumber|description$/;
681 $form->{$form->{column}} = $form->{q} || '';
682 $form->{limit} = ($form->{limit} * 1) || 10;
683 $form->{searchitems} ||= '';
685 my @results = IC->all_parts(\%myconfig, $form);
687 print $form->ajax_response_header(),
688 $form->parse_html_template('ic/ajax_autocomplete');
690 $main::lxdebug->leave_sub();
697 delete @{$::form}{qw(action action_add action_back_to_record back_sub description item notes partnumber sellprice taxaccount2 unit vc)};
699 $::auth->restore_form_from_session($::form->{previousform}, clobber => 1);
700 $::form->{rowcount}--;
701 $::form->{action} = 'display_form';
702 $::form->{callback} = $::form->{script} . '?' . join('&', map { $::form->escape($_) . '=' . $::form->escape($::form->{$_}) } sort keys %{ $::form });
706 sub continue { call_sub($form->{"nextsub"}); }
709 my $action = first { $::form->{"action_${_}"} } qw(add back_to_record);
710 $::form->error($::locale->text('No action defined.')) unless $action;
712 $::form->{dispatched_action} = $action;