diff --git a/SL/Controller/Part.pm b/SL/Controller/Part.pm index 6674ca8f31..aeb933054c 100644 --- a/SL/Controller/Part.pm +++ b/SL/Controller/Part.pm @@ -22,6 +22,7 @@ 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; @@ -34,7 +35,9 @@ 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 @@ -138,6 +141,25 @@ sub action_save_and_print { $self->js_reset_part_after_save(); $self->js->flash('info', t8('The item has been saved.')); + $self->print_part($self->part); + + $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->js->render(); +} + +sub print_part { + my ($self, $part) = @_; + my $formname = $::form->{print_options}->{formname}; my $format = $::form->{print_options}->{format}; my $media = $::form->{print_options}->{media}; @@ -147,9 +169,10 @@ sub action_save_and_print { $language = SL::DB::Manager::Language->find_by( id => $::form->{print_options}->{language_id} ); + } my $copies; if ($::form->{print_options}->{part_labels_for_stock}) { - $copies = $self->part->stockqty; + $copies = $part->stockqty; } else { $copies = $::form->{print_options}->{copies}; } @@ -178,13 +201,13 @@ sub action_save_and_print { } my $print_form = Form->new(''); - $print_form->{part} = $self->part; + $print_form->{part} = $part; # for doc_filename $print_form->{type} = 'part'; $print_form->{formname} = $formname; $print_form->{format} = $format; - $print_form->{partnumber} = $self->part->partnumber; + $print_form->{partnumber} = $part->partnumber; $print_form->{language} = $language && ("_" . ($language->template_code || $language->description) ); @@ -244,8 +267,6 @@ sub action_save_and_print { ->flash('error', t8("Creating the PDF failed!")) ->flash('error', $@); }; - - $self->js->render(); } sub action_save_and_purchase_order { @@ -915,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 @@ -1437,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) ], ); @@ -2025,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__ 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/js/kivi.Part.js b/js/kivi.Part.js index 0fe7911849..ab144cd84e 100644 --- a/js/kivi.Part.js +++ b/js/kivi.Part.js @@ -50,16 +50,26 @@ namespace('kivi.Part', function(ns) { $.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('Part Labels for Stock Print options'), + 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'); @@ -71,6 +81,16 @@ namespace('kivi.Part', function(ns) { $.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() { var data = $('#ic').serializeArray(); data.push({ name: 'action', value: 'Part/delete' }); 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 76bc3ba023..8fc76b228e 100755 --- a/locale/de/all +++ b/locale/de/all @@ -2837,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', @@ -3015,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', @@ -3043,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', @@ -3051,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', @@ -3468,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', @@ -3750,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', @@ -3817,6 +3827,7 @@ $ ./scripts/installation_check.pl', 'Step 2' => 'Schritt 2', 'Step 2 -- Watch status' => 'Schritt 2 -- Status beobachten', 'Steuersatz' => 'Steuersatz', + 'Stock' => 'Bestand', 'Stock Local/Shop' => 'Bestand Lokal/Online', 'Stock Qty for Date' => 'Lagerbestand am', 'Stock for part #1' => 'Bestand für Artikel #1', @@ -4208,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 b7fedda561..f8dc3ac7ef 100755 --- a/locale/en/all +++ b/locale/en/all @@ -2836,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' => '', @@ -3014,6 +3015,7 @@ $self->{texts} = { 'Previous transnumber text' => '', 'Price' => '', 'Price #1' => '', + 'Price Change Printed' => '', 'Price Factor' => '', 'Price Factors' => '', 'Price List' => '', @@ -3042,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' => '', @@ -3050,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' => '', @@ -3467,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' => '', @@ -3749,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' => '', @@ -3816,6 +3826,7 @@ $self->{texts} = { 'Step 2' => '', 'Step 2 -- Watch status' => '', 'Steuersatz' => '', + 'Stock' => '', 'Stock Local/Shop' => '', 'Stock Qty for Date' => '', 'Stock for part #1' => '', @@ -4206,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 6e93556b01..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 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 %] + +