diff --git a/SL/Controller/Inventory.pm b/SL/Controller/Inventory.pm index 6940b2eb5b..d646472717 100644 --- a/SL/Controller/Inventory.pm +++ b/SL/Controller/Inventory.pm @@ -43,7 +43,7 @@ __PACKAGE__->run_before('set_layout'); sub action_stock_in { my ($self) = @_; - $::form->{title} = t8('Stock'); + $::form->{title} = t8('Store'); # Sometimes we want to open stock_in with a part already selected, but only # the parts_id is passed in the url (and not also warehouse, bin and unit). @@ -972,7 +972,7 @@ sub setup_stock_in_action_bar { for my $bar ($::request->layout->get('actionbar')) { $bar->add( action => [ - t8('Stock'), + t8('Store'), submit => [ '#form', { action => 'Inventory/stock' } ], checks => [ 'check_part_selection_before_stocking' ], accesskey => 'enter', diff --git a/SL/Controller/Part.pm b/SL/Controller/Part.pm index 8965377e7e..aeb933054c 100644 --- a/SL/Controller/Part.pm +++ b/SL/Controller/Part.pm @@ -22,17 +22,22 @@ use SL::DB::History; use SL::DB::Part; use SL::DB::PartsGroup; use SL::DB::PriceRuleItem; +use SL::DB::PartLabelPrint; use SL::DB::PurchaseBasketItem; use SL::DB::Shop; use SL::Helper::Flash; +use SL::Helper::CreatePDF qw(:all); use SL::Helper::PrintOptions; use SL::Helper::UserPreferences::PartPickerSearch; use SL::JSON; +use SL::MIME; use SL::Locale::String qw(t8); use SL::MoreCommon qw(save_form); use SL::Presenter::EscapedText qw(escape is_escaped); use SL::Presenter::Part; -use SL::Presenter::Tag qw(select_tag); +use SL::Presenter::Tag qw(select_tag checkbox_tag); +use SL::ReportGenerator; +use SL::Controller::Helper::ReportGenerator; use Rose::Object::MakeMethods::Generic ( 'scalar --get_set_init' => [ qw(parts models part p warehouses multi_items_models @@ -40,6 +45,7 @@ use Rose::Object::MakeMethods::Generic ( customerprices orphaned assortment assortment_items assembly assembly_items + print_options part_labels_for_stock_print_options all_pricegroups all_translations all_partsgroups all_units all_buchungsgruppen all_payment_terms all_warehouses parts_classification_filter @@ -113,76 +119,154 @@ sub action_add { sub action_save { my ($self, %params) = @_; - # checks that depend only on submitted $::form - $self->check_form or return $self->js->render; + my $is_new = !$self->part->id; - my $is_new = !$self->part->id; # $ part gets loaded here + $self->save_with_render_error() or return; - # check that the part hasn't been modified - unless ( $is_new ) { - $self->check_part_not_modified or - return $self->js->error(t8('The document has been changed by another user. Please reopen it in another window and copy the changes to the new window'))->render; - } + flash_later('info', $is_new ? t8('The item has been created.') . " " . $self->part->displayable_name : t8('The item has been saved.')); - if ( $is_new - && $::form->{part}{partnumber} - && SL::DB::Manager::Part->find_by(partnumber => $::form->{part}{partnumber}) - ) { - return $self->js->error(t8('The partnumber is already being used'))->render; + if ( $::form->{callback} ) { + $self->redirect_to($::form->unescape($::form->{callback}) . '&new_parts_id=' . $self->part->id); + + } else { + # default behaviour after save: reload item, this also resets last_modification! + $self->redirect_to(controller => 'Part', action => 'edit', 'part.id' => $self->part->id); } +} - $self->parse_form; +sub action_save_and_print { + my ($self) = @_; - my @errors = $self->part->validate; - return $self->js->error(@errors)->render if @errors; + $self->save_with_render_error() or return; + $self->js_reset_part_after_save(); + $self->js->flash('info', t8('The item has been saved.')); - if ($is_new) { - # Ensure CVars that should be enabled by default actually are when - # creating new parts. - my @default_valid_configs = - grep { ! $_->{flag_defaults_to_invalid} } - grep { $_->{module} eq 'IC' } - @{ CVar->get_configs() }; + $self->print_part($self->part); - $::form->{"cvar_" . $_->{name} . "_valid"} = 1 for @default_valid_configs; - } else { - $self->{lastcost_modified} = $self->check_lastcost_modified; + $self->js->render(); +} + +sub action_print_multi { + my ($self) = @_; + + my $parts = SL::DB::Manager::Part->get_all(where => [id => $::form->{ids}]); + foreach my $part(@{$parts}) { + $self->print_part($part); } - # $self->part has been loaded, parsed and validated without errors and is ready to be saved - $self->part->db->with_transaction(sub { + $self->js->render(); +} - $self->part->save(cascade => 1); - $self->part->set_lastcost_assemblies_and_assortiments if $self->{lastcost_modified}; +sub print_part { + my ($self, $part) = @_; - SL::DB::History->new( - trans_id => $self->part->id, - snumbers => 'partnumber_' . $self->part->partnumber, - employee_id => SL::DB::Manager::Employee->current->id, - what_done => 'part', - addition => 'SAVED', - )->save(); + my $formname = $::form->{print_options}->{formname}; + my $format = $::form->{print_options}->{format}; + my $media = $::form->{print_options}->{media}; + my $printer_id = $::form->{print_options}->{printer_id}; + my $language; + if ($::form->{print_options}->{language_id}) { + $language = SL::DB::Manager::Language->find_by( + id => $::form->{print_options}->{language_id} + ); + } + my $copies; + if ($::form->{print_options}->{part_labels_for_stock}) { + $copies = $part->stockqty; + } else { + $copies = $::form->{print_options}->{copies}; + } - CVar->save_custom_variables( - dbh => $self->part->db->dbh, - module => 'IC', - trans_id => $self->part->id, - variables => $::form, # $::form->{cvar} would be nicer - save_validity => 1, + eval { + + my $template_ext; + my $template_type; + if ($format =~ /(opendocument|oasis)/i) { + $template_ext = 'odt'; + $template_type = 'OpenDocument'; + } elsif ($format =~ m{html}i) { + $template_ext = 'html'; + $template_type = 'HTML'; + } + + # search for the template + my ($template_file, @template_files) = SL::Helper::CreatePDF->find_template( + name => $formname, + extension => $template_ext, + language => $language, + printer_id => $printer_id, ); + if (!defined $template_file) { + die t8('Cannot find matching template for this print request. Please contact your template maintainer. I tried these: #1.', join ', ', map { "'$_'"} @template_files); + } - 1; - }) or return $self->js->error(t8('The item couldn\'t be saved!') . " " . $self->part->db->error )->render; + my $print_form = Form->new(''); + $print_form->{part} = $part; - flash_later('info', $is_new ? t8('The item has been created.') . " " . $self->part->displayable_name : t8('The item has been saved.')); + # for doc_filename + $print_form->{type} = 'part'; + $print_form->{formname} = $formname; + $print_form->{format} = $format; + $print_form->{partnumber} = $part->partnumber; + $print_form->{language} = $language && ("_" . + ($language->template_code || $language->description) + ); - if ( $::form->{callback} ) { - $self->redirect_to($::form->unescape($::form->{callback}) . '&new_parts_id=' . $self->part->id); + # for tempalte variables + $print_form->{template_meta}->{language} = $language; + $print_form->{media} = $media; + $print_form->{media} = 'file' if $print_form->{media} eq 'screen'; + my $default = SL::DB::Default->get; + $print_form->{employee_company} = $default->company; + $print_form->{currency} = $default->currency->name; + + my $document = SL::Helper::CreatePDF->create_pdf( + format => $format, + template_type => $template_type, + template => $template_file, + variables => $print_form, + variable_content_types => { + notes => 'html', + # TODO: html cvars + }, + ); - } else { - # default behaviour after save: reload item, this also resets last_modification! - $self->redirect_to(controller => 'Part', action => 'edit', 'part.id' => $self->part->id); - } + if ($media eq 'screen') { + my $doc_filename = $print_form->generate_attachment_filename(); + + $self->send_file( + \$document, + type => SL::MIME->mime_type_from_ext($doc_filename), + name => $doc_filename, + js_no_render => 1, + ); + } elsif ($media eq 'printer') { + my $printer = SL::DB::Printer->new(id => $printer_id)->load; + $printer->print_document( + copies => $copies, + content => $document, + ); + + my %part_label_print_values = ( + price_history_id => $part->last_price_update->id, + print_type => $::form->{print_options}->{part_labels_for_stock} ? + 'stock' : 'single', + template => $formname, + ); + SL::DB::Manager::PartLabelPrint->find_by(%part_label_print_values) + or SL::DB::PartLabelPrint->new(%part_label_print_values)->save(); + + $self->js->flash('info', t8('The document has been sent to the printer \'#1\'.', $printer->printer_description)); + } else { + die t8('Media \'#1\' is not supported yet/anymore.', $media); + } + + 1; + } or do { + $self->js + ->flash('error', t8("Creating the PDF failed!")) + ->flash('error', $@); + }; } sub action_save_and_purchase_order { @@ -852,6 +936,137 @@ sub action_showdetails { $self->render(\$output, { layout => 0, process => 0 }); } +sub action_search_print_part_labels_of_changed_prices { + my ($self) = @_; + $self->_setup_search_print_part_labels_of_changed_prices_action_bar(); + + $::form->{filter} ||= {}; + + $::form->{filter}->{price_change_printed} ||= { + template => 'part_label', + print_type => 'stock', + printed => 0 + }; + + my $price_change_printed =$::form->{filter}->{price_change_printed}; + $::form->{filter}->{price_change_printed} = \$price_change_printed; + + my $report = SL::ReportGenerator->new(\%::myconfig, $::form); + + $self->models->finalize; # for filter laundering + + $::form->{filter}->{price_change_printed} = ${$::form->{filter}->{price_change_printed}}; + + my $callback = $self->models->get_callback; + + $self->{report} = $report; + + my @columns_order = qw( + id_check_box + partnumber + ean + description + notes + partsgroup + ); + + my @default_columns = qw( + id_check_box + partnumber + ean + description + partsgroup + ); + + my %column_defs = ( + id_check_box => { + raw_data => sub { + checkbox_tag("ids[]", value => $_[0]->id, "data-checkall" => 1, checked => 1); + }, + raw_header_data => checkbox_tag("", id => "check_all", checkall => "[data-checkall=2]", checked => 1), + text => ' ', + }, + partnumber => { + obj_link => sub {$self->url_for(action => 'edit', 'part.id' => $_[0]->id, callback => $callback)}, + sub => sub { $_[0]->partnumber }, + }, + ean => { + obj_link => sub {$self->url_for(action => 'edit', 'part.id' => $_[0]->id, callback => $callback)}, + sub => sub { $_[0]->ean }, + }, + description => { + sub => sub {$_[0]->description }, + }, + notes => { + sub => sub {$_[0]->notes }, + }, + partsgroup => { + sub => sub {t8($_[0]->partsgroup ? $_[0]->partsgroup->partsgroup : '') }, + }, + ); + + $column_defs{$_}->{text} ||= t8( $self->models->get_sort_spec->{$_}->{title} || $_ ) + for keys %column_defs; + + unless ($::form->{active_in_report}) { + $::form->{active_in_report}->{$_} = 1 foreach @default_columns; + } + + $self->models->add_additional_url_params( + active_in_report => $::form->{active_in_report}); + map { $column_defs{$_}->{visible} = $::form->{active_in_report}->{"$_"} || 0 } + grep {$_ ne 'id_check_box'} + keys %column_defs; + + # make all sortable + my @sortable = + grep {$_ ne 'id_check_box'} + keys %column_defs; + + use SL::Presenter::Filter::Part; + my $filter_html = SL::Presenter::Filter::Part::filter( + $::form->{filter}, + show_price_change_printed_filter => 1, + active_in_report => $::form->{active_in_report} + ); + + $report->set_options( + std_column_visibility => 1, + controller_class => 'Part', + output_format => 'HTML', + raw_top_info_text => $self->render( + 'part/_print_part_labels_of_changed_prices_report_top', + { output => 0 }, + FILTER_HTML => $filter_html, + ), + raw_bottom_info_text => $self->render( + 'part/_print_part_labels_of_changed_prices_report_bottom', + { output => 0 }, + models => $self->models + ), + title => t8('Parts with Changed Prices'), + allow_pdf_export => 1, + allow_csv_export => 1, + ); + + $report->set_columns(%column_defs); + $report->set_column_order(@columns_order); + $report->set_export_options( + 'search_print_part_labels_of_changed_prices', + qw(filter active_in_report) + ); + $report->set_options_from_form; + $self->models->set_report_generator_sort_options( + report => $report, + sortable_columns => \@sortable + ); + + $self->report_generator_list_objects( + report => $report, + objects => $self->models->get, + ); +} + sub action_print_label { my ($self) = @_; # TODO: implement @@ -1242,6 +1457,95 @@ sub build_bin_select { ); } +sub save { + my ($self) = @_; + my @errors = (); + + my $is_new = !$self->part->id; + + # check that the part hasn't been modified + unless ( $is_new ) { + $self->check_part_not_modified or + return [t8('The document has been changed by another user. Please reopen it in another window and copy the changes to the new window')]; + } + + $self->parse_form; + + @errors = $self->part->validate; + return \@errors if @errors; + + if ($is_new) { + # Ensure CVars that should be enabled by default actually are when + # creating new parts. + my @default_valid_configs = + grep { ! $_->{flag_defaults_to_invalid} } + grep { $_->{module} eq 'IC' } + @{ CVar->get_configs() }; + + $::form->{"cvar_" . $_->{name} . "_valid"} = 1 for @default_valid_configs; + } else { + $self->{lastcost_modified} = $self->check_lastcost_modified; + } + + # $self->part has been loaded, parsed and validated without errors and is ready to be saved + $self->part->db->with_transaction(sub { + + $self->part->save(cascade => 1); + $self->part->set_lastcost_assemblies_and_assortiments if $self->{lastcost_modified}; + + SL::DB::History->new( + trans_id => $self->part->id, + snumbers => 'partnumber_' . $self->part->partnumber, + employee_id => SL::DB::Manager::Employee->current->id, + what_done => 'part', + addition => 'SAVED', + )->save(); + + CVar->save_custom_variables( + dbh => $self->part->db->dbh, + module => 'IC', + trans_id => $self->part->id, + variables => $::form, # $::form->{cvar} would be nicer + save_validity => 1, + ); + + 1; + }) || push(@errors, $self->part->db->error); + + return \@errors; +} + +sub save_with_render_error { + my ($self) = @_; + + # checks that depend only on submitted $::form + if (!$self->check_form) { + $self->js->render(); + return 0; + } + + my $errors = $self->save(); + + if (scalar @{ $errors }) { + $self->js->error(t8('The item couldn\'t be saved!')); + $self->js->error(@{ $errors }); + $self->js->render(); + return 0; + } + + return 1; +} + +sub js_reset_part_after_save { + my ($self) = @_; + + $self->part->load(); + $self->js + ->val('#part_id', $self->part->id) + ->val('#part_partnumber', $self->part->partnumber) + ->val('#part_weight_as_number', $self->part->weight_as_number) + ->val('#last_modification', $self->part->last_modification . ""); +} # get_set_inits for partpicker @@ -1285,7 +1589,10 @@ sub init_models { dir => 1, }, partnumber => t8('Partnumber'), - description => t8('Description'), + description => t8('Description'), + ean => t8('EAN'), + notes => t8('Notes'), + partsgroup => t8('Partsgroup'), }, with_objects => [ qw(unit_obj classification) ], ); @@ -1504,6 +1811,45 @@ sub init_parts_classification_filter { die "no query rules for parts_classification_type " . $::form->{parts_classification_type}; } +sub init_print_options { + + my $print_form = Form->new(''); + $print_form->{type} = 'part'; + $print_form->{printers} = SL::DB::Manager::Printer->get_all_sorted; + $print_form->{languages} = SL::DB::Manager::Language->get_all_sorted; + + return SL::Helper::PrintOptions->get_print_options( + form => $print_form, + options => {dialog_name_prefix => 'print_options.', + show_headers => 1, + no_queue => 1, + no_postscript => 1, + no_opendocument => 1, + no_html => 1}, + ); +} + +sub init_part_labels_for_stock_print_options { + + my $print_form = Form->new(''); + $print_form->{type} = 'part'; + $print_form->{printers} = SL::DB::Manager::Printer->get_all_sorted; + $print_form->{languages} = SL::DB::Manager::Language->get_all_sorted; + + return SL::Helper::PrintOptions->get_print_options( + form => $print_form, + options => { + dialog_name_prefix => 'print_options.', + show_headers => 1, + no_queue => 1, + no_postscript => 1, + no_opendocument => 1, + no_html => 1, + no_display_copies => 1, + }, + ); +} + # simple checks to run on $::form before saving sub form_check_part_description_exists { @@ -1766,7 +2112,21 @@ sub _setup_form_action_bar { combobox => [ action => [ t8('Export'), - only_if => $self->part->is_assembly || $self->part->is_assortment, + ], + action => [ + t8('Save and print'), + call => [ 'kivi.Part.show_print_options' ], + disabled => !$may_edit ? t8('You do not have the permissions to access this function.') : undef, + checks => [ 'kivi.validate_form' ], + only_if => !$::form->{inline_create}, + ], + action => [ + t8('Save and Print Labels for Stock'), + call => [ 'kivi.Part.show_part_labels_for_stock_print_options' ], + disabled => !$self->part->id ? t8('The object has not been saved yet.') + : !$self->part->stockqty ? t8('The part has no stock.') + : !$may_edit ? t8('You do not have the permissions to access this function.') : undef, + checks => [ 'kivi.validate_form' ], ], action => [ $self->part->is_assembly ? t8('Assembly items') : t8('Assortment items'), @@ -1820,6 +2180,37 @@ sub _setup_form_action_bar { } } +sub _setup_search_print_part_labels_of_changed_prices_action_bar { + my ($self, %params) = @_; + + for my $bar ($::request->layout->get('actionbar')) { + $bar->add( + action => [ + t8('Update'), + submit => [ + '#filter_form', { + action => 'Part/search_print_part_labels_of_changed_prices', + } + ], + accesskey => 'enter', + ], + combobox => [ + action => [ + t8('Print'), + ], + action => [ + t8('Print one Label'), + call => [ 'kivi.Part.show_print_options' ], + ], + action => [ + t8('Print Labels for Stock'), + call => [ 'kivi.Part.show_part_labels_for_stock_print_options' ], + ], + ], + ); + } +} + 1; __END__ @@ -1934,6 +2325,11 @@ template. Saves the current part and then reloads the edit page for the part. +=item C + +Saves the current part, prints the selected template and then reloads the edit +page for the part. + =item C Takes the information from the current part, plus any modifications made on the @@ -2133,6 +2529,26 @@ Depending on the price_type the lastcost sum or sellprice sum is returned. Doesn't work for recursive items. +=item C + +Helper function for saving the part object in $self->part. Returns a error list +on failure. + +=item C + +Helper function for checking the form, saving the part object and error +rendering. Returns 1 on success and 0 when a error is rendered. + +It can be called like this: + + $self->save_with_render_error() or return; + +=item C + +Helper function for updating changed values after save in the form via js. It +chances 'part_id', 'part_partnumber', 'part_weight_as_number' and +'last_modification'. + =back =head1 GET SET INITS diff --git a/SL/Controller/Reclamation.pm b/SL/Controller/Reclamation.pm index f28aca5d3a..f922aab11d 100644 --- a/SL/Controller/Reclamation.pm +++ b/SL/Controller/Reclamation.pm @@ -6,7 +6,7 @@ use parent qw(SL::Controller::Base); use SL::Helper::Flash qw(flash_later); use SL::HTML::Util; use SL::Presenter::Tag qw(select_tag hidden_tag div_tag); -use SL::Presenter::ReclamationFilter qw(filter); +use SL::Presenter::Filter::Reclamation; use SL::Locale::String qw(t8); use SL::SessionFile::Random; use SL::PriceSource; @@ -216,10 +216,12 @@ sub action_save { sub action_list { my ($self) = @_; + $::form->{filter} ||= {}; + $self->_setup_search_action_bar; - $self->prepare_report; + my $report = $self->prepare_report; $self->report_generator_list_objects( - report => $self->{report}, + report => $report, objects => $self->models->get, options => { action_bar_additional_submit_values => { @@ -1186,7 +1188,7 @@ sub init_models { closed => t8('Closed'), }, query => [ - SL::DB::Manager::Reclamation->type_filter($self->type), + (record_type => $self->type), (salesman_id => SL::DB::Manager::Employee->current->id) x ($self->reclamation->is_sales && !$::auth->assert('sales_all_edit', 1)), (employee_id => SL::DB::Manager::Employee->current->id) x ($self->reclamation->is_sales && !$::auth->assert('sales_all_edit', 1)), (employee_id => SL::DB::Manager::Employee->current->id) x (!$self->reclamation->is_sales && !$::auth->assert('purchase_all_edit', 1)), @@ -1672,10 +1674,6 @@ sub prepare_report { my ($self) = @_; my $report = SL::ReportGenerator->new(\%::myconfig, $::form); - $report->{title} = t8('Sales Reclamations'); - if ($self->type eq PURCHASE_RECLAMATION_TYPE()){ - $report->{title} = t8('Purchase Reclamations'); - } $self->models->disable_plugin('paginated') if $report->{options}{output_format} =~ /^(pdf|csv)$/i; $self->models->add_additional_url_params(type => $self->type); @@ -1683,8 +1681,6 @@ sub prepare_report { my $callback = $self->models->get_callback; - $self->{report} = $report; - # TODO: shipto_id is not linked to custom_shipto my @columns_order = qw( id @@ -1892,10 +1888,13 @@ sub prepare_report { unless ($::form->{active_in_report}) { $::form->{active_in_report}->{$_} = 1 foreach @default_columns; } + $self->models->add_additional_url_params( - active_in_report => $::form->{active_in_report}); - map { $column_defs{$_}->{visible} = $::form->{active_in_report}->{"$_"} } - keys %column_defs; + active_in_report => $::form->{active_in_report} + ); + + $column_defs{$_}->{visible} = $::form->{active_in_report}->{"$_"} || 0 + foreach keys %column_defs; ## add cvars TODO: Add own cvars #my %cvar_column_defs = map { @@ -1915,7 +1914,7 @@ sub prepare_report { # make all sortable my @sortable = keys %column_defs; - my $filter_html = SL::Presenter::ReclamationFilter::filter( + my $filter_html = SL::Presenter::Filter::Reclamation::filter( $::form->{filter}, $self->type, active_in_report => $::form->{active_in_report} ); @@ -1940,9 +1939,11 @@ sub prepare_report { $report->set_columns(%column_defs); $report->set_column_order(@columns_order); #$report->set_export_options(qw(list filter), @cvar_column_form_names); TODO: for cvars - $report->set_export_options(qw(list filter active_in_report)); + $report->set_export_options('list', qw(filter active_in_report)); $report->set_options_from_form; $self->models->set_report_generator_sort_options(report => $report, sortable_columns => \@sortable); + + return $report; } sub _setup_edit_action_bar { @@ -2139,7 +2140,7 @@ sub _setup_search_action_bar { $bar->add( action => [ t8('Update'), - submit => [ '#search_form', { action => 'Reclamation/list', type => $self->type } ], + submit => [ '#filter_form', { action => 'Reclamation/list', type => $self->type } ], accesskey => 'enter', ], link => [ diff --git a/SL/DB/Helper/ALL.pm b/SL/DB/Helper/ALL.pm index 62a366254e..7b1f352c00 100644 --- a/SL/DB/Helper/ALL.pm +++ b/SL/DB/Helper/ALL.pm @@ -92,6 +92,7 @@ use SL::DB::OrderVersion; use SL::DB::Part; use SL::DB::PartClassification; use SL::DB::PartCustomerPrice; +use SL::DB::PartLabelPrint; use SL::DB::PartsGroup; use SL::DB::PartsPriceHistory; use SL::DB::PaymentTerm; diff --git a/SL/DB/Helper/Mappings.pm b/SL/DB/Helper/Mappings.pm index df7780a2cb..a60b927790 100644 --- a/SL/DB/Helper/Mappings.pm +++ b/SL/DB/Helper/Mappings.pm @@ -173,6 +173,7 @@ my %kivitendo_package_names = ( part_classifications => 'PartClassification', part_customer_prices => 'PartCustomerPrice', parts_price_history => 'PartsPriceHistory', + part_label_prints => 'PartLabelPrint', payment_terms => 'payment_term', periodic_invoices => 'periodic_invoice', periodic_invoices_configs => 'periodic_invoices_config', diff --git a/SL/DB/Manager/Part.pm b/SL/DB/Manager/Part.pm index b101d1a140..e1a1091802 100644 --- a/SL/DB/Manager/Part.pm +++ b/SL/DB/Manager/Part.pm @@ -34,6 +34,75 @@ __PACKAGE__->add_filter_specs( return or => [ map { $prefix . $_ => $value } qw(partnumber description ean customerprices.customer_partnumber) ], $prefix . 'customerprices'; }, + price_change_printed => sub { + my ($key, $value, $prefix) = @_; + die "value must be a scalar ref to a hash ref" + unless ref $value eq 'REF' && ref ($$value) eq 'HASH'; + + my %value = %$$value; + + my $template = $value{template}; + my $print_type = $value{print_type}; + my $printed = $value{printed}; + + my $comp = !!$printed ? '>' : '<='; + + # table part_table is aliased as t1 + return + [\qq{( + SELECT DISTINCT CASE WHEN count(*) $comp 1 THEN FALSE ELSE TRUE END + FROM ( + ( + -- last printed or first price + SELECT t2_sellprice + FROM ( + ( + SELECT + parts_price_history.sellprice AS t2_sellprice, + parts_price_history.valid_from AS t2_valid_from, + parts_price_history.id as t2_id + FROM parts_price_history + JOIN part_label_prints + ON (parts_price_history.id = part_label_prints.price_history_id) + WHERE parts_price_history.part_id = t1.id AND ( + ('' = ? OR part_label_prints.template = ?) AND + ('' = ? OR part_label_prints.print_type = ?) + ) + ORDER by + parts_price_history.valid_from DESC, + parts_price_history.id DESC + LIMIT 1 + ) UNION ( + SELECT + parts_price_history.sellprice AS t2_sellprice, + parts_price_history.valid_from AS t2_valid_from, + parts_price_history.id as t2_id + FROM parts_price_history + WHERE part_id = t1.id + ORDER BY + parts_price_history.valid_from ASC, + parts_price_history.id ASC + LIMIT 1 + ) + ) + ORDER by + t2_valid_from DESC, + t2_id DESC + LIMIT 1 + ) UNION ( + -- current price + SELECT parts_price_history.sellprice AS t2_sellprice + FROM parts_price_history + WHERE part_id = t1.id + ORDER BY + parts_price_history.valid_from DESC, + parts_price_history.id + DESC LIMIT 1 + ) + ) + )} => ($template, $template, $print_type, $print_type || 'stock') + ] => \'TRUE'; + }, ); sub type_filter { diff --git a/SL/DB/Manager/PartLabelPrint.pm b/SL/DB/Manager/PartLabelPrint.pm new file mode 100644 index 0000000000..3df62bbd87 --- /dev/null +++ b/SL/DB/Manager/PartLabelPrint.pm @@ -0,0 +1,14 @@ +# This file has been auto-generated only because it didn't exist. +# Feel free to modify it at will; it will not be overwritten automatically. + +package SL::DB::Manager::PartLabelPrint; + +use strict; + +use parent qw(SL::DB::Helper::Manager); + +sub object_class { 'SL::DB::PartLabelPrint' } + +__PACKAGE__->make_manager_methods; + +1; diff --git a/SL/DB/MetaSetup/PartLabelPrint.pm b/SL/DB/MetaSetup/PartLabelPrint.pm new file mode 100644 index 0000000000..a8a27787c7 --- /dev/null +++ b/SL/DB/MetaSetup/PartLabelPrint.pm @@ -0,0 +1,27 @@ +# This file has been auto-generated. Do not modify it; it will be overwritten +# by rose_auto_create_model.pl automatically. +package SL::DB::PartLabelPrint; + +use strict; + +use parent qw(SL::DB::Object); + +__PACKAGE__->meta->table('part_label_prints'); + +__PACKAGE__->meta->columns( + price_history_id => { type => 'integer', not_null => 1 }, + print_type => { type => 'enum', check_in => [ 'single', 'stock' ], db_type => 'part_label_print_types', not_null => 1 }, + template => { type => 'text', not_null => 1 }, +); + +__PACKAGE__->meta->primary_key_columns([ 'price_history_id', 'print_type', 'template' ]); + +__PACKAGE__->meta->foreign_keys( + price_history => { + class => 'SL::DB::PartsPriceHistory', + key_columns => { price_history_id => 'id' }, + }, +); + +1; +; diff --git a/SL/DB/PartLabelPrint.pm b/SL/DB/PartLabelPrint.pm new file mode 100644 index 0000000000..ff92bdda79 --- /dev/null +++ b/SL/DB/PartLabelPrint.pm @@ -0,0 +1,13 @@ +# This file has been auto-generated only because it didn't exist. +# Feel free to modify it at will; it will not be overwritten automatically. + +package SL::DB::PartLabelPrint; + +use strict; + +use SL::DB::MetaSetup::PartLabelPrint; +use SL::DB::Manager::PartLabelPrint; + +__PACKAGE__->meta->initialize; + +1; diff --git a/SL/DB/PartsPriceHistory.pm b/SL/DB/PartsPriceHistory.pm index f53e8f2903..150739f27e 100644 --- a/SL/DB/PartsPriceHistory.pm +++ b/SL/DB/PartsPriceHistory.pm @@ -5,6 +5,14 @@ use strict; use SL::DB::MetaSetup::PartsPriceHistory; use SL::DB::Manager::PartsPriceHistory; +__PACKAGE__->meta->add_relationships( + part_label_prints => { + type => 'many to one', + class => 'SL::DB::PartLabelPrint', + column_map => { id => 'price_history_id' }, + }, +); + __PACKAGE__->meta->initialize; 1; diff --git a/SL/Form.pm b/SL/Form.pm index 15a715a292..2f77068b2f 100644 --- a/SL/Form.pm +++ b/SL/Form.pm @@ -1123,6 +1123,8 @@ sub get_formname_translation { letter => $main::locale->text('Letter'), ic_supply => $main::locale->text('Intra-Community supply'), statement => $main::locale->text('Statement'), + part_info => $main::locale->text('Part info'), + part_label => $main::locale->text('Part label'), ); $main::lxdebug->leave_sub(); @@ -1152,6 +1154,7 @@ sub get_number_prefix_for_type { : ($self->{type} =~ /_quotation/) ? 'quo' : ($self->{type} =~ /_delivery_order$/) ? 'do' : ($self->{type} =~ /letter/) ? 'letter' + : ($self->{type} =~ /part/) ? 'part' : 'ord'; # better default like this? diff --git a/SL/Helper/PrintOptions.pm b/SL/Helper/PrintOptions.pm index eab17294be..56186d7c34 100644 --- a/SL/Helper/PrintOptions.pm +++ b/SL/Helper/PrintOptions.pm @@ -97,6 +97,10 @@ sub get_print_options { ($form->{type} =~ /_reclamation$/) ? ( opthash($form->{type}, $form->{PD}{$form->{type}}, $locale->text('Reclamation')), ) : undef, + ($form->{type} =~ /^part$/) ? ( + opthash('part_info', $form->{PD}{part_info}, $locale->text('Part info')), + opthash('part_label', $form->{PD}{part_label}, $locale->text('Part label')), + ) : undef, ($form->{type} =~ /^letter$/) ? ( opthash('letter', $form->{PD}{letter}, $locale->text('Letter')), ) : undef; @@ -133,7 +137,7 @@ sub get_print_options { push @LANGUAGE_ID, map { opthash($_->{id}, ($_->{id} eq $form->{language_id} ? 'selected' : ''), $_->{description}) } +{}, @{ $form->{languages} } - if (ref $form->{languages} eq 'ARRAY'); + if ((ref $form->{languages} eq 'ARRAY') && scalar @{$form->{languages}}); push @PRINTER_ID, map { opthash($_->{id}, ($_->{id} eq $form->{printer_id} ? 'selected' : ''), $_->{printer_description}) } +{}, @{ $form->{printers} } @@ -154,12 +158,16 @@ sub get_print_options { my %dont_display_groupitems = ( 'dunning' => 1, + 'part' => 1, ); my %template_vars = ( name_prefix => $prefix || '', show_headers => $options->{show_headers}, - display_copies => scalar @{ $form->{printers} || [] } && $::lx_office_conf{print_templates}->{latex} && $form->{media} ne 'email', + display_copies => !$options->{no_display_copies} + && scalar @{ $form->{printers} || [] } + && $::lx_office_conf{print_templates}->{latex} + && $form->{media} ne 'email', display_remove_draft => (!$form->{id} && $form->{draft_id}), display_groupitems => !$dont_display_groupitems{$form->{type}}, display_bothsided => $options->{show_bothsided}, diff --git a/SL/Presenter/Filter.pm b/SL/Presenter/Filter.pm new file mode 100644 index 0000000000..e90164c89e --- /dev/null +++ b/SL/Presenter/Filter.pm @@ -0,0 +1,378 @@ +package SL::Presenter::Filter; + +use strict; + +use SL::Presenter::EscapedText qw(escape is_escaped); +use SL::Presenter::Tag qw(html_tag input_tag select_tag date_tag checkbox_tag); +use SL::Locale::String qw(t8); + +use Carp; +use List::Util qw(min); +use Params::Validate qw(:all); + +sub create_filter { + validate_pos(@_, + { + type => HASHREF, + default => {}, + callbacks => { + has_all_keys => sub { + foreach my $main_key (keys %{$_[0]}) { + foreach my $sub_key (qw( + position text input_type input_name + )) { + return die "Key '$sub_key' is missing under '$main_key'." + unless exists $_[0]->{$main_key}->{$sub_key}; + } + } + return 1; + } + }, + }, + (0) x (@_ - 1) # allow extra parameters + ); + my $filter_elements = shift @_; + my %params = validate_with( + params => \@_, + spec => { + }, + allow_extra => 1, + ); + + my @filter_element_params = + sort { $a->{position} <=> $b->{position} } + grep { $_->{active} } + values %{$filter_elements}; + + my @filter_elements; + for my $filter_element_param (@filter_element_params) { + + my $filter_element = _create_input_element($filter_element_param, %params); + + push @filter_elements, $filter_element; + } + + my $filter_form_div = _create_filter_form(\@filter_elements, %params); + + is_escaped($filter_form_div); +} + +sub _create_input_element { + my $element_param = shift @_; + my %params = validate_with( + params => \@_, + spec => { + no_show => { + type => BOOLEAN, + default => 0 + }, + active_in_report => { + type => HASHREF, + default => {} + }, + }, + allow_extra => 1, + ); + + my $element_th; + + if($element_param->{input_type} eq 'input_group') { + $element_th = html_tag('th', $element_param->{text}, + align => 'right', + colspan => 2, + class => "caption", + ); + } else { + $element_th = html_tag('th', $element_param->{text}, align => 'right'); + } + + my $element_input = ''; + + if($element_param->{input_type} eq 'input_group') { + + my @filter_element_params = + sort { $a->{position} <=> $b->{position} } + grep { $_->{active} } + values %{$element_param->{input_values}}; + + + my @filter_elements; + for my $filter_element_param (@filter_element_params) { + my $filter_element = _create_input_element($filter_element_param, %params); + push @filter_elements, $filter_element; + } + + $element_input = join('', map{html_tag('tr',$_)} @filter_elements); + $element_input .= html_tag('tr'); + } elsif($element_param->{input_type} eq 'input_tag') { + + $element_input = input_tag($element_param->{input_name}, $element_param->{input_default}); + + } elsif ($element_param->{input_type} eq 'select_tag') { + + $element_input = select_tag($element_param->{input_name}, $element_param->{input_values}, default => $element_param->{input_default}) + + } elsif ($element_param->{input_type} eq 'yes_no_tag') { + + $element_input = select_tag($element_param->{input_name}, [ [ 1 => t8('Yes') ], [ 0 => t8('No') ] ], default => $element_param->{input_default}, with_empty => 1) + + } elsif($element_param->{input_type} eq 'date_tag') { + + my $after_input = + html_tag('th', t8("After"), align => 'right') . + html_tag('td', + date_tag("filter." . $element_param->{input_name} . ":date::ge", $element_param->{input_default_ge}) + ) + ; + my $before_input = + html_tag('th', t8("Before"), align => 'right') . + html_tag('td', + date_tag("filter." . $element_param->{input_name} . ":date::le", $element_param->{input_default_le}) + ) + ; + + $element_input = + html_tag('table', + html_tag('tr', $after_input) + . + html_tag('tr', $before_input) + ) + ; + } else { + confess "unknown input_type " . $element_param->{input_type}; + } + + my $element_input_td = html_tag('td', + $element_input, + nowrap => 1, + ); + + my $element_checkbox_td = ''; + unless($params{no_show} || $element_param->{report_id} eq '') { + my $checkbox = checkbox_tag( + 'active_in_report.' . $element_param->{report_id}, + checked => $params{active_in_report}->{$element_param->{report_id}}, + for_submit => 1 + ); + $element_checkbox_td = html_tag('td', $checkbox); + } + + return $element_th . $element_input_td . $element_checkbox_td; +} + +sub _create_filter_form { + my $ref_elements = shift @_; + my %params = validate_with( + params => \@_, + spec => { + }, + allow_extra => 1, + ); + + my $filter_table = _create_input_div($ref_elements, %params); + + my $filter_form = html_tag('form', $filter_table, method => 'post', action => 'controller.pl', id => 'filter_form'); + + return $filter_form; +} + +sub _create_input_div { + my $ref_elements = shift @_; + my %params = validate_with( + params => \@_, + spec => { + count_columns => { + type => SCALAR, + default => 4, + }, + no_show => { + type => BOOLEAN, + default => 0, + }, + }, + allow_extra => 1, + ); + my @elements = @{$ref_elements}; + my $count_elements = scalar(@elements); + my $count_columns = min($count_elements, $params{count_columns}); + + my $div_columns = ""; + my $start_index = 0; + my $min_elements_per_column = int(($count_elements) / $count_columns); + for my $i (0 .. ($count_columns - 1)) { + my $elements_in_cloumn = $min_elements_per_column + + (($count_elements % $count_columns) > $i ? 1 : 0); + + my $rows = ""; + for my $j (0 .. ($elements_in_cloumn - 1) ) { + my $idx = $start_index + $j; + my $element = $elements[$idx]; + $rows .= html_tag('tr', $element); + + } + $start_index += $elements_in_cloumn; + $div_columns .= html_tag('div', + html_tag('table', + html_tag('tr', + html_tag('td') + . html_tag('th', t8('Filter')) + . ( $params{no_show} ? '' : html_tag('th', t8('Show')) ) + ) + . $rows + ), + style => "flex:1"); + } + + my $input_div = html_tag('div', $div_columns, style => "display:flex;flex-wrap:wrap"); + + return $input_div; +} + +1; + +__END__ + +=pod + +=encoding utf8 + +=head1 NAME + +SL::Presenter::Filter - Presenter module for a generic Filter. + +=head1 SYNOPSIS + + my $filter_elements = { + id => { + 'position' => 1, + 'text' => t8("ID"), + 'input_type' => 'input_tag', + 'input_name' => 'filter.id:number', + 'input_default' => $::form->{filter}->{'id:number'}, + 'report_id' => 'id', + 'active' => 1, + }, + # ... + }; + + my $filter_html = SL::Presenter::Filter::create_filter( + $filter_elements, + active_in_report => ['id'], + ); + + +=head1 FUNCTIONS + +=over 4 + +=item C + +Returns a rendered version (actually an instance of +L) of a filter form for reclamations of type +C<$reclamation_type>. + +C<$filter_elements> is a hash reference with the values declaring which inputs +to create. + +=over 2 + +FILTER ELEMENTS + +A filter element is a hash reference. Each filter has a unique key and can have +entries for: + +=over 4 + +=item * position (mandatory) + +Is a number after which the elements are ordered. This can be a float. + +=item * text (mandatory) + +Is shown before the input field. + +=item * input_name (mandatory) + +C is used to set the name of the field, which should match the +filter syntax. + +=item * C (mandatory) + +This must be C, C, C or C. It sets +the input type for the filter. + +=over 2 + +=item * C + +Creates a text input field. The default value of this field is set to the +C entry of the filter element. + +=item * C + +Creates a drop down field. C is used to set the options. +See L for more details. The default value +of this field is set to the C entry. + +=item * C + +Creates a yes/no input field. The default value of this field is set to the +C entry. + +=item * C + +Creates two date input fields. One filters for after the date and the other +filters for before the date. The default values of these fields are set to the +C and C entries of the filter element. +For the first field ":date::ge" and for the second ":date::le" is added to the +end of C. + +=back + +=item * C + +Is used to generate the id of the check box after the input field. The value of +the check box can be found in the form under +C<$::form-E{'active_in_report'}-E{report_id}>. + +=item * C + +If falsish the element is ignored. + +=item * C, C, C + +Look at C to see how they are used. + +=back + +=back + +C<%params> can include: + +=over 2 + +=item * no_show + +If falsish (the default) then a check box is added after the input field. Which +specifies whether the corresponding column appears in the report. The value of +the check box can be changed by the user. + +=item * active_in_report + +If C<$params{no_show}> is falsish, this is used to set the values of the check +boxes, after the input fields. This should be set to the C +value of the last C<$::form>. + +=back + +=back + +=head1 BUGS + +Nothing here yet. + +=head1 AUTHOR + +Tamino Steinert Etamino.steinert@tamino.stE + +=cut diff --git a/SL/Presenter/Filter/Part.pm b/SL/Presenter/Filter/Part.pm new file mode 100644 index 0000000000..c04fc7fbc7 --- /dev/null +++ b/SL/Presenter/Filter/Part.pm @@ -0,0 +1,191 @@ +package SL::Presenter::Filter::Part; + +use parent SL::Presenter::Filter; + +use strict; + +use SL::Locale::String qw(t8); + +use Params::Validate qw(:all); + +sub get_default_filter_elements { + my ($filter) = @_; + + my %default_filter_elements = ( # {{{ + part_data => { + 'position' => 1, + 'text' => t8('Article data'), + 'input_type' => 'input_group', + 'input_values' => { + 'partnumber' => { + 'position' => 1, + 'text' => t8("Partnumber"), + 'input_type' => 'input_tag', + 'input_name' => 'filter.partnumber:substr::ilike', + 'input_default' => $filter->{'partnumber:substr::ilike'}, + 'report_id' => 'partnumber', + 'active' => 1, + }, + 'ean' => { + 'position' => 2, + 'text' => t8("EAN"), + 'input_type' => 'input_tag', + 'input_name' => 'filter.ean:substr::ilike', + 'input_default' => $filter->{'ean:substr::ilike'}, + 'report_id' => 'ean', + 'active' => 1, + }, + 'description' => { + 'position' => 3, + 'text' => t8("Part Description"), + 'input_type' => 'input_tag', + 'input_name' => 'filter.description:substr::ilike', + 'input_default' => $filter->{'description:substr::ilike'}, + 'report_id' => 'description', + 'active' => 1, + }, + 'notes' => { + 'position' => 4, + 'text' => t8("Notes"), + 'input_type' => 'input_tag', + 'input_name' => 'filter.notes:substr::ilike', + 'input_default' => $filter->{'notes:substr::ilike'}, + 'report_id' => 'notes', + 'active' => 1, + }, + 'partsgroup' => { + 'position' => 5, + 'text' => t8("Partsgroup"), + 'input_type' => 'input_tag', + 'input_name' => 'filter.partsgroup.partsgroup:substr::ilike', + 'input_default' => $filter->{partsgroup}->{'partsgroup:substr::ilike'}, + 'report_id' => 'partsgroup', + 'active' => 1, + }, + }, + 'input_name' => 'part_data', + 'active' => 1, + }, + ); # }}} + return \%default_filter_elements; +} + +sub get_price_change_printed_filter { + my ($filter) = @_; + my %part_label_print_filter = ( + price_change_printed => { + 'position' => 0.5, + 'text' => t8('Price Change Printed'), + 'input_type' => 'input_group', + 'input_values' => { + 'template' => { + 'position' => 1, + 'text' => t8("Template"), + 'input_type' => 'select_tag', + 'input_values' => [ + ['' => ''], + ['part_label' => t8('Part label')], + ['part_info' => t8('Part info')], + ], + 'input_name' => 'filter.price_change_printed.template', + 'input_default' => $filter->{price_change_printed}->{template}, + 'active' => 1, + }, + 'print_type' => { + 'position' => 2, + 'text' => t8("Print Type"), + 'input_type' => 'select_tag', + 'input_values' => [ + ['' => ''], + ['stock' => t8('Stock')], + ['single' => t8('Single')], + ], + 'input_name' => 'filter.price_change_printed.print_type', + 'input_default' => $filter->{price_change_printed}->{print_type}, + 'active' => 1, + }, + 'printed' => { + 'position' => 3, + 'text' => t8("Printed"), + 'input_type' => 'select_tag', + 'input_values' => [['0' => t8('No')], ['1' => t8('Yes')]], + 'input_name' => 'filter.price_change_printed.printed', + 'input_default' => $filter->{price_change_printed}->{printed}, + 'active' => 1, + }, + }, + 'input_name' => 'price_change_printed_group', + 'active' => 1, + }, + ); + return \%part_label_print_filter +} + +sub filter { + my $filter = shift @_; + die "filter has to be a hash ref" if ref $filter ne 'HASH'; + my %params = validate_with( + params => \@_, + spec => { + }, + allow_extra => 1, + ); + + my $filter_elements = get_default_filter_elements($filter); + if(delete $params{show_price_change_printed_filter}) { + $filter_elements = { + %{$filter_elements}, + %{get_price_change_printed_filter($filter)} + } + } + + return SL::Presenter::Filter::create_filter($filter_elements, %params); +} + +1; + +__END__ + +=pod + +=encoding utf8 + +=head1 NAME + +SL::Presenter::Filter::Reclamation - Presenter module for a generic Filter on +Reclamation. + +=head1 SYNOPSIS + + # in Reclamation Controller + my $filter_html = SL::Presenter::Filter::Reclamation::filter( + $::form->{filter}, $self->type, active_in_report => $::form->{active_in_report} + ); + + +=head1 FUNCTIONS + +=over 4 + +=item C + +Returns a rendered version (actually an instance of +L) of a filter form for reclamations of type +C<$reclamation_type>. + +C<$filter> should be the C value of the last C<$::form>. This is used to +get the previous values of the input fields. + +C<%params> fields get forwarded to C. + +=back + +=head1 BUGS + +Nothing here yet. + +=head1 AUTHOR + +Tamino Steinert Etamino.steinert@tamino.stE + +=cut diff --git a/SL/Presenter/ReclamationFilter.pm b/SL/Presenter/Filter/Reclamation.pm similarity index 56% rename from SL/Presenter/ReclamationFilter.pm rename to SL/Presenter/Filter/Reclamation.pm index a7f0dc3548..1b963d4aa9 100644 --- a/SL/Presenter/ReclamationFilter.pm +++ b/SL/Presenter/Filter/Reclamation.pm @@ -1,22 +1,16 @@ -package SL::Presenter::ReclamationFilter; +package SL::Presenter::Filter::Reclamation; + +use parent SL::Presenter::Filter; use strict; -use SL::Presenter::EscapedText qw(escape is_escaped); -use SL::Presenter::Tag qw(html_tag input_tag select_tag date_tag checkbox_tag); use SL::Locale::String qw(t8); -use Exporter qw(import); -our @EXPORT_OK = qw( -filter -); - -use Carp; +use Params::Validate qw(:all); -sub filter { - my ($filter, $reclamation_type, %params) = @_; +sub get_default_filter_elements { + my ($filter, $reclamation_type) = @_; - $filter ||= undef; #filter should not be '' (empty string); my %default_filter_elements = ( # {{{ 'reason_names' => { 'position' => 1, @@ -31,7 +25,7 @@ sub filter { 'text' => t8("Reclamation ID"), 'input_type' => 'input_tag', 'input_name' => 'filter.id:number', - 'input_default' =>$filter->{'id:number'}, + 'input_default' => $filter->{'id:number'}, 'report_id' => 'id', 'active' => 0, }, @@ -40,7 +34,7 @@ sub filter { 'text' => t8("Reclamation Number"), 'input_type' => 'input_tag', 'input_name' => 'filter.record_number:substr::ilike', - 'input_default' =>$filter->{'record_number:substr::ilike'}, + 'input_default' => $filter->{'record_number:substr::ilike'}, 'report_id' => 'record_number', 'active' => 1, }, @@ -49,7 +43,7 @@ sub filter { 'text' => t8("Employee Name"), 'input_type' => 'input_tag', 'input_name' => 'filter.employee.name:substr::ilike', - 'input_default' =>$filter->{employee}->{'name:substr::ilike'}, + 'input_default' => $filter->{employee}->{'name:substr::ilike'}, 'report_id' => 'employee', 'active' => 1, }, @@ -58,7 +52,7 @@ sub filter { 'text' => t8("Salesman Name"), 'input_type' => 'input_tag', 'input_name' => 'filter.salesman.name:substr::ilike', - 'input_default' =>$filter->{salesman}->{'name:substr::ilike'}, + 'input_default' => $filter->{salesman}->{'name:substr::ilike'}, 'report_id' => 'salesman', 'active' => 1, }, @@ -102,7 +96,7 @@ sub filter { 'text' => t8("Contact Name"), 'input_type' => 'input_tag', 'input_name' => 'filter.contact.cp_name:substr::ilike', - 'input_default' =>$filter->{contact}->{'cp_name:substr::ilike'}, + 'input_default' => $filter->{contact}->{'cp_name:substr::ilike'}, 'report_id' => 'contact', 'active' => 1, }, @@ -111,7 +105,7 @@ sub filter { 'text' => t8("Language Code"), 'input_type' => 'input_tag', 'input_name' => 'filter.language.article_code:substr::ilike', - 'input_default' =>$filter->{language}->{'article_code:substr::ilike'}, + 'input_default' => $filter->{language}->{'article_code:substr::ilike'}, 'report_id' => 'language', 'active' => 1, }, @@ -120,7 +114,7 @@ sub filter { 'text' => t8("Department Description"), 'input_type' => 'input_tag', 'input_name' => 'filter.department.description:substr::ilike', - 'input_default' =>$filter->{department}->{'description:substr::ilike'}, + 'input_default' => $filter->{department}->{'description:substr::ilike'}, 'report_id' => 'department', 'active' => 1, }, @@ -129,7 +123,7 @@ sub filter { 'text' => t8("Project Number"), 'input_type' => 'input_tag', 'input_name' => 'filter.globalproject.projectnumber:substr::ilike', - 'input_default' =>$filter->{globalproject}->{'projectnumber:substr::ilike'}, + 'input_default' => $filter->{globalproject}->{'projectnumber:substr::ilike'}, 'report_id' => 'globalproject', 'active' => 1, }, @@ -138,7 +132,7 @@ sub filter { 'text' => t8("Project Description"), 'input_type' => 'input_tag', 'input_name' => 'filter.globalproject.description:substr::ilike', - 'input_default' =>$filter->{globalproject}->{'description:substr::ilike'}, + 'input_default' => $filter->{globalproject}->{'description:substr::ilike'}, 'active' => 1, }, 'cv_record_number' => { @@ -158,7 +152,7 @@ sub filter { 'text' => t8("Description"), 'input_type' => 'input_tag', 'input_name' => 'filter.transaction_description:substr::ilike', - 'input_default' =>$filter->{'transaction_description:substr::ilike'}, + 'input_default' => $filter->{'transaction_description:substr::ilike'}, 'report_id' => 'transaction_description', 'active' => 1, }, @@ -167,7 +161,7 @@ sub filter { 'text' => t8("Notes"), 'input_type' => 'input_tag', 'input_name' => 'filter.notes:substr::ilike', - 'input_default' =>$filter->{'notes:substr::ilike'}, + 'input_default' => $filter->{'notes:substr::ilike'}, 'report_id' => 'notes', 'active' => 1, }, @@ -176,7 +170,7 @@ sub filter { 'text' => t8("Internal Notes"), 'input_type' => 'input_tag', 'input_name' => 'filter.intnotes:substr::ilike', - 'input_default' =>$filter->{'intnotes:substr::ilike'}, + 'input_default' => $filter->{'intnotes:substr::ilike'}, 'report_id' => 'intnotes', 'active' => 1, }, @@ -185,7 +179,7 @@ sub filter { 'text' => t8("Shipping Point"), 'input_type' => 'input_tag', 'input_name' => 'filter.shippingpoint:substr::ilike', - 'input_default' =>$filter->{'shippingpoint:substr::ilike'}, + 'input_default' => $filter->{'shippingpoint:substr::ilike'}, 'report_id' => 'shippingpoint', 'active' => 1, }, @@ -194,7 +188,7 @@ sub filter { 'text' => t8("Ship via"), 'input_type' => 'input_tag', 'input_name' => 'filter.shipvia:substr::ilike', - 'input_default' =>$filter->{'shipvia:substr::ilike'}, + 'input_default' => $filter->{'shipvia:substr::ilike'}, 'report_id' => 'shipvia', 'active' => 1, }, @@ -203,7 +197,7 @@ sub filter { 'text' => t8("Total"), 'input_type' => 'input_tag', 'input_name' => 'filter.amount:number', - 'input_default' =>$filter->{'amount:number'}, + 'input_default' => $filter->{'amount:number'}, 'report_id' => 'amount', 'active' => 1, }, @@ -212,7 +206,7 @@ sub filter { 'text' => t8("Subtotal"), 'input_type' => 'input_tag', 'input_name' => 'filter.netamount:number', - 'input_default' =>$filter->{'netamount:number'}, + 'input_default' => $filter->{'netamount:number'}, 'report_id' => 'netamount', 'active' => 1, }, @@ -221,7 +215,7 @@ sub filter { 'text' => t8("Delivery Terms"), 'input_type' => 'input_tag', 'input_name' => 'filter.delivery_term.description:substr::ilike', - 'input_default' =>$filter->{delivery_term}->{'description:substr::ilike'}, + 'input_default' => $filter->{delivery_term}->{'description:substr::ilike'}, 'report_id' => 'delivery_term', 'active' => 1, }, @@ -230,7 +224,7 @@ sub filter { 'text' => t8("Payment Terms"), 'input_type' => 'input_tag', 'input_name' => 'filter.payment.description:substr::ilike', - 'input_default' =>$filter->{payment}->{'description:substr::ilike'}, + 'input_default' => $filter->{payment}->{'description:substr::ilike'}, 'report_id' => 'payment', 'active' => 1, }, @@ -239,7 +233,7 @@ sub filter { 'text' => t8("Currency"), 'input_type' => 'input_tag', 'input_name' => 'filter.currency.name:substr::ilike', - 'input_default' =>$filter->{currency}->{'name:substr::ilike'}, + 'input_default' => $filter->{currency}->{'name:substr::ilike'}, 'report_id' => 'currency', 'active' => 1, }, @@ -248,7 +242,7 @@ sub filter { 'text' => t8("Exchangerate"), 'input_type' => 'input_tag', 'input_name' => 'filter.exchangerate:number', - 'input_default' =>$filter->{'exchangerate:number'}, + 'input_default' => $filter->{'exchangerate:number'}, 'report_id' => 'exchangerate', 'active' => 1, }, @@ -257,7 +251,7 @@ sub filter { 'text' => t8("Tax Included"), 'input_type' => 'yes_no_tag', 'input_name' => 'filter.taxincluded', - 'input_default' =>$filter->{taxincluded}, + 'input_default' => $filter->{taxincluded}, 'report_id' => 'taxincluded', 'active' => 1, }, @@ -266,7 +260,7 @@ sub filter { 'text' => t8("Tax zone"), 'input_type' => 'input_tag', 'input_name' => 'filter.taxzone.description:substr::ilike', - 'input_default' =>$filter->{taxzone}->{'description:substr::ilike'}, + 'input_default' => $filter->{taxzone}->{'description:substr::ilike'}, 'report_id' => 'taxzone', 'active' => 1, }, @@ -325,7 +319,7 @@ sub filter { 'text' => t8("Delivered"), 'input_type' => 'yes_no_tag', 'input_name' => 'filter.delivered', - 'input_default' =>$filter->{delivered}, + 'input_default' => $filter->{delivered}, 'report_id' => 'delivered', 'active' => 1, }, @@ -334,142 +328,30 @@ sub filter { 'text' => t8("Closed"), 'input_type' => 'yes_no_tag', 'input_name' => 'filter.closed', - 'input_default' =>$filter->{closed}, + 'input_default' => $filter->{closed}, 'report_id' => 'closed', 'active' => 1, }, ); # }}} - - # combine default and param values for filter_element, - # only replace the lowest occurrence - my %filter_elements = %default_filter_elements; - while(my ($key, $value) = each (%{$params{filter_elements}})) { - if(exists $filter_elements{$key}) { - $filter_elements{$key} = ({ - %{$filter_elements{$key}}, - %{$value}, - }); - } else { - $filter_elements{$key} = $value; - } - } - - my @filter_element_params = - sort { $a->{position} <=> $b->{position} } - grep { $_->{active} } - values %filter_elements; - - my @filter_elements; - for my $filter_element_param (@filter_element_params) { - unless($filter_element_param->{active}) { - next; - } - - my $filter_element = _create_input_element($filter_element_param, %params); - - push @filter_elements, $filter_element; - } - - my $filter_form_div = _create_filter_form(\@filter_elements, %params); - - is_escaped($filter_form_div); + return \%default_filter_elements; } -sub _create_input_element { - my ($element_param, %params) = @_; - - my $element_th = html_tag('th', $element_param->{text}, align => 'right'); - - my $element_input = ''; - - if($element_param->{input_type} eq 'input_tag') { - - $element_input = input_tag($element_param->{input_name}, $element_param->{input_default}); - - } elsif ($element_param->{input_type} eq 'yes_no_tag') { - - $element_input = select_tag($element_param->{input_name}, [ [ 1 => t8('Yes') ], [ 0 => t8('No') ] ], default => $element_param->{input_default}, with_empty => 1) - - } elsif($element_param->{input_type} eq 'date_tag') { - - my $after_input = - html_tag('th', t8("After"), align => 'right') . - html_tag('td', - date_tag("filter." . $element_param->{input_name} . ":date::ge", $element_param->{input_default_ge}) - ) - ; - my $before_input = - html_tag('th', t8("Before"), align => 'right') . - html_tag('td', - date_tag("filter." . $element_param->{input_name} . ":date::le", $element_param->{input_default_le}) - ) - ; - - $element_input = - html_tag('table', - html_tag('tr', $after_input) - . - html_tag('tr', $before_input) - ) - ; - } - - my $element_input_td = html_tag('td', - $element_input, - nowrap => 1, +sub filter { + my $filter = shift @_; + die "filter has to be a hash ref" if ref $filter ne 'HASH'; + my $reclamation_type = shift @_; + my %params = validate_with( + params => \@_, + spec => { + }, + allow_extra => 1, ); - my $element_checkbox_td = ''; - unless($params{no_show} || $element_param->{report_id} eq '') { - my $checkbox = checkbox_tag('active_in_report.' . $element_param->{report_id}, checked => $params{active_in_report}->{$element_param->{report_id}}, for_submit => 1); - $element_checkbox_td = html_tag('td', $checkbox); - } - - return $element_th . $element_input_td . $element_checkbox_td; -} - -sub _create_filter_form { - my ($ref_elements, %params) = @_; - - my $filter_table = _create_input_div($ref_elements, %params); - - my $filter_form = html_tag('form', $filter_table, method => 'post', action => 'controller.pl', id => 'search_form'); - - return $filter_form; -} - -sub _create_input_div { - my ($ref_elements, %params) = @_; - my @elements = @{$ref_elements}; - - my $div_columns = ""; - - $params{count_columns} ||= 4; - my $elements_per_column = (int((scalar(@{$ref_elements}) - 1) / $params{count_columns}) + 1); - for my $i (0 .. ($params{count_columns} - 1)) { - - my $rows = ""; - for my $j (0 .. ($elements_per_column - 1) ) { - my $idx = $elements_per_column * $i + $j; - my $element = $elements[$idx]; - $rows .= html_tag('tr', $element); - - } - $div_columns .= html_tag('div', - html_tag('table', - html_tag('tr', - html_tag('td') - . html_tag('th', t8('Filter')) - . ( $params{no_show} ? '' : html_tag('th', t8('Show')) ) - ) - . $rows - ), - style => "flex:1"); - } - - my $input_div = html_tag('div', $div_columns, style => "display:flex;flex-wrap:wrap"); + # combine default and param values for filter_element, + # only replace the lowest occurrence + my $filter_elements = get_default_filter_elements($filter, $reclamation_type); - return $input_div; + return SL::Presenter::Filter::create_filter($filter_elements, %params); } 1; @@ -482,13 +364,13 @@ __END__ =head1 NAME -SL::Presenter::ReclamationFilter - Presenter module for a generic Filter on +SL::Presenter::Filter::Reclamation - Presenter module for a generic Filter on Reclamation. =head1 SYNOPSIS # in Reclamation Controller - my $filter_html = SL::Presenter::ReclamationFilter::filter( + my $filter_html = SL::Presenter::Filter::Reclamation::filter( $::form->{filter}, $self->type, active_in_report => $::form->{active_in_report} ); @@ -506,95 +388,7 @@ C<$reclamation_type>. C<$filter> should be the C value of the last C<$::form>. This is used to get the previous values of the input fields. -C<%params> can include: - -=over 2 - -=item * no_show - -If falsish (the default) then a check box is added after the input field. Which -specifies whether the corresponding column appears in the report. The value of -the check box can be changed by the user. - -=item * active_in_report - -If C<$params{no_show}> is falsish, this is used to set the values of the check -boxes, after the input fields. This can be set to the C value -of the last C<$::form>. - -=item * filter_elements - -Is combined with the default filter elements. This can be used to override -default values of the filter elements or to add a new ones. - - #deactivate the id and record_number fields - $params{filter_elements} = ({ - id => {active => 0}, - record_number => {active => 0} - }); - -=back - -=back - -=head1 FILTER ELEMENTS - -A filter element is stored in and as a hash map. Each filter has a unique key -and should have entries for: - -=over 4 - -=item * position - -Is a number after which the elements are ordered. This can be a float. - -=item * text - -Is shown before the input field. - -=item * input_type - -This must be C, C or C. It sets the input type -for the filter. - -=over 2 - -=item * input_tag - -Creates a text input field. The default value of this field is set to the -C entry of the filter element. C is used to set the -name of the field, which should match the filter syntax. - -=item * yes_no_tag - -Creates a yes/no input field. The default value of this field is set to the -C entry of the filter element. C is used to set the -name of the field, which should match the filter syntax. - -=item * date_tag - -Creates two date input fields. One filters for after the date and the other -filters for before the date. The default values of these fields are set to the -C and C entries of the filter element. -C is used to set the names of these fields, which should match the -filter syntax. For the first field ":date::ge" and for the second ":date::le" is -added to the end of C. - -=back - -=item * report_id - -Is used to generate the id of the check box after the input field. The value of -the check box can be found in the form under -C<$::form-E{'active_in_report'}-E{report_id}>. - -=item * active - -If falsish the element is ignored. - -=item * input_name, input_default, input_default_ge, input_default_le - -Look at I to see how they are used. +C<%params> fields get forwarded to C. =back diff --git a/bin/mozilla/amtemplates.pl b/bin/mozilla/amtemplates.pl index 0917769f3b..14ee5c714b 100644 --- a/bin/mozilla/amtemplates.pl +++ b/bin/mozilla/amtemplates.pl @@ -156,6 +156,8 @@ sub display_template_form { credit_note => $locale->text('Credit Note'), income_statement => { translation => $locale->text('Income Statement'), html => 1 }, invoice => $locale->text('Invoice'), + part_info => { translation => $locale->text('Part info'), tex => 1 }, + part_label => { translation => $locale->text('Part label'), tex => 1 }, pick_list => $locale->text('Pick List'), proforma => $locale->text('Proforma Invoice'), purchase_delivery_order => { translation => $::locale->text('Purchase delivery order'), tex => 1 }, diff --git a/bin/mozilla/do.pl b/bin/mozilla/do.pl index bdaad2b162..d86756cc3e 100644 --- a/bin/mozilla/do.pl +++ b/bin/mozilla/do.pl @@ -1572,7 +1572,7 @@ sub display_stock_in_form { my %myconfig = %main::myconfig; my $locale = $main::locale; - $form->{title} = $locale->text('Stock'); + $form->{title} = $locale->text('Store'); my $part_info = IC->get_basic_part_info('id' => $form->{parts_id}); diff --git a/doc/changelog b/doc/changelog index 4c396165f5..5b63867d2a 100644 --- a/doc/changelog +++ b/doc/changelog @@ -10,6 +10,8 @@ Mittelgroße neue Features: Kleinere neue Features und Detailverbesserungen: + - Es können jetzt Artikeldaten aus der Artikelmaske gedruckt werden. Z.B + Etiketten oder Stammblätter. Bugfixes (Tracker: https://www.kivitendo.de/redmine): diff --git a/js/kivi.Part.js b/js/kivi.Part.js index 4d0361af99..ab144cd84e 100644 --- a/js/kivi.Part.js +++ b/js/kivi.Part.js @@ -26,7 +26,69 @@ namespace('kivi.Part', function(ns) { var data = $('#print_options_form').serializeArray(); data.push({ name: 'action', value: 'Part/print_label' }); data.push({ name: 'part.id', value: part_id }); - $.download("controller.pl", data); + $.download("controller.pl", data); + }; + + ns.show_print_options = function() { + kivi.popup_dialog({ + id: 'print_options', + dialog: { + title: kivi.t8('Print options'), + width: 800, + height: 300 + } + }); + } + + ns.save_and_print = function() { + $('#print_options').dialog('close'); + + var data = $('#ic').serializeArray(); + data = data.concat($('#print_options_form').serializeArray()); + data.push({ name: 'action', value: 'Part/save_and_print' }); + + $.post("controller.pl", data, kivi.eval_json_result); + }; + + ns.print_multi = function() { + $('#print_options').dialog('close'); + + var data = $('#report_form').serializeArray(); + data = data.concat($('#print_options_form').serializeArray()); + data.push({ name: 'action', value: 'Part/print_multi' }); + + $.post("controller.pl", data, kivi.eval_json_result); + }; + + ns.show_part_labels_for_stock_print_options = function() { + kivi.popup_dialog({ + id: 'print_part_labels_for_stock_print_options', + dialog: { + title: kivi.t8('Print options for Stock Print'), + width: 800, + height: 300 + } + }); + }; + + ns.save_and_print_part_labels_for_stock = function() { + $('#print_part_labels_for_stock_print_options').dialog('close'); + + var data = $('#ic').serializeArray(); + data = data.concat($('#print_part_labels_for_stock_print_options_form').serializeArray()); + data.push({ name: 'action', value: 'Part/save_and_print' }); + + $.post("controller.pl", data, kivi.eval_json_result); + }; + + ns.print_multi_part_labels_for_stock = function() { + $('#print_part_labels_for_stock_print_options').dialog('close'); + + var data = $('#report_form').serializeArray(); + data = data.concat($('#print_part_labels_for_stock_print_options_form').serializeArray()); + data.push({ name: 'action', value: 'Part/print_multi' }); + + $.post("controller.pl", data, kivi.eval_json_result); }; ns.delete = function() { diff --git a/js/locale/de.js b/js/locale/de.js index b0d9c5046e..bc30cad195 100644 --- a/js/locale/de.js +++ b/js/locale/de.js @@ -133,6 +133,7 @@ namespace("kivi").setupLocale({ "Previous month":"vorheriger Monat", "Price Types":"Preistypen", "Print options":"Druckoptionen", +"Print options for Stock Print":"Druckoptionen für Bestand-Druck", "Print record":"Beleg drucken", "Project link actions":"Projektverknüpfungs-Aktionen", "Project picker":"Projektauswahl", diff --git a/js/locale/en.js b/js/locale/en.js index 0d7e746dc1..0db2a384bd 100644 --- a/js/locale/en.js +++ b/js/locale/en.js @@ -133,6 +133,7 @@ namespace("kivi").setupLocale({ "Previous month":"", "Price Types":"", "Print options":"", +"Print options for Stock Print":"", "Print record":"", "Project link actions":"", "Project picker":"", diff --git a/locale/de/all b/locale/de/all index 0a1047eb58..8fc76b228e 100755 --- a/locale/de/all +++ b/locale/de/all @@ -962,6 +962,7 @@ $self->{texts} = { 'Creating Documents' => 'Erzeuge Dokumente', 'Creating Factur-X/ZUGFeRD invoices is not enabled for this customer.' => 'Das Erzeugen von Factur-X/ZUGFeRD-Rechnungen ist für diesen Kunden nicht aktiviert.', 'Creating invoices' => 'Erzeuge Rechnungen', + 'Creating the PDF failed!' => 'Erstellung des PDFs fehlgeschlagen!', 'Creating the PDF failed:' => 'PDF-Erzeugung fehlgeschlagen:', 'Creation Date' => 'Erstelldatum', 'Creation Time' => 'Erstellungszeit', @@ -2820,6 +2821,8 @@ $ ./scripts/installation_check.pl', 'Part already in purchasebasket or has no vendor' => 'Artikel ist schon im Bestellwarenkorb oder hat keinen Lieferanten', 'Part already ordered' => 'Artikel ist bestellt', 'Part classifications' => 'Artikel-Klassifizierungen', + 'Part info' => 'Artikelinfo', + 'Part label' => 'Artikeletikett', 'Part marked as "Shop part"' => 'Markiert als Shopartikel', 'Part picker' => 'Artikelauswahl', 'Part picker search dialog (magnifying glass): "all as list" defaults to on' => 'Artikel-Such-Dialog (Lupe): "alle als Liste" voreinstellen', @@ -2834,6 +2837,7 @@ $ ./scripts/installation_check.pl', 'Parts Classification' => 'Artikel-Klassifizierung', 'Parts Inventory' => 'Warenliste', 'Parts Master Data' => 'Artikelstammdaten', + 'Parts with Changed Prices' => 'Artikel mit Preisänderung', 'Parts with existing part numbers' => 'Artikel mit existierender Artikelnummer', 'Parts, services and assemblies' => 'Waren, Dienstleistungen und Erzeugnisse', 'Partsgroup' => 'Warengruppe', @@ -3012,6 +3016,7 @@ $ ./scripts/installation_check.pl', 'Previous transnumber text' => 'Letzte Buchung mit der Buchungsnummer', 'Price' => 'Preis', 'Price #1' => 'Preis #1', + 'Price Change Printed' => 'Preisänderung gedruckt', 'Price Factor' => 'Preisfaktor', 'Price Factors' => 'Preisfaktoren', 'Price List' => 'Preisliste', @@ -3040,6 +3045,9 @@ $ ./scripts/installation_check.pl', 'Prices' => 'Preise', 'Prices & Discounts' => 'Preise & Rabatte', 'Print' => 'Drucken', + 'Print Labels for Stock' => 'Etikettendruck für Bestand', + 'Print Part Labels of Changed Prices' => 'Etikettendruck für geänderte Preise', + 'Print Type' => 'Drucktyp', 'Print and Post' => 'Drucken und Buchen', 'Print automatically' => 'Automatisch ausdrucken', 'Print both sided' => 'Beidseitig ausdrucken', @@ -3048,12 +3056,15 @@ $ ./scripts/installation_check.pl', 'Print destination (copy)' => 'Druckausgabe (Kopie)', 'Print dunnings' => 'Mahnungen drucken', 'Print list' => 'Liste ausdrucken', + 'Print one Label' => 'Etikettendruck einzeln', 'Print options' => 'Druckoptionen', + 'Print options for Stock Print' => 'Druckoptionen für Bestand-Druck', 'Print record' => 'Beleg drucken', 'Print template base file name' => 'Druckvorlagen-Basisdateiname', 'Print templates' => 'Druckvorlagen', 'Print templates to use' => 'Zu verwendende Druckvorlagen', 'Printdate' => 'Druckdatum', + 'Printed' => 'Gedruckt', 'Printer' => 'Drucker', 'Printer Command' => 'Druckbefehl', 'Printer Description' => 'Druckerbeschreibung', @@ -3465,6 +3476,7 @@ $ ./scripts/installation_check.pl', 'Save and Invoice' => 'Speichern und Rechnung erfassen', 'Save and Invoice for Advance Payment' => 'Speichern und Anzahlungsrechnung', 'Save and Order' => 'Speichern und Auftrag erfassen', + 'Save and Print Labels for Stock' => 'Speichern und Etiketten für Bestand drucken', 'Save and Purchase Delivery Order' => 'Speichern und Lieferschein (Einkauf)', 'Save and Purchase Delivery Order with item selection' => 'Speichern und Lieferschein (Einkauf) mit Artikelauswahl', 'Save and Purchase Order' => 'Speichern und Lieferantenauftrag', @@ -3747,6 +3759,7 @@ $ ./scripts/installation_check.pl', 'Show »not delivered qty/value« column in sales and purchase orders' => 'Spalte »Nicht gelieferte Menge/Wert« in Aufträgen anzeigen', 'Signature' => 'Unterschrift', 'Since bin is not enforced in the parts data, please specify a bin where goods without a specified bin will be put.' => 'Da Lagerplätze kein Pflichtfeld sind, geben Sie bitte einen Lagerplatz an, in dem Waren ohne spezifizierten Lagerplatz eingelagert werden sollen.', + 'Single' => 'Einzeln', 'Single quotes' => 'Einfache Anführungszeichen', 'Single values in item mode, cumulated values in invoice mode' => 'Einzelwerte im Artikelmodus, kumulierte Werte im Rechnungsmodus', 'Singular' => 'Singular', @@ -3814,7 +3827,7 @@ $ ./scripts/installation_check.pl', 'Step 2' => 'Schritt 2', 'Step 2 -- Watch status' => 'Schritt 2 -- Status beobachten', 'Steuersatz' => 'Steuersatz', - 'Stock' => 'Einlagern', + 'Stock' => 'Bestand', 'Stock Local/Shop' => 'Bestand Lokal/Online', 'Stock Qty for Date' => 'Lagerbestand am', 'Stock for part #1' => 'Bestand für Artikel #1', @@ -3833,6 +3846,7 @@ $ ./scripts/installation_check.pl', 'Storage Type for generated/imported PDF Documents' => 'Speichertyp für erzeugte oder importierte Dokumente', 'Storage Type for images' => 'Speichertyp für Bilder', 'Storage Type for shopimages' => 'Speichertyp für Shopbilder', + 'Store' => 'Einlagern', 'Storing PDF in storage backend failed: #1' => 'Speichern der PDF-Datei im Datei-Speicher fehlgeschlagen: #1', 'Storing PDF to webdav folder failed: #1' => 'Speichern der PDF im WebDAV Ordner fehlgeschlagen: #1', 'Storing the ZUGFeRD file in the storage backend failed: #1' => 'Das Speichern der ZUGFeRD Datei im Datei-Speicher ist fehlgeschlagen: #1', @@ -4115,6 +4129,7 @@ $ ./scripts/installation_check.pl', 'The document has been changed by another user. Please reopen it in another window and copy the changes to the new window' => 'Die Daten wurden bereits von einem anderen Benutzer verändert. Deshalb ist das Dokument ungültig. Bitte öffnen Sie das Dokument erneut in einem extra Fenster und übertragen Sie die Daten', 'The document has been created.' => 'Das Dokument wurde erzeugt.', 'The document has been printed.' => 'Das Dokument wurde gedruckt.', + 'The document has been sent to the printer \'#1\'.' => 'Das Dokument wurde zum Drucker \'#1\' gesendet.', 'The documents have been sent to the printer \'#1\'.' => 'Die Dokumente sind zum Drucker \'#1\' geschickt', 'The dunnings have been printed.' => 'Die Mahnung(en) wurden gedruckt.', 'The email entry for #1 looks invalid' => 'Die eingetragene E-Mail-Adresse für #1 sieht ungültig aus.', @@ -4204,6 +4219,7 @@ $ ./scripts/installation_check.pl', 'The order intake has been deleted' => 'Der Auftragseingang wurde gelöscht', 'The order intake has been saved' => 'Der Auftragseingang wurde gespeichert', 'The package name is invalid.' => 'Der Paketname ist ungültig.', + 'The part has no stock.' => 'Der Artikel hat kein Bestand', 'The partnumber already exists!' => 'Die Artikelnummer wird bereits verwendet.', 'The partnumber already exists.' => 'Die Artikelnummer wird bereits verwendet.', 'The partnumber is already being used' => 'Der Artikel ist bereits in Verwendung', diff --git a/locale/en/all b/locale/en/all index 489def3a4f..f8dc3ac7ef 100755 --- a/locale/en/all +++ b/locale/en/all @@ -962,6 +962,7 @@ $self->{texts} = { 'Creating Documents' => '', 'Creating Factur-X/ZUGFeRD invoices is not enabled for this customer.' => '', 'Creating invoices' => '', + 'Creating the PDF failed!' => '', 'Creating the PDF failed:' => '', 'Creation Date' => '', 'Creation Time' => '', @@ -2819,6 +2820,8 @@ $self->{texts} = { 'Part already in purchasebasket or has no vendor' => '', 'Part already ordered' => '', 'Part classifications' => '', + 'Part info' => '', + 'Part label' => '', 'Part marked as "Shop part"' => '', 'Part picker' => '', 'Part picker search dialog (magnifying glass): "all as list" defaults to on' => '', @@ -2833,6 +2836,7 @@ $self->{texts} = { 'Parts Classification' => '', 'Parts Inventory' => '', 'Parts Master Data' => '', + 'Parts with Changed Prices' => '', 'Parts with existing part numbers' => '', 'Parts, services and assemblies' => '', 'Partsgroup' => '', @@ -3011,6 +3015,7 @@ $self->{texts} = { 'Previous transnumber text' => '', 'Price' => '', 'Price #1' => '', + 'Price Change Printed' => '', 'Price Factor' => '', 'Price Factors' => '', 'Price List' => '', @@ -3039,6 +3044,9 @@ $self->{texts} = { 'Prices' => '', 'Prices & Discounts' => '', 'Print' => '', + 'Print Labels for Stock' => '', + 'Print Part Labels of Changed Prices' => '', + 'Print Type' => '', 'Print and Post' => '', 'Print automatically' => '', 'Print both sided' => '', @@ -3047,12 +3055,15 @@ $self->{texts} = { 'Print destination (copy)' => '', 'Print dunnings' => '', 'Print list' => '', + 'Print one Label' => '', 'Print options' => '', + 'Print options for Stock Print' => '', 'Print record' => '', 'Print template base file name' => '', 'Print templates' => '', 'Print templates to use' => '', 'Printdate' => '', + 'Printed' => '', 'Printer' => '', 'Printer Command' => '', 'Printer Description' => '', @@ -3464,6 +3475,7 @@ $self->{texts} = { 'Save and Invoice' => '', 'Save and Invoice for Advance Payment' => '', 'Save and Order' => '', + 'Save and Print Labels for Stock' => '', 'Save and Purchase Delivery Order' => '', 'Save and Purchase Delivery Order with item selection' => '', 'Save and Purchase Order' => '', @@ -3746,6 +3758,7 @@ $self->{texts} = { 'Show »not delivered qty/value« column in sales and purchase orders' => '', 'Signature' => '', 'Since bin is not enforced in the parts data, please specify a bin where goods without a specified bin will be put.' => '', + 'Single' => '', 'Single quotes' => '', 'Single values in item mode, cumulated values in invoice mode' => '', 'Singular' => '', @@ -3832,6 +3845,7 @@ $self->{texts} = { 'Storage Type for generated/imported PDF Documents' => '', 'Storage Type for images' => '', 'Storage Type for shopimages' => '', + 'Store' => '', 'Storing PDF in storage backend failed: #1' => '', 'Storing PDF to webdav folder failed: #1' => '', 'Storing the ZUGFeRD file in the storage backend failed: #1' => '', @@ -4113,6 +4127,7 @@ $self->{texts} = { 'The document has been changed by another user. Please reopen it in another window and copy the changes to the new window' => '', 'The document has been created.' => '', 'The document has been printed.' => '', + 'The document has been sent to the printer \'#1\'.' => '', 'The documents have been sent to the printer \'#1\'.' => '', 'The dunnings have been printed.' => '', 'The email entry for #1 looks invalid' => '', @@ -4202,6 +4217,7 @@ $self->{texts} = { 'The order intake has been deleted' => '', 'The order intake has been saved' => '', 'The package name is invalid.' => '', + 'The part has no stock.' => '', 'The partnumber already exists!' => '', 'The partnumber already exists.' => '', 'The partnumber is already being used' => '', diff --git a/menus/user/00-erp.yaml b/menus/user/00-erp.yaml index f979c48bb5..1fac40192a 100644 --- a/menus/user/00-erp.yaml +++ b/menus/user/00-erp.yaml @@ -96,17 +96,28 @@ action: RequirementSpec/new is_template: 1 - parent: master_data - id: master_data_update_prices + id: master_data_prices + name: Prices + order: 800 +- parent: master_data_prices + id: master_data_prices_update_prices name: Update Prices icon: prices_update - order: 800 + order: 100 access: part_service_assembly_edit & part_service_assembly_edit_prices params: action: PartsPriceUpdate/search_update_prices -- parent: master_data - id: master_data_price_rules +- parent: master_data_prices + id: master_data_prices_printing_part_labels + name: Print Part Labels of Changed Prices + order: 200 + access: part_service_assembly_details + params: + action: Part/search_print_part_labels_of_changed_prices +- parent: master_data_prices + id: master_data_prices_price_rules name: Price Rules - order: 900 + order: 300 access: part_service_assembly_edit params: action: PriceRule/list @@ -555,7 +566,7 @@ order: 400 - parent: warehouse id: warehouse_stock - name: Stock + name: Store order: 100 access: warehouse_management params: diff --git a/sql/Pg-upgrade2/create_part_label_prints.sql b/sql/Pg-upgrade2/create_part_label_prints.sql new file mode 100644 index 0000000000..8167416d53 --- /dev/null +++ b/sql/Pg-upgrade2/create_part_label_prints.sql @@ -0,0 +1,16 @@ +-- @tag: create_part_label_prints +-- @description: Tabelle für automatischen Edikettendruck +-- @depends: release_3_9_1 + +CREATE TYPE part_label_print_types AS ENUM ( + 'single', + 'stock' +); + +CREATE TABLE part_label_prints ( + price_history_id INTEGER REFERENCES parts_price_history(id) NOT NULL, + print_type part_label_print_types NOT NULL, + template TEXT NOT NULL, + + PRIMARY KEY (price_history_id, print_type, template) +); diff --git a/templates/design40_webpages/part/_inventory.html b/templates/design40_webpages/part/_inventory.html index f566a8d114..f43b252e53 100644 --- a/templates/design40_webpages/part/_inventory.html +++ b/templates/design40_webpages/part/_inventory.html @@ -3,7 +3,7 @@ [%- IF AUTH.assert('warehouse_management', 1) -%] diff --git a/templates/design40_webpages/part/_print_part_labels_of_changed_prices_report_bottom.html b/templates/design40_webpages/part/_print_part_labels_of_changed_prices_report_bottom.html new file mode 100644 index 0000000000..a5a81d5d0e --- /dev/null +++ b/templates/design40_webpages/part/_print_part_labels_of_changed_prices_report_bottom.html @@ -0,0 +1,4 @@ +[% USE L %] +[%- L.paginate_controls(models=models) %] + + diff --git a/templates/design40_webpages/part/_print_part_labels_of_changed_prices_report_top.html b/templates/design40_webpages/part/_print_part_labels_of_changed_prices_report_top.html new file mode 100644 index 0000000000..3f36b5e4cb --- /dev/null +++ b/templates/design40_webpages/part/_print_part_labels_of_changed_prices_report_top.html @@ -0,0 +1,33 @@ +[%- USE L %] +[%- USE LxERP %] + +[% BLOCK filter_toggle_panel %] +[%- FILTER_HTML %] +[% END %] + +
+ [% SET display_status = 'open' %] + [% INCLUDE 'common/toggle_panel.html' %] +
+ + + + + +
+
diff --git a/templates/design40_webpages/part/form.html b/templates/design40_webpages/part/form.html index 77475f87f9..54875ab17a 100644 --- a/templates/design40_webpages/part/form.html +++ b/templates/design40_webpages/part/form.html @@ -6,6 +6,25 @@

[% FORM.title %] [% IF SELF.part.id %]: [% HTML.escape(SELF.part.displayable_name) %][% END %]

+
+ + +
[% INCLUDE 'common/flash.html' %] diff --git a/templates/print/RB/part_info.tex b/templates/print/RB/part_info.tex new file mode 100644 index 0000000000..084e926ea2 --- /dev/null +++ b/templates/print/RB/part_info.tex @@ -0,0 +1,221 @@ +% config: use-template-toolkit=1 +% config: tag-style=$( )$ +$( USE KiviLatex )$ +$( USE P )$ +$( USE Dumper )$ +$( USE LxERP )$ +\input{inheaders.tex} +\usepackage{graphicx} + +$( KiviLatex.required_packages_for_html )$ +% Variablen, die in settings verwendet werden +\newcommand{\lxlangcode} {$(template_meta.language.template_code)$} +\newcommand{\lxmedia} {$(media)$} +\newcommand{\lxcurrency} {$(currency)$} +\newcommand{\kivicompany} {$(employee_company)$} + +% settings: Einstellungen, Logo, Briefpapier, Kopfzeile, Fusszeile + +% Sprachüberprüfung +\ifthenelse{\equal{\lxlangcode}{EN}}{\input{english.tex}}{ + \ifthenelse{\equal{\lxlangcode}{DE}}{\input{deutsch.tex}}{\input{deutsch.tex}} +} % Ende EN + + +% Mandanten-/Firmenabhängigkeiten + +% Pfad zu firmenspez. Angaben +% Hat man mehrere Mandanten muß man statt "Firma1" den Datenbanknamen seines +% Mandanten eingeben. + +\IfSubStringInString{Firma1}{\kivicompany}{\newcommand{\identpath}{firma1}}{ + \IfSubStringInString{Firma2}{\kivicompany}{\newcommand{\identpath}{firma2}} + {\newcommand{\identpath}{firma}} % sonst +} % Ende Firma1 + +% Identität +\input{\identpath/ident.tex} + +% Währungen/Konten +\IfSubStringInString{USD}{\lxcurrency}{\input{\identpath/usd_account.tex}}{ + \IfSubStringInString{CHF}{\lxcurrency}{\input{\identpath/chf_account.tex}}{ + \IfSubStringInString{EUR}{\lxcurrency}{\input{\identpath/euro_account.tex}}{\input{\identpath/euro_account.tex}} + } % Ende CHF +} % Ende USD + +% Briefkopf, Logo oder Briefpapier +%% \IfSubStringInString{mail}{\lxmedia}{ % nur bei Mail + % Grafik als Briefkopf + %%\setlength{\wpYoffset}{380pt} % Verschiebung von der Mitte nach oben + %%\setlength{\wpYoffset}{130mm} % Verschiebung von der Mitte nach oben + %%\CenterWallPaper{0.885}{\identpath/briefkopf.png} % mit Skalierung + + % oder nur ein Logo oben rechts + %% \setlength{\wpXoffset}{180pt} % Verschiebung von der Mitte nach rechts + %% \setlength{\wpYoffset}{380pt} % Verschiebung von der Mitte nach oben + %% \CenterWallPaper{0.1}{\identpath/logo.png} % mit Skalierung + + % oder ganzer Briefbogen als Hintergrund + % \CenterWallPaper{1}{\identpath/Briefpapier.pdf} +%% } + + +% keine Absätze nach rechts einrücken +\setlength\parindent{0pt} + +% Papierformat, Ränder, usw. +\geometry{ + a4paper, % DINA4 + %% left=19mm, % Linker Rand + width=182mm, % Textbreite + top=35mm, % Abstand Textanfang von oben + head=44mm, % Höhe des Kopfes + headsep=4mm, % Abstand Kopf zu Textanfang + bottom=30mm, % Abstand von unten + % showframe, % Rahmen zum Debuggen anzeigen +} + + +% Befehl f. normale Schriftart und -größe +%\setmainfont{cmunrm.otf} +\newcommand{\ourfont}{\fontfamily{cmss}\fontsize{10pt}{12pt}\selectfont} +%\newcommand{\ourfont}{\setmainfont +% [ BoldFont={cmunsx.otf}, +% ItalicFont={cmunsi.otf}, +% BoldItalicFont={cmunso.otf}]{cmunss.otf}} + +% Einstellungen f. Kopf und Fuss +\pagestyle{scrheadings} +\clearscrheadfoot +%\setheadwidth[20mm]{page} % Kopfzeile nach rechts verschieben +%\setfootwidth[-39mm]{page} % Fusszeile verschieben + +% Befehl f. laufende Kopfzeile: +% 1. Text f. Kunden- oder Lieferantennummer (oder leer, wenn diese nicht ausgegeben werden soll) +% 2. Kunden- oder Lieferantennummer (oder leer) +% 3. Belegname {oder leer} +% 4. Belegnummer {oder leer} +% 5. Belegdatum {oder leer} +% Beispiel: \ourhead{\kundennummer}{<%customernumber%>}{\angebot}{<%quonumber%>}{<%quodate%>} +\newcommand{\ourhead}[5] { +\chead{ + \ifthenelse{\equal{\thepage}{1}} + {}% then + {\normalfont\fontfamily{cmss}\scriptsize + \ifthenelse{\equal{#1}{}}{}{#1: #2 \hspace{0.7cm}}{} + #3 + \ifthenelse{\equal{#4}{}}{}{~\nr: #4} + \ifthenelse{\equal{#5}{}}{}{\vom ~ #5} + \hspace{0.7cm} - \seite ~ \thepage/\pageref{LastPage} ~- } +}%ende chead +} + +% Firmenfuss +%\cfoot{ +% {\normalfont\fontfamily{cmss} \tiny +% \begin{tabular}{p{5cm}p{4.5cm}lr} +% \firma & \email & \textKontonummer & \kontonummer \\ +% \strasse & \homepage & \textBank & \bank \\ +% \ort & \textUstid\ \ustid & \textIban & \iban \\ +% \textTelefon~\telefon & \finanzamt & \textBic & \bic \\ +% \ifthenelse{\equal{\fax}{}}{}{\textFax~\fax} & &\textBankleitzahl & \bankleitzahl \\ +% \end{tabular} +% } +%} + +\newcolumntype{L}[1]{>{\raggedright\arraybackslash}p{#1}} %Linksbündig mit eigener Breite +\newcolumntype{C}[1]{>{\centering\arraybackslash}p{#1}} % zentriert mit Breitenangabe +\newcolumntype{R}[1]{>{\raggedleft\arraybackslash}p{#1}} % rechtsbündig mit Breitenangabe +\begin{document} +\ourfont +$(# Dumper.dump_html(part.assemblies) )$ +\large \textbf{Artikeldaten}\hfill \today +\vspace*{0.3cm} +\normalsize +\begin{tabular}{L{5cm}L{12cm}} + Art.Nr.: & $( KiviLatex.filter(part.partnumber) )$ \\ + Artikelbeschreibung: & $( KiviLatex.filter(part.description) )$ \\ + Langtext: & $( KiviLatex.filter_html(part.notes) )$ \\ + Gruppe: & $( KiviLatex.filter(part.partsgroup.partsgroup) )$ \\ + Lager - Lagerplatz: & $( KiviLatex.filter(part.warehouse.description) )$ - $( KiviLatex.filter(part.bin.description) )$ \\ + Aktuelle Bestandsmenge: & $( KiviLatex.filter(part.onhand_as_number) )$ $( KiviLatex.filter(part.unit) )$\\ + Buchungsgruppe: & $( KiviLatex.filter(part.buchungsgruppen.description) )$ \\ + %$(IF part.part_type )$ + Artikeltyp: & $( KiviLatex.filter(LxERP.t8(part.part_type)) )$ \\ + %$(END)$ + %$(IF part.classification )$ + Artikelklassifizierung: & $( KiviLatex.filter(LxERP.t8(part.classification.description)) )$ \\ + %$(END)$ + %$(IF part.customm_tariff_number )$ + Zolltarifnummer: & $( KiviLatex.filter(LxERP.t8(part.customm_tariff_number)) )$ \\ + %$(END)$ + %$(IF part.ean )$ + EAN: & $( KiviLatex.filter(LxERP.t8(part.ean)) )$ \\ + %$(END)$ + %$(IF part.microfiche )$ + Microfilm: & $( KiviLatex.filter(LxERP.t8(part.microfiche)) )$ \\ + %$(END)$ + %$(IF part.drawing )$ + Zeichnung: & $( KiviLatex.filter(LxERP.t8(part.drawing)) )$ \\ + %$(END)$ + %$(IF part.weight )$ + Gewicht: & $( KiviLatex.filter(LxERP.t8(part.weight)) )$ \\ + %$(END)$ +\end{tabular} + + $(IF KiviLatex.filter(part.image))$ + \begin{minipage}{2cm} + % \begin{pspicture}(-5cm,2em)(1.5in,0.5in) + \includegraphics[width=7cm ]{$( part.image )$} + % \end{pspicture} + \end{minipage} + $(END)$ + +\begin{tabular}{L{6cm}R{2cm}} + \rowcolor{gray}Preisart & Preis \\ + Listenpreis: & $( KiviLatex.filter(part.listprice_as_number) )$ € \\ + Einkaufspreis: & $( KiviLatex.filter(part.lastcost_as_number) )$ € \\ + Verkaufspreis: & $( KiviLatex.filter(part.sellprice_as_number) )$ €\\ + %$( IF part.prices.size )$ + \rowcolor{gray}\multicolumn{2}{c}{Preisgruppen(nur Verkauf)} \\ + %$( FOREACH price = part.prices )$ + $( KiviLatex.filter(price.pricegroup.pricegroup) )$ & $( KiviLatex.filter(price.price_as_number) )$ €\\ + %$(END)$ + %$(END)$ + %$( IF prules_sell.size )$ + \rowcolor{gray}\multicolumn{2}{c}{Preisregel Verkauf} \\ + %$( FOREACH pricerule_s = prules_sell )$ + $( KiviLatex.filter(pricerule_s.price_rules.name) )$ & $( KiviLatex.filter(pricerule_s.price_rules.price_as_number) )$ €\\ + %$(END)$ + %$(END)$ + %$( IF prules_buy.size )$ + \rowcolor{gray}\multicolumn{2}{c}{Preisregel Einkauf} \\ + %$( FOREACH pricerule_b = prules_buy )$ + $( KiviLatex.filter(pricerule_b.price_rules.name) )$ & $( KiviLatex.filter(pricerule_b.price_rules.price_as_number) )$ €\\ + %$(END)$ + %$(END)$ +\end{tabular} + +%$( IF part.makemodels.size )$ +\begin{tabularx}{\textwidth}{L{6cm}L{3cm}XR{2cm}} + \rowcolor{gray}Lieferant & Art.Nr. & Artikelbeschreibung & Preis \\ + %$( FOREACH vendor = part.makemodels )$ + $( KiviLatex.filter(vendor.maker.name) )$ & $( KiviLatex.filter(vendor.model) )$ & $( KiviLatex.filter(vendor.model_description) )$ & $( KiviLatex.filter(vendor.lastcost_as_number) )$ € \\ + %$( END )$ + \end{tabularx} +%$(END)$ + +%$(IF part.is_assembly)$ + \textbf{Bestandteile} + +%\begin{longtable}{\textwidth}{L{3cm}XR{2cm}} +\setlength\LTleft\parindent % Tabelle beginnt am linken Textrand +\setlength\LTright{0pt} % Tabelle endet am rechten Textrand + \begin{longtable}{@{}p{3cm}p{9.5cm}rp{2cm}@{}} + \rowcolor{gray}Typ - Art.Nr & Artikelbeschreibung & Menge & Lagerplatz \\ + %$( FOREACH assembly = part.assemblies )$ + $( KiviLatex.filter(assembly.part.presenter.typeclass_abbreviation) )$ $( KiviLatex.filter(assembly.part.partnumber) )$ & $( KiviLatex.filter(assembly.part.description) )$ & $( KiviLatex.filter(assembly.qty) )$ $( KiviLatex.filter(assembly.part.unit) )$ & $( KiviLatex.filter(assembly.part.bin.description) )$\\ + %$( END )$ + \end{longtable} +%$( END )$ +\end{document} diff --git a/templates/print/RB/part_label.tex b/templates/print/RB/part_label.tex new file mode 100644 index 0000000000..a82ff8e688 --- /dev/null +++ b/templates/print/RB/part_label.tex @@ -0,0 +1,17 @@ +% config: use-template-toolkit=1 +% config: tag-style=$( )$ +$( USE KiviLatex )$ +$( USE P )$ + +\documentclass[oneside]{scrartcl} +\usepackage{tabularx} +\usepackage{graphicx} +\usepackage[utf8]{inputenc} +%\setlength{\textwidth}{50cm} +%\usepackage[paperwidth=80mm, paperheight=55mm, margin=3mm]{geometry} + +%\setlength{\parindent}{0pt} + +\begin{document} +Art.Nr.:~~ $( KiviLatex.filter(part.partnumber) )$ \\[1em] +\end{document} diff --git a/templates/print/marei/part_info.tex b/templates/print/marei/part_info.tex new file mode 100644 index 0000000000..4ce8534349 --- /dev/null +++ b/templates/print/marei/part_info.tex @@ -0,0 +1,219 @@ +% config: use-template-toolkit=1 +% config: tag-style=$( )$ +$( USE KiviLatex )$ +$( USE P )$ +$( USE Dumper )$ +$( USE LxERP )$ +\documentclass[a4paper]{scrartcl}[10pt] + +% Schriftart, Eingabelayout der Tastatur +\usepackage[utf8]{inputenc} +\usepackage{graphicx} +\usepackage{german} +\usepackage{ifthen} +\usepackage{longtable} +\usepackage{tabularx} +\usepackage{eurosym} +\usepackage{substr} +\usepackage{scrlayer-scrpage} +\usepackage{wallpaper} % Hintergrundbilder +\usepackage{xcolor,colortbl} +\usepackage{lastpage} +\usepackage{geometry} + +$( KiviLatex.required_packages_for_html )$ +% Variablen, die in settings verwendet werden +\newcommand{\lxlangcode} {$(template_meta.language.template_code)$} +\newcommand{\lxmedia} {$(media)$} +\newcommand{\lxcurrency} {$(currency)$} +\newcommand{\kivicompany} {$(employee_company)$} + +% settings: Einstellungen, Logo, Briefpapier, Kopfzeile, Fusszeile + +% Sprachüberprüfung +\ifthenelse{\equal{\lxlangcode}{EN}}{\input{english.tex}}{ + \ifthenelse{\equal{\lxlangcode}{DE}}{\input{deutsch.tex}}{\input{deutsch.tex}} +} % Ende EN + + +% Mandanten-/Firmenabhängigkeiten + +% Pfad zu firmenspez. Angaben +% Hat man mehrere Mandanten muß man statt "Firma1" den Datenbanknamen seines +% Mandanten eingeben. + +\IfSubStringInString{Firma1}{\kivicompany}{\newcommand{\identpath}{firma1}}{ + \IfSubStringInString{Firma2}{\kivicompany}{\newcommand{\identpath}{firma2}} + {\newcommand{\identpath}{firma}} % sonst +} % Ende Firma1 + +% Identität +\input{\identpath/ident.tex} + +% Währungen/Konten +\IfSubStringInString{USD}{\lxcurrency}{\input{\identpath/usd_account.tex}}{ + \IfSubStringInString{CHF}{\lxcurrency}{\input{\identpath/chf_account.tex}}{ + \IfSubStringInString{EUR}{\lxcurrency}{\input{\identpath/euro_account.tex}}{\input{\identpath/euro_account.tex}} + } % Ende CHF +} % Ende USD + + +% keine Absätze nach rechts einrücken +\setlength\parindent{0pt} + +% Papierformat, Ränder, usw. +\geometry{ + a4paper, % DINA4 + %% left=19mm, % Linker Rand + width=182mm, % Textbreite + top=35mm, % Abstand Textanfang von oben + head=44mm, % Höhe des Kopfes + headsep=4mm, % Abstand Kopf zu Textanfang + bottom=30mm, % Abstand von unten + % showframe, % Rahmen zum Debuggen anzeigen +} + + +% Befehl f. normale Schriftart und -größe +%\setmainfont{cmunrm.otf} +\newcommand{\ourfont}{\fontfamily{cmss}\fontsize{10pt}{12pt}\selectfont} +%\newcommand{\ourfont}{\setmainfont +% [ BoldFont={cmunsx.otf}, +% ItalicFont={cmunsi.otf}, +% BoldItalicFont={cmunso.otf}]{cmunss.otf}} + +% Einstellungen f. Kopf und Fuss +\pagestyle{scrheadings} +\clearscrheadfoot +%\setheadwidth[20mm]{page} % Kopfzeile nach rechts verschieben +%\setfootwidth[-39mm]{page} % Fusszeile verschieben + +% Befehl f. laufende Kopfzeile: +% 1. Text f. Kunden- oder Lieferantennummer (oder leer, wenn diese nicht ausgegeben werden soll) +% 2. Kunden- oder Lieferantennummer (oder leer) +% 3. Belegname {oder leer} +% 4. Belegnummer {oder leer} +% 5. Belegdatum {oder leer} +% Beispiel: \ourhead{\kundennummer}{<%customernumber%>}{\angebot}{<%quonumber%>}{<%quodate%>} +\newcommand{\ourhead}[5] { +\chead{ + \ifthenelse{\equal{\thepage}{1}} + {}% then + {\normalfont\fontfamily{cmss}\scriptsize + \ifthenelse{\equal{#1}{}}{}{#1: #2 \hspace{0.7cm}}{} + #3 + \ifthenelse{\equal{#4}{}}{}{~\nr: #4} + \ifthenelse{\equal{#5}{}}{}{\vom ~ #5} + \hspace{0.7cm} - \seite ~ \thepage/\pageref{LastPage} ~- } +}%ende chead +} + +% Firmenfuss +%\cfoot{ +% {\normalfont\fontfamily{cmss} \tiny +% \begin{tabular}{p{5cm}p{4.5cm}lr} +% \firma & \email & \textKontonummer & \kontonummer \\ +% \strasse & \homepage & \textBank & \bank \\ +% \ort & \textUstid\ \ustid & \textIban & \iban \\ +% \textTelefon~\telefon & \finanzamt & \textBic & \bic \\ +% \ifthenelse{\equal{\fax}{}}{}{\textFax~\fax} & &\textBankleitzahl & \bankleitzahl \\ +% \end{tabular} +% } +%} + +\newcolumntype{L}[1]{>{\raggedright\arraybackslash}p{#1}} %Linksbündig mit eigener Breite +\newcolumntype{C}[1]{>{\centering\arraybackslash}p{#1}} % zentriert mit Breitenangabe +\newcolumntype{R}[1]{>{\raggedleft\arraybackslash}p{#1}} % rechtsbündig mit Breitenangabe +\begin{document} +\ourfont +$(# Dumper.dump_html(part.assemblies) )$ +\large \textbf{Artikeldaten}\hfill \today +\vspace*{0.3cm} +\normalsize +\begin{tabular}{L{5cm}L{12cm}} + Art.Nr.: & $( KiviLatex.filter(part.partnumber) )$ \\ + Artikelbeschreibung: & $( KiviLatex.filter(part.description) )$ \\ + Langtext: & $( KiviLatex.filter_html(part.notes) )$ \\ + Gruppe: & $( KiviLatex.filter(part.partsgroup.partsgroup) )$ \\ + Lager - Lagerplatz: & $( KiviLatex.filter(part.warehouse.description) )$ - $( KiviLatex.filter(part.bin.description) )$ \\ + Aktuelle Bestandsmenge: & $( KiviLatex.filter(part.onhand_as_number) )$ $( KiviLatex.filter(part.unit) )$\\ + Buchungsgruppe: & $( KiviLatex.filter(part.buchungsgruppen.description) )$ \\ + %$(IF part.part_type )$ + Artikeltyp: & $( KiviLatex.filter(LxERP.t8(part.part_type)) )$ \\ + %$(END)$ + %$(IF part.classification )$ + Artikelklassifizierung: & $( KiviLatex.filter(LxERP.t8(part.classification.description)) )$ \\ + %$(END)$ + %$(IF part.customm_tariff_number )$ + Zolltarifnummer: & $( KiviLatex.filter(LxERP.t8(part.customm_tariff_number)) )$ \\ + %$(END)$ + %$(IF part.ean )$ + EAN: & $( KiviLatex.filter(LxERP.t8(part.ean)) )$ \\ + %$(END)$ + %$(IF part.microfiche )$ + Microfilm: & $( KiviLatex.filter(LxERP.t8(part.microfiche)) )$ \\ + %$(END)$ + %$(IF part.drawing )$ + Zeichnung: & $( KiviLatex.filter(LxERP.t8(part.drawing)) )$ \\ + %$(END)$ + %$(IF part.weight )$ + Gewicht: & $( KiviLatex.filter(LxERP.t8(part.weight)) )$ \\ + %$(END)$ +\end{tabular} + + $(IF KiviLatex.filter(part.image))$ + \begin{minipage}{2cm} + % \begin{pspicture}(-5cm,2em)(1.5in,0.5in) + \includegraphics[width=7cm ]{../$( part.image )$} + % \end{pspicture} + \end{minipage} + $(END)$ + +\begin{tabular}{L{6cm}R{2cm}} + \rowcolor{gray}Preisart & Preis \\ + Listenpreis: & $( KiviLatex.filter(part.listprice_as_number) )$ € \\ + Einkaufspreis: & $( KiviLatex.filter(part.lastcost_as_number) )$ € \\ + Verkaufspreis: & $( KiviLatex.filter(part.sellprice_as_number) )$ €\\ + %$( IF part.prices.size )$ + \rowcolor{gray}\multicolumn{2}{c}{Preisgruppen(nur Verkauf)} \\ + %$( FOREACH price = part.prices )$ + $( KiviLatex.filter(price.pricegroup.pricegroup) )$ & $( KiviLatex.filter(price.price_as_number) )$ €\\ + %$(END)$ + %$(END)$ + %$( IF prules_sell.size )$ + \rowcolor{gray}\multicolumn{2}{c}{Preisregel Verkauf} \\ + %$( FOREACH pricerule_s = prules_sell )$ + $( KiviLatex.filter(pricerule_s.price_rules.name) )$ & $( KiviLatex.filter(pricerule_s.price_rules.price_as_number) )$ €\\ + %$(END)$ + %$(END)$ + %$( IF prules_buy.size )$ + \rowcolor{gray}\multicolumn{2}{c}{Preisregel Einkauf} \\ + %$( FOREACH pricerule_b = prules_buy )$ + $( KiviLatex.filter(pricerule_b.price_rules.name) )$ & $( KiviLatex.filter(pricerule_b.price_rules.price_as_number) )$ €\\ + %$(END)$ + %$(END)$ +\end{tabular} + +%$( IF part.makemodels.size )$ +\begin{tabularx}{\textwidth}{L{6cm}L{3cm}XR{2cm}} + \rowcolor{gray}Lieferant & Art.Nr. & Artikelbeschreibung & Preis \\ + %$( FOREACH vendor = part.makemodels )$ + $( KiviLatex.filter(vendor.maker.name) )$ & $( KiviLatex.filter(vendor.model) )$ & $( KiviLatex.filter(vendor.model_description) )$ & $( KiviLatex.filter(vendor.lastcost_as_number) )$ € \\ + %$( END )$ + \end{tabularx} +%$(END)$ + +%$(IF part.is_assembly)$ + \textbf{Bestandteile} + +%\begin{longtable}{\textwidth}{L{3cm}XR{2cm}} +\setlength\LTleft\parindent % Tabelle beginnt am linken Textrand +\setlength\LTright{0pt} % Tabelle endet am rechten Textrand + \begin{longtable}{@{}p{3cm}p{9.5cm}rp{2cm}@{}} + \rowcolor{gray}Typ - Art.Nr & Artikelbeschreibung & Menge & Lagerplatz \\ + %$( FOREACH assembly = part.assemblies )$ + $( KiviLatex.filter(assembly.part.presenter.typeclass_abbreviation) )$ $( KiviLatex.filter(assembly.part.partnumber) )$ & $( KiviLatex.filter(assembly.part.description) )$ & $( KiviLatex.filter(assembly.qty) )$ $( KiviLatex.filter(assembly.part.unit) )$ & $( KiviLatex.filter(assembly.part.bin.description) )$\\ + %$( END )$ + \end{longtable} +%$( END )$ +\end{document} diff --git a/templates/print/marei/part_label.tex b/templates/print/marei/part_label.tex new file mode 100644 index 0000000000..4f09ec4c55 --- /dev/null +++ b/templates/print/marei/part_label.tex @@ -0,0 +1,84 @@ +% config: use-template-toolkit=1 +% config: tag-style=$( )$ +$( USE KiviLatex )$ +$( USE P )$ + +\documentclass[parskip]{scrartcl} +\usepackage{scrlayer-scrpage} +\usepackage{geometry} +\usepackage{pst-barcode} +\usepackage{graphicx} +\usepackage{tikz} +\usepackage{fontspec} +\geometry{ + paperwidth=101mm, + paperheight=54mm, + margin=6mm, + %showframe, % Rahmen zum Debuggen anzeigen +} + +$(IF KiviLatex.filter(part.image))$ + \DeclareNewLayer[ + background,hoffset=7cm,voffset=1.7cm, + contents={\resizebox{2cm}{3cm} + { + \includegraphics[width=2cm ]{../$( part.image )$} + } + } + ]{bild} +$( ELSE )$ + \DeclareNewLayer[ + background, + contents={ + { + } + } + ]{bild} +$( END )$ + +\DeclareNewLayer[ + background,hoffset=4mm,voffset=3.7cm, + contents={%\resizebox{1.5in}{0.5in} + { + \begin{pspicture}(0,2em)(1.5in,0.5in) + \psbarcode{$(KiviLatex.filter(part.partnumber))$}{width=1.5 height=0.5}{code128} + \end{pspicture} + } + } +]{barcode} + +% Befehl f. normale Schriftart und -größe +\setmainfont{cmunrm.otf} +\newcommand{\ourfont}{\setmainfont + [ BoldFont={cmunsx.otf}, + ItalicFont={cmunsi.otf}, + BoldItalicFont={cmunso.otf} + ]{cmunss.otf} + } + +\DeclarePageStyleByLayers{label}{bild,barcode} + +\pagestyle{label} + +\begin{document} +\setlength{\parskip}{0pt} % 1ex plus 0.5ex minus 0.2ex} +\setlength{\parindent}{0pt} + \ourfont + +\Huge \textbf{Artnr.:~ $(KiviLatex.filter(part.partnumber))$ } + + \large\textbf{$(KiviLatex.filter(part.description))$} + \normalsize + + $( IF part.bin.description )$ + Lagerplatz: $(KiviLatex.filter(part.bin.description))$ + $( END )$ + + $( IF part.drawing )$ + Zeichnungsnr: $(KiviLatex.filter(part.drawing))$ + $( END )$ + +% $( # IF part.cvar_by_name('dbwin_gruppe').value() )$ +% Verwendungszweck: $( # KiviLatex.filter(part.cvar_by_name('dbwin_gruppe').value()) )$ +% $( # END )$ +\end{document} diff --git a/templates/webpages/part/_inventory.html b/templates/webpages/part/_inventory.html index 8b48b18ba4..32c5043e9a 100644 --- a/templates/webpages/part/_inventory.html +++ b/templates/webpages/part/_inventory.html @@ -3,7 +3,7 @@ [%- IF AUTH.assert('warehouse_management', 1) -%]

[% 'Actions' | $T8 %]: - [% 'Stock' | $T8 %] + [% 'Store' | $T8 %] [% 'Transfer' | $T8 %] [% 'Removal' | $T8 %]

diff --git a/templates/webpages/part/form.html b/templates/webpages/part/form.html index 05e73f34c0..512ff27646 100644 --- a/templates/webpages/part/form.html +++ b/templates/webpages/part/form.html @@ -7,6 +7,15 @@

[% FORM.title %] [% IF SELF.part.id %]: [% HTML.escape(SELF.part.displayable [% INCLUDE 'common/flash.html' %] + +
[% L.hidden_tag('part.part_type' , SELF.part.part_type) %]