--- /dev/null
+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
#dunning_invoice_list .direct_debit a {
color: #aaa;
}
+/* orderitems */
+.shipped { color: green }
+.not_shipped { color: red }
- 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
'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',
'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',
'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',
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
--- /dev/null
+[%- 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>
--- /dev/null
+[% 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>