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_edit');
82 my $title = 'Add ' . ucfirst $form->{item};
83 $form->{title} = $locale->text($title);
84 $form->{callback} = "$form->{script}?action=add&item=$form->{item}" unless $form->{callback};
85 $form->{unit_changeable} = 1;
87 IC->get_pricegroups(\%myconfig, \%$form);
91 $lxdebug->leave_sub();
95 $lxdebug->enter_sub();
97 $auth->assert('part_service_assembly_details');
99 $form->{revers} = 0; # switch for backward sorting
100 $form->{lastsort} = ""; # memory for which table was sort at last time
101 $form->{ndxs_counter} = 0; # counter for added entries to top100
103 my %is_xyz = map { +"is_$_" => ($form->{searchitems} eq $_) } qw(part service assembly);
105 $form->{title} = (ucfirst $form->{searchitems}) . "s";
106 $form->{title} = $locale->text($form->{title});
107 $form->{title} = $locale->text('Assemblies') if ($is_xyz{is_assembly});
109 $form->{CUSTOM_VARIABLES} = CVar->get_configs('module' => 'IC');
110 ($form->{CUSTOM_VARIABLES_FILTER_CODE},
111 $form->{CUSTOM_VARIABLES_INCLUSION_CODE}) = CVar->render_search_options('variables' => $form->{CUSTOM_VARIABLES},
112 'include_prefix' => 'l_',
113 'include_value' => 'Y');
117 $form->get_lists('partsgroup' => 'ALL_PARTSGROUPS');
118 print $form->parse_html_template('ic/search', { %is_xyz, });
120 $lxdebug->leave_sub();
123 sub search_update_prices {
124 $lxdebug->enter_sub();
126 $auth->assert('part_service_assembly_edit');
128 my $pricegroups = IC->get_pricegroups(\%myconfig, \%$form);
130 $form->{title} = $locale->text('Update Prices');
134 print $form->parse_html_template('ic/search_update_prices', { PRICE_ROWS => $pricegroups });
136 $lxdebug->leave_sub();
139 sub confirm_price_update {
140 $lxdebug->enter_sub();
142 $auth->assert('part_service_assembly_edit');
145 my $value_found = undef;
147 foreach my $idx (qw(sellprice listprice), (1..$form->{price_rows})) {
148 my $name = $idx =~ m/\d/ ? $form->{"pricegroup_${idx}"} : $idx eq 'sellprice' ? $locale->text('Sell Price') : $locale->text('List Price');
149 my $type = $idx =~ m/\d/ ? $form->{"pricegroup_type_${idx}"} : $form->{"${idx}_type"};
150 my $value_idx = $idx =~ m/\d/ ? "price_${idx}" : $idx;
151 my $value = $form->parse_amount(\%myconfig, $form->{$value_idx});
153 if ((0 > $value) && ($type eq 'percent')) {
154 push @errors, $locale->text('You cannot adjust the price for pricegroup "#1" by a negative percentage.', $name);
156 } elsif (!$value && ($form->{$value_idx} ne '')) {
157 push @errors, $locale->text('No valid number entered for pricegroup "#1".', $name);
159 } elsif (0 < $value) {
164 push @errors, $locale->text('No prices will be updated because no prices have been entered.') if (!$value_found);
166 my $num_matches = IC->get_num_matches_for_priceupdate();
171 $form->show_generic_error(join('<br>', @errors), 'back_button' => 1);
174 $form->{nextsub} = "update_prices";
176 map { delete $form->{$_} } qw(action header);
178 print $form->parse_html_template('ic/confirm_price_update', { HIDDENS => [ map { name => $_, value => $form->{$_} }, keys %$form ],
179 num_matches => $num_matches });
181 $lxdebug->leave_sub();
185 $lxdebug->enter_sub();
187 $auth->assert('part_service_assembly_edit');
189 my $num_updated = IC->update_prices(\%myconfig, \%$form);
191 if (-1 != $num_updated) {
192 $form->redirect($locale->text('#1 prices were updated.', $num_updated));
194 $form->error($locale->text('Could not update prices!'));
197 $lxdebug->leave_sub();
201 $::lxdebug->enter_sub();
203 $::auth->assert('part_service_assembly_edit');
205 $::form->{l_soldtotal} = "Y";
206 $::form->{sort} = "soldtotal";
207 $::form->{lastsort} = "soldtotal";
209 $::form->{l_qty} = undef;
210 $::form->{l_linetotal} = undef;
211 $::form->{l_number} = "Y";
212 $::form->{number} = "position";
214 unless ( $::form->{bought}
217 || $::form->{quoted}) {
218 $::form->{bought} = $::form->{sold} = 1;
223 $lxdebug->leave_sub();
228 # Warning, deep magic ahead.
229 # This function parses the requested details, sanity checks them, and converts them into a format thats usable for IC->all_parts
231 # flags coming from the form:
233 # searchitems=part revers=0 lastsort=''
236 # partnumber ean description partsgroup serialnumber make model drawing microfiche
237 # transdatefrom transdateto
240 # itemstatus = active | onhand | short | obsolete | orphaned
241 # action = continue | top100
244 # bought sold onorder ordered rfq quoted
245 # l_partnumber l_description l_serialnumber l_unit l_listprice l_sellprice l_lastcost
246 # l_linetotal l_priceupdate l_bin l_rop l_weight l_image l_drawing l_microfiche
247 # l_partsgroup l_subtotal l_soldtotal l_deliverydate l_pricegroups
250 # nextsub revers lastsort sort ndxs_counter
252 sub generate_report {
253 $lxdebug->enter_sub();
255 $auth->assert('part_service_assembly_details');
257 my ($revers, $lastsort, $description);
259 my $cvar_configs = CVar->get_configs('module' => 'IC');
262 '' => $locale->text('Articles'),
263 part => $locale->text('Parts'),
264 service => $locale->text('Services'),
265 assembly => $locale->text('Assemblies'),
268 $form->{title} = $titles{$form->{searchitems}};
271 'bin' => { 'text' => $locale->text('Bin'), },
272 'deliverydate' => { 'text' => $locale->text('deliverydate'), },
273 'description' => { 'text' => $locale->text('Part Description'), },
274 'notes' => { 'text' => $locale->text('Notes'), },
275 'drawing' => { 'text' => $locale->text('Drawing'), },
276 'ean' => { 'text' => $locale->text('EAN'), },
277 'image' => { 'text' => $locale->text('Image'), },
278 'insertdate' => { 'text' => $locale->text('Insert Date'), },
279 'invnumber' => { 'text' => $locale->text('Invoice Number'), },
280 'lastcost' => { 'text' => $locale->text('Last Cost'), },
281 'linetotallastcost' => { 'text' => $locale->text('Extended'), },
282 'linetotallistprice' => { 'text' => $locale->text('Extended'), },
283 'linetotalsellprice' => { 'text' => $locale->text('Extended'), },
284 'listprice' => { 'text' => $locale->text('List Price'), },
285 'microfiche' => { 'text' => $locale->text('Microfiche'), },
286 'name' => { 'text' => $locale->text('Name'), },
287 'onhand' => { 'text' => $locale->text('Stocked Qty'), },
288 'ordnumber' => { 'text' => $locale->text('Order Number'), },
289 'partnumber' => { 'text' => $locale->text('Part Number'), },
290 'partsgroup' => { 'text' => $locale->text('Group'), },
291 'priceupdate' => { 'text' => $locale->text('Updated'), },
292 'quonumber' => { 'text' => $locale->text('Quotation'), },
293 'rop' => { 'text' => $locale->text('ROP'), },
294 'sellprice' => { 'text' => $locale->text('Sell Price'), },
295 'serialnumber' => { 'text' => $locale->text('Serial Number'), },
296 'soldtotal' => { 'text' => $locale->text('Qty in Selected Records'), },
297 'name' => { 'text' => $locale->text('Name in Selected Records'), },
298 'transdate' => { 'text' => $locale->text('Transdate'), },
299 'unit' => { 'text' => $locale->text('Unit'), },
300 'weight' => { 'text' => $locale->text('Weight'), },
301 'shop' => { 'text' => $locale->text('Shop article'), },
302 'projectnumber' => { 'text' => $locale->text('Project Number'), },
303 'projectdescription' => { 'text' => $locale->text('Project Description'), },
306 $revers = $form->{revers};
307 $lastsort = $form->{lastsort};
309 # sorting and direction of sorting
310 # ToDO: change this to the simpler field+direction method
311 if (($form->{lastsort} eq "") && ($form->{sort} eq undef)) {
313 $form->{lastsort} = "partnumber";
314 $form->{sort} = "partnumber";
316 if ($form->{lastsort} eq $form->{sort}) {
317 $form->{revers} = 1 - $form->{revers};
320 $form->{lastsort} = $form->{sort};
324 # special case if we have a serialnumber limit search
325 # serialnumbers are only given in invoices and orders,
326 # so they can only pop up in bought, sold, rfq, and quoted stuff
327 $form->{no_sn_joins} = 'Y' if ( !$form->{bought} && !$form->{sold}
328 && !$form->{rfq} && !$form->{quoted}
329 && ($form->{l_serialnumber} || $form->{serialnumber}));
331 # special case for any checkbox of bought | sold | onorder | ordered | rfq | quoted.
332 # if any of these are ticked the behavior changes slightly for lastcost
333 # since all those are aggregation checks for the legder tables this is an internal switch
334 # refered to as ledgerchecks
335 $form->{ledgerchecks} = 'Y' if ( $form->{bought} || $form->{sold} || $form->{onorder}
336 || $form->{ordered} || $form->{rfq} || $form->{quoted});
338 # if something should be activated if something else is active, enter it here
340 onhand => [ qw(l_onhand) ],
341 short => [ qw(l_onhand) ],
342 onorder => [ qw(l_ordnumber) ],
343 ordered => [ qw(l_ordnumber) ],
344 rfq => [ qw(l_quonumber) ],
345 quoted => [ qw(l_quonumber) ],
346 bought => [ qw(l_invnumber) ],
347 sold => [ qw(l_invnumber) ],
348 ledgerchecks => [ qw(l_name) ],
349 serialnumber => [ qw(l_serialnumber) ],
350 no_sn_joins => [ qw(bought sold) ],
353 # get name of partsgroup if id is given
355 if ($form->{partsgroup_id}) {
356 my $pg = SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load;
357 $pg_name = $pg->{'partsgroup'};
360 # these strings get displayed at the top of the results to indicate the user which switches were used
362 active => $locale->text('Active'),
363 obsolete => $locale->text('Obsolete'),
364 orphaned => $locale->text('Orphaned'),
365 onhand => $locale->text('On Hand'),
366 short => $locale->text('Short'),
367 onorder => $locale->text('On Order'),
368 ordered => $locale->text('Ordered'),
369 rfq => $locale->text('RFQ'),
370 quoted => $locale->text('Quoted'),
371 bought => $locale->text('Bought'),
372 sold => $locale->text('Sold'),
373 transdatefrom => $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1),
374 transdateto => $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1),
375 partnumber => $locale->text('Part Number') . ": '$form->{partnumber}'",
376 partsgroup => $locale->text('Group') . ": '$form->{partsgroup}'",
377 partsgroup_id => $locale->text('Group') . ": '$pg_name'",
378 serialnumber => $locale->text('Serial Number') . ": '$form->{serialnumber}'",
379 description => $locale->text('Part Description') . ": '$form->{description}'",
380 make => $locale->text('Make') . ": '$form->{make}'",
381 model => $locale->text('Model') . ": '$form->{model}'",
382 drawing => $locale->text('Drawing') . ": '$form->{drawing}'",
383 microfiche => $locale->text('Microfiche') . ": '$form->{microfiche}'",
384 l_soldtotal => $locale->text('Qty in Selected Records'),
385 ean => $locale->text('EAN') . ": '$form->{ean}'",
386 insertdatefrom => $locale->text('Insert Date') . ": " . $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1),
387 insertdateto => $locale->text('Insert Date') . ": " . $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1),
390 my @itemstatus_keys = qw(active obsolete orphaned onhand short);
391 my @callback_keys = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
392 drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto insertdatefrom insertdateto ean shop all);
394 # calculate dependencies
395 for (@itemstatus_keys, @callback_keys) {
396 next if ($form->{itemstatus} ne $_ && !$form->{$_});
397 map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
400 # generate callback and optionstrings
402 for my $key (@itemstatus_keys, @callback_keys) {
403 next if ($form->{itemstatus} ne $key && !$form->{$key});
404 push @options, $optiontexts{$key};
407 # special case for lastcost
408 if ($form->{ledgerchecks}){
409 # ledgerchecks don't know about sellprice or lastcost. they just return a
410 # price. so rename sellprice to price, and drop lastcost.
411 $column_defs{sellprice}{text} = $locale->text('Price');
412 $form->{l_lastcost} = ""
415 if ($form->{description}) {
416 $description = $form->{description};
417 $description =~ s/\n/<br>/g;
420 if ($form->{l_linetotal}) {
421 $form->{l_qty} = "Y";
422 $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
423 $form->{l_linetotallastcost} = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if $form->{l_lastcost};
424 $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
427 if ($form->{searchitems} eq 'service') {
429 # remove bin, weight and rop from list
430 map { $form->{"l_$_"} = "" } qw(bin weight rop);
432 $form->{l_onhand} = "";
434 # qty is irrelevant unless bought or sold
440 || $form->{quoted}) {
441 # $form->{l_onhand} = "Y";
443 $form->{l_linetotalsellprice} = "";
444 $form->{l_linetotallastcost} = "";
448 # soldtotal doesn't make sense with more than one bsooqr option.
449 # so reset it to sold (the most common option), and issue a warning
451 # also it doesn't make sense without bsooqr. disable and issue a warning too
452 my @bsooqr = qw(sold bought onorder ordered rfq quoted);
453 my $bsooqr_mode = grep { $form->{$_} } @bsooqr;
454 if ($form->{l_subtotal} && 1 < $bsooqr_mode) {
455 my $enabled = first { $form->{$_} } @bsooqr;
456 $form->{$_} = '' for @bsooqr;
457 $form->{$enabled} = 'Y';
459 push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
461 if ($form->{l_soldtotal} && !$bsooqr_mode) {
462 delete $form->{l_soldtotal};
464 flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
466 if ($form->{l_name} && !$bsooqr_mode) {
467 delete $form->{l_name};
469 flash('warning', $::locale->text('Name does not make sense without any bsooqr options'));
471 IC->all_parts(\%myconfig, \%$form);
474 partnumber description notes partsgroup bin onhand rop soldtotal unit listprice
475 linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
476 priceupdate weight image drawing microfiche invnumber ordnumber quonumber
477 transdate name serialnumber deliverydate ean projectnumber projectdescription
481 my $pricegroups = SL::DB::Manager::Pricegroup->get_all_sorted;
482 my @pricegroup_columns;
483 my %column_defs_pricegroups;
484 if ($form->{l_pricegroups}) {
485 @pricegroup_columns = map { "pricegroup_" . $_->id } @{ $pricegroups };
486 %column_defs_pricegroups = map {
487 "pricegroup_" . $_->id => {
488 text => $::locale->text('Pricegroup') . ' ' . $_->pricegroup,
493 push @columns, @pricegroup_columns;
495 my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
496 my @searchable_custom_variables = grep { $_->{searchable} } @{ $cvar_configs };
497 my %column_defs_cvars = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
499 push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
501 %column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
502 map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
503 map { $column_defs{$_}->{align} = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal shop), @pricegroup_columns;
505 my @hidden_variables = (
506 qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups insertdatefrom insertdateto),
509 map({ "cvar_$_->{name}" } @searchable_custom_variables),
510 map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
511 map({ "l_$_" } @columns),
514 my $callback = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
516 my @sort_full = qw(partnumber description onhand soldtotal deliverydate insertdate shop);
517 my @sort_no_revers = qw(partsgroup bin priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
519 foreach my $col (@sort_full) {
520 $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
522 map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
524 # add order to callback
525 $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
527 my $report = SL::ReportGenerator->new(\%myconfig, $form);
529 my %attachment_basenames = (
530 'part' => $locale->text('part_list'),
531 'service' => $locale->text('service_list'),
532 'assembly' => $locale->text('assembly_list'),
535 $report->set_options('raw_top_info_text' => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
536 'raw_bottom_info_text' => $form->parse_html_template('ic/generate_report_bottom'),
537 'output_format' => 'HTML',
538 'title' => $form->{title},
539 'attachment_basename' => $attachment_basenames{$form->{searchitems}} . strftime('_%Y%m%d', localtime time),
541 $report->set_options_from_form();
542 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
544 $report->set_columns(%column_defs);
545 $report->set_column_order(@columns);
547 $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
549 $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
551 CVar->add_custom_variables_to_report('module' => 'IC',
552 'trans_id_field' => 'id',
553 'configs' => $cvar_configs,
554 'column_defs' => \%column_defs,
555 'data' => $form->{parts});
557 CVar->add_custom_variables_to_report('module' => 'IC',
558 'sub_module' => sub { $_[0]->{ioi} },
559 'trans_id_field' => 'ioi_id',
560 'configs' => $cvar_configs,
561 'column_defs' => \%column_defs,
562 'data' => $form->{parts});
564 my @subtotal_columns = qw(sellprice listprice lastcost);
565 my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
566 my %totals = map { $_ => 0 } @subtotal_columns;
568 my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
570 my $defaults = AM->get_defaults();
573 foreach my $ref (@{ $form->{parts} }) {
575 # fresh row, for inserting later
576 my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
578 $ref->{exchangerate} ||= 1;
579 $ref->{price_factor} ||= 1;
580 $ref->{sellprice} *= $ref->{exchangerate} / $ref->{price_factor};
581 $ref->{listprice} *= $ref->{exchangerate} / $ref->{price_factor};
582 $ref->{lastcost} *= $ref->{exchangerate} / $ref->{price_factor};
584 # use this for assemblies
585 my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
587 if ($ref->{assemblyitem}) {
588 $row->{partnumber}{align} = 'right';
589 $row->{soldtotal}{data} = 0;
590 $soldtotal = 0 if ($form->{sold});
593 my $edit_link = build_std_url('action=edit', 'id=' . E($ref->{id}), 'callback');
594 $row->{partnumber}->{link} = $edit_link;
595 $row->{description}->{link} = $edit_link;
597 foreach (qw(sellprice listprice lastcost)) {
598 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}, 2);
599 $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
601 foreach ( @pricegroup_columns ) {
602 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
606 map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
608 $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
610 # 'yes' and 'no' for boolean value shop
611 if ($form->{l_shop}) {
612 $row->{shop}{data} = $row->{shop}{data}? $::locale->text('yes') : $::locale->text('no');
615 if (!$ref->{assemblyitem}) {
616 foreach my $col (@subtotal_columns) {
617 $totals{$col} += $soldtotal * $ref->{$col};
618 $subtotals{$col} += $soldtotal * $ref->{$col};
621 $subtotals{soldtotal} += $soldtotal;
625 if ($ref->{module} eq 'oe') {
626 # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
628 # | ist bestellt | Von Kunden bestellt | -> edit_oe_ord_link
629 # | Anfrage | Angebot | -> edit_oe_quo_link
631 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');
632 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');
634 $row->{ordnumber}{link} = $edit_oe_ord_link;
635 $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
638 $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback') if ($ref->{invnumber});
641 # set properties of images
642 if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
643 $row->{image}{data} = '';
644 $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
646 map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
648 $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
650 $report->add_data($row);
652 my $next_ref = $form->{parts}[$idx + 1];
654 # insert subtotal rows
655 if (($form->{l_subtotal} eq 'Y') &&
657 (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
658 my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
660 if (($form->{searchitems} ne 'assembly') || !$form->{bom}) {
661 $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
664 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
665 map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
667 $report->add_data($row);
669 $same_item = $next_ref->{ $form->{sort} };
675 if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
676 my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
678 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
680 $report->add_separator();
681 $report->add_data($row);
684 $report->generate_with_headers();
686 $lxdebug->leave_sub();
687 } #end generate_report
690 $lxdebug->enter_sub();
692 $auth->assert('part_service_assembly_edit');
695 my ($column_index, $subtotalonhand, $subtotalsellprice, $subtotallastcost, $subtotallistprice) = @_;
697 map { $column_data{$_} = "<td> </td>" } @{ $column_index };
698 $$subtotalonhand = 0 if ($form->{searchitems} eq 'assembly' && $form->{bom});
700 $column_data{onhand} =
701 "<th class=listsubtotal align=right>"
702 . $form->format_amount(\%myconfig, $$subtotalonhand)
705 $column_data{linetotalsellprice} =
706 "<th class=listsubtotal align=right>"
707 . $form->format_amount(\%myconfig, $$subtotalsellprice, 2)
709 $column_data{linetotallistprice} =
710 "<th class=listsubtotal align=right>"
711 . $form->format_amount(\%myconfig, $$subtotallistprice, 2)
713 $column_data{linetotallastcost} =
714 "<th class=listsubtotal align=right>"
715 . $form->format_amount(\%myconfig, $$subtotallastcost, 2)
718 $$subtotalonhand = 0;
719 $$subtotalsellprice = 0;
720 $$subtotallistprice = 0;
721 $$subtotallastcost = 0;
723 print "<tr class=listsubtotal>";
725 map { print "\n$column_data{$_}" } @{ $column_index };
731 $lxdebug->leave_sub();
735 $lxdebug->enter_sub();
737 $auth->assert('part_service_assembly_details');
739 # show history button
740 $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
741 #/show hhistory button
742 IC->get_part(\%myconfig, \%$form);
744 $form->{"original_partnumber"} = $form->{"partnumber"};
746 my $title = 'Edit ' . ucfirst $form->{item};
747 $form->{title} = $locale->text($title);
752 $lxdebug->leave_sub();
756 $lxdebug->enter_sub();
758 $auth->assert('part_service_assembly_details');
760 IC->create_links("IC", \%myconfig, \%$form);
763 map({ $form->{selectcurrency} .= "<option>$_\n" } $::form->get_all_currencies());
765 # parts and assemblies have the same links
766 my $item = $form->{item};
767 if ($form->{item} eq 'assembly') {
771 # build the popup menus
772 $form->{taxaccounts} = "";
773 foreach my $key (keys %{ $form->{IC_links} }) {
774 foreach my $ref (@{ $form->{IC_links}{$key} }) {
776 # if this is a tax field
777 if ($key =~ /IC_tax/) {
778 if ($key =~ /\Q$item\E/) {
779 $form->{taxaccounts} .= "$ref->{accno} ";
780 $form->{"IC_tax_$ref->{accno}_description"} =
781 "$ref->{accno}--$ref->{description}";
784 if ($form->{amount}{ $ref->{accno} }) {
785 $form->{"IC_tax_$ref->{accno}"} = "checked";
788 $form->{"IC_tax_$ref->{accno}"} = "checked";
793 $form->{"select$key"} .=
794 "<option $ref->{selected}>$ref->{accno}--$ref->{description}\n";
795 if ($form->{amount}{$key} eq $ref->{accno}) {
796 $form->{$key} = "$ref->{accno}--$ref->{description}";
802 chop $form->{taxaccounts};
804 if (($form->{item} eq "part") || ($form->{item} eq "assembly")) {
805 $form->{selectIC_income} = $form->{selectIC_sale};
806 $form->{selectIC_expense} = $form->{selectIC_cogs};
807 $form->{IC_income} = $form->{IC_sale};
808 $form->{IC_expense} = $form->{IC_cogs};
811 delete $form->{IC_links};
812 delete $form->{amount};
814 $form->get_partsgroup(\%myconfig, { all => 1 });
816 $form->{partsgroup} = "$form->{partsgroup}--$form->{partsgroup_id}";
818 if (@{ $form->{all_partsgroup} }) {
819 $form->{selectpartsgroup} = qq|<option>\n|;
820 map { $form->{selectpartsgroup} .= qq|<option value="$_->{partsgroup}--$_->{id}">$_->{partsgroup}\n| } @{ $form->{all_partsgroup} };
823 if ($form->{item} eq 'assembly') {
825 foreach my $i (1 .. $form->{assembly_rows}) {
826 if ($form->{"partsgroup_id_$i"}) {
827 $form->{"partsgroup_$i"} =
828 qq|$form->{"partsgroup_$i"}--$form->{"partsgroup_id_$i"}|;
831 $form->get_partsgroup(\%myconfig);
833 if (@{ $form->{all_partsgroup} }) {
834 $form->{selectassemblypartsgroup} = qq|<option>\n|;
837 $form->{selectassemblypartsgroup} .=
838 qq|<option value="$_->{partsgroup}--$_->{id}">$_->{partsgroup}\n|
839 } @{ $form->{all_partsgroup} };
842 $lxdebug->leave_sub();
846 $lxdebug->enter_sub();
848 $auth->assert('part_service_assembly_details');
850 $form->{pg_keys} = sub { "$_[0]->{partsgroup}--$_[0]->{id}" };
851 $form->{description_area} = ($form->{rows} = $form->numtextrows($form->{description}, 40)) > 1;
852 $form->{notes_rows} = max 4, $form->numtextrows($form->{notes}, 40), $form->numtextrows($form->{formel}, 40);
854 map { $form->{"is_$_"} = ($form->{item} eq $_) } qw(part service assembly);
855 map { $form->{$_} =~ s/"/"/g; } qw(unit);
857 $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS',
858 'partsgroup' => 'all_partsgroup',
859 'vendors' => 'ALL_VENDORS',
860 'warehouses' => { 'key' => 'WAREHOUSES',
861 'bins' => 'BINS', });
862 # leerer wert für Lager und Lagerplatz korrekt einstellt
863 # ID 0 sollte in Ordnung sein, da der Zähler sowieso höher ist
864 my $no_default_bin_entry = { 'id' => '0', description => '--', 'BINS' => [ { id => '0', description => ''} ] };
865 push @ { $form->{WAREHOUSES} }, $no_default_bin_entry;
866 if (my $max = scalar @{ $form->{WAREHOUSES} }) {
867 my ($default_warehouse_id, $default_bin_id);
868 if ($form->{action} eq 'add') { # default only for new entries
869 $default_warehouse_id = $::instance_conf->get_warehouse_id;
870 $default_bin_id = $::instance_conf->get_bin_id;
872 $form->{warehouse_id} ||= $default_warehouse_id || $form->{WAREHOUSES}->[$max -1]->{id};
873 $form->{bin_id} ||= $default_bin_id || $form->{WAREHOUSES}->[$max -1]->{BINS}->[0]->{id};
876 $form->{LANGUAGES} = SL::DB::Manager::Language->get_all_sorted;
877 $form->{translations_map} = { map { ($_->{language_id} => $_) } @{ $form->{translations} || [] } };
879 IC->retrieve_buchungsgruppen(\%myconfig, $form);
880 @{ $form->{BUCHUNGSGRUPPEN} } = grep { $_->{id} eq $form->{buchungsgruppen_id} || ($form->{id} && $form->{orphaned}) || !$form->{id} } @{ $form->{BUCHUNGSGRUPPEN} };
882 if (($form->{partnumber} ne '') && !SL::TransNumber->new(number => $form->{partnumber}, type => $form->{item}, id => $form->{id})->is_unique) {
883 flash('info', $::locale->text('This partnumber is not unique. You should change it.'));
886 my $units = AM->retrieve_units(\%myconfig, $form);
887 $form->{ALL_UNITS} = [ map +{ name => $_ }, sort { $units->{$a}{sortkey} <=> $units->{$b}{sortkey} } keys %$units ];
889 $form->{defaults} = AM->get_defaults();
891 $form->{CUSTOM_VARIABLES} = CVar->get_custom_variables('module' => 'IC', 'trans_id' => $form->{id});
893 my ($null, $partsgroup_id) = split /--/, $form->{partsgroup};
895 CVar->render_inputs('variables' => $form->{CUSTOM_VARIABLES}, show_disabled_message => 1, partsgroup_id => $partsgroup_id)
896 if (scalar @{ $form->{CUSTOM_VARIABLES} });
898 $::request->layout->use_javascript("${_}.js") for qw(ckeditor/ckeditor ckeditor/adapters/jquery kivi.PriceRule);
899 $::request->layout->add_javascripts_inline("\$(function(){kivi.PriceRule.load_price_rules_for_part(@{[ $::form->{id} * 1 ]})});") if $::form->{id};
901 #print $form->parse_html_template('ic/form_header', { ALL_PRICE_FACTORS => $form->{ALL_PRICE_FACTORS},
902 # ALL_UNITS => $form->{ALL_UNITS},
903 # BUCHUNGSGRUPPEN => $form->{BUCHUNGSGRUPPEN},
904 # payment_terms => $form->{payment_terms},
905 # all_partsgroup => $form->{all_partsgroup}});
907 $form->{show_edit_buttons} = $main::auth->check_right($::myconfig{login}, 'part_service_assembly_edit');
909 print $form->parse_html_template('ic/form_header');
910 $lxdebug->leave_sub();
914 $lxdebug->enter_sub();
916 $auth->assert('part_service_assembly_details');
918 print $form->parse_html_template('ic/form_footer');
920 $lxdebug->leave_sub();
924 $lxdebug->enter_sub();
927 my @mm_data = grep { any { $_ ne '' } @$_{qw(make model)} } map +{ make => $form->{"make_$_"}, model => $form->{"model_$_"}, lastcost => $form->{"lastcost_$_"}, lastupdate => $form->{"lastupdate_$_"}, sortorder => $form->{"sortorder_$_"} }, 1 .. $numrows;
928 delete @{$form}{grep { m/^make_\d+/ || m/^model_\d+/ } keys %{ $form }};
929 print $form->parse_html_template('ic/makemodel', { MM_DATA => [ @mm_data, {} ], mm_rows => scalar @mm_data + 1 });
931 $lxdebug->leave_sub();
935 $lxdebug->enter_sub();
938 my ($nochange, $callback, $previousform, $linetotal, $line_purchase_price, $href);
940 @column_index = qw(runningnumber qty unit bom partnumber description partsgroup lastcost total);
942 if ($form->{previousform}) {
944 @column_index = qw(qty unit bom partnumber description partsgroup total);
948 $form->{old_callback} = $form->{callback};
949 $callback = $form->{callback};
950 $form->{callback} = "$form->{script}?action=display_form";
953 map { delete $form->{$_} } qw(action header);
955 # save form variables in a previousform variable
956 my %form_to_save = map { ($_ => m/^ (?: listprice | sellprice | lastcost ) $/x ? $form->format_amount(\%myconfig, $form->{$_}) : $form->{$_}) }
958 $previousform = $::auth->save_form_in_session(form => \%form_to_save);
960 $form->{callback} = $callback;
961 $form->{assemblytotal} = 0;
962 $form->{assembly_purchase_price_total} = 0;
967 runningnumber => { text => $locale->text('No.'), nowrap => 1, width => '5%', align => 'left',},
968 qty => { text => $locale->text('Qty'), nowrap => 1, width => '10%', align => 'left',},
969 unit => { text => $locale->text('Unit'), nowrap => 1, width => '5%', align => 'left',},
970 partnumber => { text => $locale->text('Part Number'), nowrap => 1, width => '20%', align => 'left',},
971 description => { text => $locale->text('Part Description'), nowrap => 1, width => '50%', align => 'left',},
972 lastcost => { text => $locale->text('Purchase Prices'), nowrap => 1, width => '50%', align => 'right',},
973 total => { text => $locale->text('Sale Prices'), nowrap => 1, align => 'right',},
974 bom => { text => $locale->text('BOM'), align => 'center',},
975 partsgroup => { text => $locale->text('Group'), align => 'left',},
980 for my $i (1 .. $numrows) {
981 my (%row, @row_hiddens);
983 $form->{"partnumber_$i"} =~ s/\"/"/g;
985 $linetotal = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / ($form->{"price_factor_$i"} || 1), 4);
986 $line_purchase_price = $form->round_amount($form->{"lastcost_$i"} * $form->{"qty_$i"} / ($form->{"price_factor_$i"} || 1), 4);
987 $form->{assemblytotal} += $linetotal;
988 $form->{assembly_purchase_price_total} += $line_purchase_price;
989 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"});
990 $linetotal = $form->format_amount(\%myconfig, $linetotal, 2);
991 $line_purchase_price = $form->format_amount(\%myconfig, $line_purchase_price, 2);
992 $href = build_std_url("action=edit", qq|id=$form->{"id_$i"}|, "rowcount=$numrows", "currow=$i", "previousform=$previousform");
993 map { $row{$_}{data} = "" } qw(qty unit partnumber description bom partsgroup runningnumber);
996 if (($i >= 1) && ($i == $numrows)) {
997 if (!$form->{previousform}) {
998 $row{partnumber}{data} = qq|<input name="partnumber_$i" size=15 value="$form->{"partnumber_$i"}">|;
999 $row{qty}{data} = qq|<input name="qty_$i" size=5 value="$form->{"qty_$i"}">|;
1000 $row{description}{data} = qq|<input name="description_$i" size=40 value="$form->{"description_$i"}">|;
1001 $row{partsgroup}{data} = qq|<input name="partsgroup_$i" size=10 value="$form->{"partsgroup_$i"}">|;
1005 if ($form->{previousform}) {
1006 push @row_hiddens, qw(qty bom);
1007 $row{partnumber}{data} = $form->{"partnumber_$i"};
1008 $row{qty}{data} = $form->{"qty_$i"};
1009 $row{bom}{data} = $form->{"bom_$i"} ? "x" : " ";
1010 $row{qty}{align} = 'right';
1012 $row{partnumber}{data} = qq|$form->{"partnumber_$i"}|;
1013 $row{partnumber}{link} = $href;
1014 $row{qty}{data} = qq|<input name="qty_$i" size=5 value="$form->{"qty_$i"}">|;
1015 $row{runningnumber}{data} = qq|<input name="runningnumber_$i" size=3 value="$i">|;
1016 $row{bom}{data} = sprintf qq|<input name="bom_$i" type=checkbox class=checkbox value=1 %s>|,
1017 $form->{"bom_$i"} ? 'checked' : '';
1019 push @row_hiddens, qw(unit description partnumber partsgroup);
1020 $row{unit}{data} = $form->{"unit_$i"};
1021 #Bei der Artikelbeschreibung und Warengruppe können Sonderzeichen verwendet
1022 #werden, die den HTML Code stören. Daher sollen diese im Template escaped werden
1023 #dies geschieht, wenn die Variable escape gesetzt ist
1024 $row{description}{data} = $form->{"description_$i"};
1025 $row{description}{escape} = 1;
1026 $row{partsgroup}{data} = $form->{"partsgroup_$i"};
1027 $row{partsgroup}{escape} = 1;
1028 $row{bom}{align} = 'center';
1031 $row{lastcost}{data} = $line_purchase_price;
1032 $row{total}{data} = $linetotal;
1033 $row{lastcost}{align} = 'right';
1034 $row{total}{align} = 'right';
1035 $row{deliverydate}{align} = 'right';
1037 push @row_hiddens, qw(id sellprice lastcost weight price_factor_id price_factor);
1038 $row{hiddens} = [ map +{ name => "${_}_$i", value => $form->{"${_}_$i"} }, @row_hiddens ];
1043 print $form->parse_html_template('ic/assembly_row', { COLUMNS => \@column_index, ROWS => \@ROWS, HEADER => \%header });
1045 $lxdebug->leave_sub();
1049 $lxdebug->enter_sub();
1051 $auth->assert('part_service_assembly_edit');
1053 # update checks whether pricegroups, makemodels or assembly items have been changed/added
1054 # new items might have been added (and the original form might have been stored and restored)
1055 # so at the end the ic form is run through check_form in io.pl
1056 # The various combination of events can lead to problems with the order of parse_amount and format_amount
1057 # Currently check_form parses some variables in assembly mode, but not in article or service mode
1058 # This will only ever really be sanely resolved with a rewrite...
1060 # parse pricegroups. and no, don't rely on check_form for this...
1061 map { $form->{"price_$_"} = $form->parse_amount(\%myconfig, $form->{"price_$_"}) } 1 .. $form->{price_rows};
1063 unless ($form->{item} eq 'assembly') {
1064 # for assemblies check_form will parse sellprice and listprice, but not for parts or services
1065 $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) for qw(sellprice listprice ve gv);
1068 if ($form->{item} eq 'part') {
1069 $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) for qw(weight rop);
1072 # same for makemodel lastcosts
1073 # but parse_amount not necessary for assembly component lastcosts
1074 unless ($form->{item} eq "assembly") {
1075 map { $form->{"lastcost_$_"} = $form->parse_amount(\%myconfig, $form->{"lastcost_$_"}) } 1 .. $form->{"makemodel_rows"};
1076 $form->{lastcost} = $form->parse_amount(\%myconfig, $form->{lastcost});
1079 if ($form->{item} eq "assembly") {
1080 my $i = $form->{assembly_rows};
1082 # if last row is empty check the form otherwise retrieve item
1083 if ( ($form->{"partnumber_$i"} eq "")
1084 && ($form->{"description_$i"} eq "")
1085 && ($form->{"partsgroup_$i"} eq "")) {
1086 # no new assembly item was added
1091 # search db for newly added assemblyitems, via partnumber or description
1092 IC->assembly_item(\%myconfig, \%$form);
1094 # form->{item_list} contains the possible matches, next check whether the
1095 # match is unique or we need to call the page to select the item
1096 my $rows = scalar @{ $form->{item_list} };
1099 $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"});
1102 $form->{makemodel_rows}--;
1103 select_item(mode => 'IC', pre_entered_qty => $form->parse_amount(\%myconfig, $form->{"qty_$i"}));
1104 $::dispatcher->end_request;
1106 map { $form->{item_list}[$i]{$_} =~ s/\"/"/g }
1107 qw(partnumber description unit partsgroup);
1108 map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} }
1109 keys %{ $form->{item_list}[0] };
1110 $form->{"runningnumber_$i"} = $form->{assembly_rows};
1111 $form->{assembly_rows}++;
1119 $form->{rowcount} = $i;
1120 $form->{assembly_rows}++;
1127 } elsif (($form->{item} eq 'part') || ($form->{item} eq 'service')) {
1131 $lxdebug->leave_sub();
1135 $lxdebug->enter_sub();
1137 $auth->assert('part_service_assembly_edit');
1138 $::form->mtime_ischanged('parts');
1139 my ($parts_id, %newform, $amount, $callback);
1141 # check if there is a part number - commented out, cause there is an automatic allocation of numbers
1142 # $form->isblank("partnumber", $locale->text(ucfirst $form->{item}." Part Number missing!"));
1144 # check if there is a description
1145 $form->isblank("description", $locale->text("Part Description missing!"));
1147 $form->error($locale->text("Inventory quantity must be zero before you can set this $form->{item} obsolete!"))
1148 if $form->{obsolete} && $form->{onhand} * 1 && $form->{item} ne 'service';
1150 if (!$form->{buchungsgruppen_id}) {
1151 $form->error($locale->text("Parts must have an entry type.") . " " .
1152 $locale->text("If you see this message, you most likely just setup your LX-Office and haven't added any entry types. If this is the case, the option is accessible for administrators in the System menu.")
1156 $form->error($locale->text('Description must not be empty!')) unless $form->{description};
1157 $form->error($locale->text('Partnumber must not be set to empty!')) if $form->{id} && !$form->{partnumber};
1159 # undef warehouse_id if the empty value is selected
1160 if ( ($form->{warehouse_id} == 0) && ($form->{bin_id} == 0) ) {
1161 undef $form->{warehouse_id};
1162 undef $form->{bin_id};
1165 if (IC->save(\%myconfig, \%$form) == 3) {
1166 $form->error($locale->text('Partnumber not unique!'));
1168 # saving the history
1169 if(!exists $form->{addition}) {
1170 $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
1171 $form->{what_done} = "part";
1172 $form->{addition} = "SAVED";
1173 $form->save_history;
1175 # /saving the history
1176 $parts_id = $form->{id};
1179 # load previous variables
1180 if ($form->{previousform}) {
1182 # save the new form variables before splitting previousform
1183 map { $newform{$_} = $form->{$_} } keys %$form;
1185 # don't trample on previous variables
1186 map { delete $form->{$_} } keys %newform;
1188 my $ic_cvar_configs = CVar->get_configs(module => 'IC');
1189 my @ic_cvar_fields = map { "cvar_$_->{name}" } @{ $ic_cvar_configs };
1191 # restore original values
1192 $::auth->restore_form_from_session($newform{previousform}, form => $form);
1193 $form->{taxaccounts} = $newform{taxaccount2};
1195 if ($form->{item} eq 'assembly') {
1197 # undo number formatting
1198 map { $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) }
1199 qw(weight listprice sellprice rop);
1201 $form->{assembly_rows}--;
1202 if ($newform{currow}) {
1203 $i = $newform{currow};
1205 $i = $form->{assembly_rows};
1207 $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"} > 0);
1209 $form->{sellprice} -= $form->{"sellprice_$i"} * $form->{"qty_$i"};
1210 $form->{weight} -= $form->{"weight_$i"} * $form->{"qty_$i"};
1212 # change/add values for assembly item
1213 map { $form->{"${_}_$i"} = $newform{$_} } qw(partnumber description bin unit weight listprice sellprice inventory_accno income_accno expense_accno price_factor_id);
1214 map { $form->{"ic_${_}_$i"} = $newform{$_} } @ic_cvar_fields;
1216 # das ist __voll__ bekloppt, dass so auszurechnen jb 22.5.09
1217 #$form->{sellprice} += $form->{"sellprice_$i"} * $form->{"qty_$i"};
1218 $form->{weight} += $form->{"weight_$i"} * $form->{"qty_$i"};
1222 # set values for last invoice/order item
1223 $i = $form->{rowcount};
1224 $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"} > 0);
1226 map { $form->{"${_}_$i"} = $newform{$_} } qw(partnumber description bin unit listprice inventory_accno income_accno expense_accno sellprice lastcost price_factor_id);
1227 map { $form->{"ic_${_}_$i"} = $newform{$_} } @ic_cvar_fields;
1229 $form->{"longdescription_$i"} = $newform{notes};
1231 $form->{"sellprice_$i"} = $newform{lastcost} if ($form->{vendor_id});
1233 if ($form->{exchangerate} != 0) {
1234 $form->{"sellprice_$i"} /= $form->{exchangerate};
1237 map { $form->{"taxaccounts_$i"} .= "$_ " } split / /, $newform{taxaccount};
1238 chop $form->{"taxaccounts_$i"};
1239 foreach my $item (qw(description rate taxnumber)) {
1240 my $index = $form->{"taxaccounts_$i"} . "_$item";
1241 $form->{$index} = $newform{$index};
1244 # credit remaining calculation
1245 $amount = $form->{"sellprice_$i"} * (1 - $form->{"discount_$i"} / 100) * $form->{"qty_$i"};
1247 map { $form->{"${_}_base"} += $amount } (split / /, $form->{"taxaccounts_$i"});
1248 map { $amount += ($form->{"${_}_base"} * $form->{"${_}_rate"}) } split / /, $form->{"taxaccounts_$i"} if !$form->{taxincluded};
1250 $form->{creditremaining} -= $amount;
1252 # redo number formatting, because invoice parse them!
1253 map { $form->{"${_}_$i"} = $form->format_amount(\%myconfig, $form->{"${_}_$i"}) } qw(weight listprice sellprice lastcost rop);
1256 $form->{"id_$i"} = $parts_id;
1258 # Get the actual price factor (not just the ID) for the marge calculation.
1259 $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
1260 foreach my $pfac (@{ $form->{ALL_PRICE_FACTORS} }) {
1261 next if ($pfac->{id} != $newform{price_factor_id});
1262 $form->{"marge_price_factor_$i"} = $pfac->{factor};
1265 delete $form->{ALL_PRICE_FACTORS};
1267 delete $form->{action};
1269 # restore original callback
1270 $callback = $form->unescape($form->{callback});
1271 $form->{callback} = $form->unescape($form->{old_callback});
1272 delete $form->{old_callback};
1274 $form->{makemodel_rows}--;
1276 # put callback together
1277 foreach my $key (keys %$form) {
1279 # do single escape for Apache 2.0
1280 my $value = $form->escape($form->{$key}, 1);
1281 $callback .= qq|&$key=$value|;
1283 $form->{callback} = $callback;
1289 $lxdebug->leave_sub();
1293 $lxdebug->enter_sub();
1295 $auth->assert('part_service_assembly_edit');
1297 # saving the history
1298 if(!exists $form->{addition}) {
1299 $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
1300 $form->{addition} = "SAVED AS NEW";
1301 $form->{what_done} = "part";
1302 $form->save_history;
1304 # /saving the history
1306 # deleting addition to get the history saved for the new part, too.
1307 delete $form->{addition};
1310 if ($form->{"original_partnumber"} &&
1311 ($form->{"partnumber"} eq $form->{"original_partnumber"})) {
1312 $form->{partnumber} = "";
1315 $lxdebug->leave_sub();
1319 $lxdebug->enter_sub();
1321 $auth->assert('part_service_assembly_edit');
1323 # saving the history
1324 if(!exists $form->{addition}) {
1325 $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
1326 $form->{addition} = "DELETED";
1327 $form->{what_done} = "part";
1328 $form->save_history;
1330 # /saving the history
1331 my $rc = IC->delete(\%myconfig, \%$form);
1334 $form->redirect($locale->text('Item deleted!')) if ($rc > 0);
1335 $form->error($locale->text('Cannot delete item!'));
1337 $lxdebug->leave_sub();
1341 $lxdebug->enter_sub();
1343 $auth->assert('part_service_assembly_details');
1348 pricegroup => $form->{"pricegroup_$_"},
1349 pricegroup_id => $form->{"pricegroup_id_$_"},
1350 price => $form->{"price_$_"},
1353 print $form->parse_html_template('ic/price_row', { PRICES => \@PRICES });
1355 $lxdebug->leave_sub();
1358 sub ajax_autocomplete {
1359 $main::lxdebug->enter_sub();
1361 my $form = $main::form;
1362 my %myconfig = %main::myconfig;
1364 $form->{column} = 'description' unless $form->{column} =~ /^partnumber|description$/;
1365 $form->{$form->{column}} = $form->{q} || '';
1366 $form->{limit} = ($form->{limit} * 1) || 10;
1367 $form->{searchitems} ||= '';
1369 my @results = IC->all_parts(\%myconfig, $form);
1371 print $form->ajax_response_header(),
1372 $form->parse_html_template('ic/ajax_autocomplete');
1374 $main::lxdebug->leave_sub();
1378 $::lxdebug->enter_sub;
1380 $auth->assert('part_service_assembly_edit');
1384 $::form->language_payment(\%::myconfig);
1386 Common::webdav_folder($::form);
1389 price_row($::form->{price_rows});
1390 makemodel_row(++$::form->{makemodel_rows}) if $::form->{item} =~ /^(part|service)$/;
1391 assembly_row(++$::form->{assembly_rows}) if $::form->{item} eq 'assembly';
1395 $::lxdebug->leave_sub;
1398 sub back_to_record {
1402 delete @{$::form}{qw(action action_add action_back_to_record back_sub description item notes partnumber sellprice taxaccount2 unit vc)};
1404 $::auth->restore_form_from_session($::form->{previousform}, clobber => 1);
1405 $::form->{rowcount}--;
1406 $::form->{action} = 'display_form';
1407 $::form->{callback} = $::form->{script} . '?' . join('&', map { $::form->escape($_) . '=' . $::form->escape($::form->{$_}) } sort keys %{ $::form });
1411 sub continue { call_sub($form->{"nextsub"}); }
1414 my $action = first { $::form->{"action_${_}"} } qw(add back_to_record);
1415 $::form->error($::locale->text('No action defined.')) unless $action;
1417 $::form->{dispatched_action} = $action;