Neue Maske: Auftragsartikelsuche
authorG. Richardson <information@kivitendo-premium.de>
Tue, 9 Aug 2016 10:12:36 +0000 (12:12 +0200)
committerG. Richardson <information@kivitendo-premium.de>
Mon, 17 Oct 2016 16:05:08 +0000 (18:05 +0200)
um schnell Positionen aus (alten) Verkaufsaufträgen zu finden:

Verkauf -> Berichte -> Auftragsartikelsuche

Dies ist kein druckbarer Bericht, sondern soll helfen, schnell einen
bestimmten Auftrag oder eine Information zu einer bestimmten verkauften
Ware zu finden.

Wurde die Ware per Lieferschein verschickt und ausgelagert wird auch der
Lieferschein und die verschickte Menge angezeigt. Dies klappt aber nur
für Aufträge, wo die Einzelpositionen per RecordLinks verknüpft sind.

SL/Controller/OrderItem.pm [new file with mode: 0644]
css/common.css
doc/changelog
locale/de/all
menus/user/00-erp.yaml
templates/webpages/order_items_search/_order_item_list.html [new file with mode: 0644]
templates/webpages/order_items_search/order_items.html [new file with mode: 0644]

diff --git a/SL/Controller/OrderItem.pm b/SL/Controller/OrderItem.pm
new file mode 100644 (file)
index 0000000..7cdfb35
--- /dev/null
@@ -0,0 +1,125 @@
+package SL::Controller::OrderItem;
+
+use strict;
+
+use parent qw(SL::Controller::Base);
+use SL::DB::Order;
+use SL::DB::OrderItem;
+use SL::DB::Customer;
+use SL::DB::Part;
+use SL::Controller::Helper::GetModels;
+use SL::Controller::Helper::ParseFilter;
+use SL::Locale::String qw(t8);
+
+__PACKAGE__->run_before('check_auth');
+
+use Rose::Object::MakeMethods::Generic (
+  'scalar'                => [ qw(orderitems) ],
+  'scalar --get_set_init' => [ qw(model) ],
+);
+
+my %sort_columns = (
+  partnumber        => t8('Part Number'),
+  ordnumber         => t8('Order'),
+  customer          => t8('Customer'),
+  transdate         => t8('Date'),
+);
+
+sub action_search {
+
+  my ($self, %params) = @_;
+
+  my $title = t8("Sold order items");
+
+  $::request->layout->use_javascript('client_js.js');
+
+  # The actual loading of orderitems happens in action_order_item_list_dynamic_table
+  # which is processed inside this template and automatically called upon
+  # loading. This causes all filtered orderitems to be displayed,
+  # there is no paginate mechanism or export
+  $self->render('order_items_search/order_items', { layout => 1, process => 1 },
+                                                  title         => $title,
+               );
+}
+
+
+sub action_order_item_list_dynamic_table {
+  my ($self) = @_;
+
+  $self->orderitems( $self->model->get );
+
+
+  $self->add_linked_delivery_order_items;
+
+  $self->render('order_items_search/_order_item_list', { layout  => 0 , process => 1 });
+}
+
+sub add_linked_delivery_order_items {
+  my ($self) = @_;
+
+  my $qty_round = 2;
+
+  foreach my $orderitem ( @{ $self->orderitems } ) {
+    my $dois = $orderitem->linked_delivery_order_items;
+    $orderitem->{deliveryorders} = join('<br>', map { $_->displayable_delivery_order_info($qty_round) } @{$dois});
+  };
+};
+
+sub init_model {
+  my ($self) = @_;
+
+  SL::Controller::Helper::GetModels->new(
+    controller => $self,
+    model      => 'OrderItem',
+    query      => [ SL::DB::Manager::Order->type_filter('sales_order') ],
+    sorted       => {
+      _default     => {
+        by           => 'transdate',
+        dir          => 0,
+      },
+      %sort_columns,
+    } ,
+    with_objects    => [ 'order', 'order.customer', 'part' ],
+  );
+}
+
+sub check_auth {
+  $::auth->assert('sales_order_edit');
+}
+
+1;
+
+__END__
+
+=encoding utf-8
+
+=head1 NAME
+
+SL::Controller::OrderItem - Controller for OrderItems
+
+=head2 OVERVIEW
+
+Controller for quickly finding orderitems in sales orders. For example the
+customer phones you, saying he would like to order another one of the green
+thingies he ordered 2 years ago. You have no idea what he is referring to, but
+you can quickly filter by customer (a customerpicker) and e.g. part description
+or partnumber or order date, successively narrowing down the search. The
+resulting list is updated dynamically after keypresses.
+
+=head1 Usage
+
+Certain fields can be preset by passing them as get parameters in the URL, so
+you could create links to this report:
+
+ controller.pl?action=OrderItem/search&ordnumber=24
+ controller.pl?action=OrderItem/search&customer_id=3455
+
+=head1 TODO AND CAVEATS
+
+=over 4
+
+=item * amount of results is limited
+
+=back
+
+=cut
index fe764b8..8893580 100644 (file)
@@ -72,3 +72,6 @@ input.grow_on_focus:focus { width: 150px }
 #dunning_invoice_list .direct_debit a {
   color: #aaa;
 }
+/* orderitems */
+.shipped     { color: green }
+.not_shipped { color: red   }
index b0e70fb..2b77b59 100644 (file)
@@ -41,6 +41,10 @@ kleinere neue Features und Detailverbesserungen:
   - Neuer Controller für Preisgruppen, die nun sortiert und ungültig gesetzt
     werden können.
 
+  - Neuer Berichte "Auftragsartikelsuche", um schnell Autragspositionen aus
+    Verkaufsauträge finden zu können:
+    Verkauf -> Berichte -> Auftragsartikelsuche
+
 Administrative Änderungen
 
   - Diverse Textsuchen werden jetzt durch eine neue Klasse Indizes
index 3dfa039..c91d6be 100755 (executable)
@@ -1942,6 +1942,7 @@ $self->{texts} = {
   'Order Number missing!'       => 'Auftragsnummer fehlt!',
   'Order amount'                => 'Auftragswert',
   'Order deleted!'              => 'Auftrag gelöscht!',
+  'Order item search'           => 'Auftragsartikelsuche',
   'Order probability'           => 'Auftragswahrscheinlichkeit',
   'Order probability & expected billing date' => 'Auftragswahrscheinlichkeit & vorrauss. Abrechnungsdatum',
   'Order value periodicity'     => 'Auftragswert basiert auf Periodizität',
@@ -2570,6 +2571,7 @@ $self->{texts} = {
   'Show follow ups...'          => 'Zeige Wiedervorlagen...',
   'Show help text'              => 'Hilfetext anzeigen',
   'Show history'                => 'Verlauf anzeigen',
+  'Show images'                 => 'Bilder zeigen',
   'Show items from invoices individually' => 'Artikel aus Rechnungen anzeigen',
   'Show mappings (csv_import)'  => 'Spaltenzuordnungen anzeigen',
   'Show old dunnings'           => 'Alte Mahnungen anzeigen',
@@ -2607,6 +2609,7 @@ $self->{texts} = {
   'Skonto information'          => 'Skonto Information',
   'So far you could use one partnumber for severel parts, for example a service and an article.' => 'Bisher war es möglich eine Artikelnummer für mehrere Artikel zu verwenden, zum Beispiel eine Artikelnummer für eine Dienstleistung, eine Ware und ein Erzeugnis.',
   'Sold'                        => 'Verkauft',
+  'Sold order items'            => 'Verkaufte Auftragsartikel',
   'Soldtotal does not make sense without any bsooqr options' => 'Option "Menge in gewählten Belegen" ohne gewählte Belege wird ignoriert.',
   'Solution'                    => 'Lösung',
   'Sort By'                     => 'Sortiert nach',
index 5f2af65..5ef1f7f 100644 (file)
   module: dn.pl
   params:
     action: search
+- parent: ar_reports
+  id: ar_order_item_search
+  name: Order item search
+  order: 750
+  access: sales_order_edit
+  params:
+    action: OrderItem/search
 - parent: ar_reports
   id: ar_reports_delivery_plan
   name: Delivery Plan
diff --git a/templates/webpages/order_items_search/_order_item_list.html b/templates/webpages/order_items_search/_order_item_list.html
new file mode 100644 (file)
index 0000000..3a66857
--- /dev/null
@@ -0,0 +1,40 @@
+[%- USE LxERP %]
+[%- USE T8 %]
+[%- USE L %]
+[%- USE HTML %]
+[%- USE P %]
+[% SET qty_round = 2 %]
+<table cellpadding="3px">
+ <tr class="listheading">
+  <th>[%- LxERP.t8("Part")           %]</th>
+  <th>[%- LxERP.t8("Customer")       %]</th>
+  <th>[%- LxERP.t8("Order")          %]</th>
+  <th>[%- LxERP.t8("Transdate")      %]</th>
+  <th>[%- LxERP.t8("Qty")            %]</th>
+  <th>[%- LxERP.t8("Delivered")      %]</th>
+  <th>[%- LxERP.t8("Price")          %]</th>
+  <th>[%- LxERP.t8("Discount")       %] %</th>
+  <th>[%- LxERP.t8("Delivery Order") %]</th>
+  [% IF FORM.show_images %]
+  <th>[%- LxERP.t8("Image")          %]</th>
+  [% END %]
+ </tr>
+ [% FOREACH order_item = SELF.orderitems %]
+ <tr id="tr_[% loop.count %]" class="listrow[% loop.count % 2 %]">
+  <td>                 [% P.part(order_item.part, no_link => 0)               %]</td>
+  <td>                 [% P.customer(order_item.order.customer, no_link => 0) %]</td>
+  <td class="numeric"> [% P.sales_order(order_item.order, no_link => 0)       %]</td>
+  <td>                 [% order_item.order.transdate.to_kivitendo             %]</td>
+  <td class="numeric [% IF order_item.delivered_qty == order_item.qty %]shipped[% ELSE %]not_shipped[% END %]">
+    [% LxERP.format_amount(order_item.qty, qty_round) %] [% order_item.unit | html %]
+  </td>
+  <td class="numeric"> [% LxERP.format_amount(order_item.delivered_qty, qty_round) %] [% order_item.unit | html %] </td>
+  <td class="numeric"> [% order_item.sellprice_as_number                      %]</td>
+  <td class="numeric"> [% order_item.discount_as_percent                      %]</td>
+  <td>                 [% order_item.deliveryorders                           %]</td>
+  [% IF FORM.show_images %]
+  <td> [% IF order_item.part.image %]<a href="[% order_item.part.image | html %]" target="_blank"><img height="32" border="0" src="[% order_item.part.image | html %]"/></a>[% END %]</td>
+  [% END %]
+ </tr>
+ [% END %]
+</table>
diff --git a/templates/webpages/order_items_search/order_items.html b/templates/webpages/order_items_search/order_items.html
new file mode 100644 (file)
index 0000000..8bcfa92
--- /dev/null
@@ -0,0 +1,92 @@
+[% USE HTML %]
+[%- USE LxERP %]
+[%- USE T8 %]
+[%- USE L %]
+
+[% SET size=50 %]
+[% SET show_images=0 %]
+
+<h1>[% title %]</h1>
+<div style="padding-bottom: 15px">
+[% 'Filter' | $T8 %]:
+<form id="filter" name="filter" method="post" action="controller.pl">
+ <table>
+  </tr>
+    <td>[% 'Customer' | $T8 %]</td>
+    <td>[% L.customer_vendor_picker('filter.order.customer.id', FORM.customer_id, type='customer', class="filter", size=size) %]</td>
+  </tr>
+  <tr>
+    <td>[% 'Part' | $T8 %]</td>
+    <td>[% L.input_tag('filter.part.all:substr:multi::ilike', FORM.part, size = size, class="filter") %]</td>
+  </tr>
+  <tr>
+    <td>[% 'Order Number' | $T8 %]</td>
+    <td>[% L.input_tag('filter.order.ordnumber:substr::ilike', FORM.ordnumber, size = 10, class="filter") %]</td>
+  <tr>
+  <tr>
+    <td>[% 'Order Date' | $T8 %]</td>
+    <td>[% 'From' | $T8 %] [% L.date_tag("filter.order.transdate:date::ge", filter.order.transdate_date___ge, class="filter") %] [% 'Until' | $T8 %] [% L.date_tag('filter.order.transdate:date::le', filter.order.transdate_date__le, class="filter") %]</td>
+  <tr>
+  <tr>
+    <td>[% 'Description' | $T8 %]</td>
+    <td>[% L.input_tag('filter.description:substr::ilike', filter.description_substr__ilike, size = size, class="filter") %]</td>
+  </tr>
+  <tr>
+    <td>[% 'Long Description' | $T8 %]</td>
+    <td>[% L.input_tag('filter.longdescription:substr::ilike', filter.longdescription_substr__ilike, size = size, class="filter") %]  </tr>
+  <tr>
+    <td>[% 'Show images' | $T8 %]</td>
+    <td>[% L.checkbox_tag('show_images', checked=show_images) %]  </tr>
+  </tr>
+</table>
+[% L.button_tag("this.form.reset(); refresh_plot();", LxERP.t8("Reset")) %]
+</form>
+
+<div id="orderitems" style="padding-top: 20px">
+[% PROCESS 'order_items_search/_order_item_list.html' %]
+</div>
+
+
+<script type="text/javascript">
+  $(function() {
+    [% IF FORM.customer_id %]
+      $( "#filter_part_all_substr_multi_ilike" ).focus();
+    [% ELSE %]
+      $( "#filter_order_customer_id_name" ).focus();
+    [% END %]
+
+    addInputCallback($(".filter"), refresh_plot , 300 );
+
+    $('#show_images').change(function(){
+      refresh_plot();
+    });
+  });
+
+
+  function refresh_plot() {
+    var filterdata = $('#filter').serialize()
+    var url = './controller.pl?action=OrderItem/order_item_list_dynamic_table&' + filterdata;
+    $.ajax({
+        url : url,
+        type: 'POST',
+        success: function(data){
+            $('#orderitems').html(data);
+        }
+    })
+
+  };
+
+function addInputCallback(inputfield, callback, delay) {
+    var timer = null;
+    inputfield.on('keyup', function() {
+        if (timer) {
+            window.clearTimeout(timer);
+        }
+        timer = window.setTimeout( function() {
+            timer = null;
+            callback();
+        }, delay );
+    });
+    inputfield = null;
+}
+</script>