Revision d4557a8b
Von Kivitendo Admin vor etwa 8 Jahren hinzugefügt
SL/Controller/OrderItem.pm | ||
---|---|---|
1 |
package SL::Controller::OrderItem; |
|
2 |
|
|
3 |
use strict; |
|
4 |
|
|
5 |
use parent qw(SL::Controller::Base); |
|
6 |
use SL::DB::Order; |
|
7 |
use SL::DB::OrderItem; |
|
8 |
use SL::DB::Customer; |
|
9 |
use SL::DB::Part; |
|
10 |
use SL::Controller::Helper::GetModels; |
|
11 |
use SL::Controller::Helper::ParseFilter; |
|
12 |
use SL::Locale::String qw(t8); |
|
13 |
|
|
14 |
__PACKAGE__->run_before('check_auth'); |
|
15 |
|
|
16 |
use Rose::Object::MakeMethods::Generic ( |
|
17 |
'scalar' => [ qw(orderitems) ], |
|
18 |
'scalar --get_set_init' => [ qw(model) ], |
|
19 |
); |
|
20 |
|
|
21 |
my %sort_columns = ( |
|
22 |
partnumber => t8('Part Number'), |
|
23 |
ordnumber => t8('Order'), |
|
24 |
customer => t8('Customer'), |
|
25 |
transdate => t8('Date'), |
|
26 |
); |
|
27 |
|
|
28 |
sub action_search { |
|
29 |
|
|
30 |
my ($self, %params) = @_; |
|
31 |
|
|
32 |
my $title = t8("Sold order items"); |
|
33 |
|
|
34 |
$::request->layout->use_javascript('client_js.js'); |
|
35 |
|
|
36 |
# The actual loading of orderitems happens in action_order_item_list_dynamic_table |
|
37 |
# which is processed inside this template and automatically called upon |
|
38 |
# loading. This causes all filtered orderitems to be displayed, |
|
39 |
# there is no paginate mechanism or export |
|
40 |
$self->render('order_items_search/order_items', { layout => 1, process => 1 }, |
|
41 |
title => $title, |
|
42 |
); |
|
43 |
} |
|
44 |
|
|
45 |
|
|
46 |
sub action_order_item_list_dynamic_table { |
|
47 |
my ($self) = @_; |
|
48 |
|
|
49 |
$self->orderitems( $self->model->get ); |
|
50 |
|
|
51 |
|
|
52 |
$self->add_linked_delivery_order_items; |
|
53 |
|
|
54 |
$self->render('order_items_search/_order_item_list', { layout => 0 , process => 1 }); |
|
55 |
} |
|
56 |
|
|
57 |
sub add_linked_delivery_order_items { |
|
58 |
my ($self) = @_; |
|
59 |
|
|
60 |
my $qty_round = 2; |
|
61 |
|
|
62 |
foreach my $orderitem ( @{ $self->orderitems } ) { |
|
63 |
my $dois = $orderitem->linked_delivery_order_items; |
|
64 |
$orderitem->{deliveryorders} = join('<br>', map { $_->displayable_delivery_order_info($qty_round) } @{$dois}); |
|
65 |
}; |
|
66 |
}; |
|
67 |
|
|
68 |
sub init_model { |
|
69 |
my ($self) = @_; |
|
70 |
|
|
71 |
SL::Controller::Helper::GetModels->new( |
|
72 |
controller => $self, |
|
73 |
model => 'OrderItem', |
|
74 |
query => [ SL::DB::Manager::Order->type_filter('sales_order') ], |
|
75 |
sorted => { |
|
76 |
_default => { |
|
77 |
by => 'transdate', |
|
78 |
dir => 0, |
|
79 |
}, |
|
80 |
%sort_columns, |
|
81 |
} , |
|
82 |
with_objects => [ 'order', 'order.customer', 'part' ], |
|
83 |
); |
|
84 |
} |
|
85 |
|
|
86 |
sub check_auth { |
|
87 |
$::auth->assert('sales_order_edit'); |
|
88 |
} |
|
89 |
|
|
90 |
1; |
|
91 |
|
|
92 |
__END__ |
|
93 |
|
|
94 |
=encoding utf-8 |
|
95 |
|
|
96 |
=head1 NAME |
|
97 |
|
|
98 |
SL::Controller::OrderItem - Controller for OrderItems |
|
99 |
|
|
100 |
=head2 OVERVIEW |
|
101 |
|
|
102 |
Controller for quickly finding orderitems in sales orders. For example the |
|
103 |
customer phones you, saying he would like to order another one of the green |
|
104 |
thingies he ordered 2 years ago. You have no idea what he is referring to, but |
|
105 |
you can quickly filter by customer (a customerpicker) and e.g. part description |
|
106 |
or partnumber or order date, successively narrowing down the search. The |
|
107 |
resulting list is updated dynamically after keypresses. |
|
108 |
|
|
109 |
=head1 Usage |
|
110 |
|
|
111 |
Certain fields can be preset by passing them as get parameters in the URL, so |
|
112 |
you could create links to this report: |
|
113 |
|
|
114 |
controller.pl?action=OrderItem/search&ordnumber=24 |
|
115 |
controller.pl?action=OrderItem/search&customer_id=3455 |
|
116 |
|
|
117 |
=head1 TODO AND CAVEATS |
|
118 |
|
|
119 |
=over 4 |
|
120 |
|
|
121 |
=item * amount of results is limited |
|
122 |
|
|
123 |
=back |
|
124 |
|
|
125 |
=cut |
css/common.css | ||
---|---|---|
72 | 72 |
#dunning_invoice_list .direct_debit a { |
73 | 73 |
color: #aaa; |
74 | 74 |
} |
75 |
/* orderitems */ |
|
76 |
.shipped { color: green } |
|
77 |
.not_shipped { color: red } |
doc/changelog | ||
---|---|---|
41 | 41 |
- Neuer Controller für Preisgruppen, die nun sortiert und ungültig gesetzt |
42 | 42 |
werden können. |
43 | 43 |
|
44 |
- Neuer Berichte "Auftragsartikelsuche", um schnell Autragspositionen aus |
|
45 |
Verkaufsauträge finden zu können: |
|
46 |
Verkauf -> Berichte -> Auftragsartikelsuche |
|
47 |
|
|
44 | 48 |
Administrative Änderungen |
45 | 49 |
|
46 | 50 |
- Diverse Textsuchen werden jetzt durch eine neue Klasse Indizes |
locale/de/all | ||
---|---|---|
1942 | 1942 |
'Order Number missing!' => 'Auftragsnummer fehlt!', |
1943 | 1943 |
'Order amount' => 'Auftragswert', |
1944 | 1944 |
'Order deleted!' => 'Auftrag gelöscht!', |
1945 |
'Order item search' => 'Auftragsartikelsuche', |
|
1945 | 1946 |
'Order probability' => 'Auftragswahrscheinlichkeit', |
1946 | 1947 |
'Order probability & expected billing date' => 'Auftragswahrscheinlichkeit & vorrauss. Abrechnungsdatum', |
1947 | 1948 |
'Order value periodicity' => 'Auftragswert basiert auf Periodizität', |
... | ... | |
2570 | 2571 |
'Show follow ups...' => 'Zeige Wiedervorlagen...', |
2571 | 2572 |
'Show help text' => 'Hilfetext anzeigen', |
2572 | 2573 |
'Show history' => 'Verlauf anzeigen', |
2574 |
'Show images' => 'Bilder zeigen', |
|
2573 | 2575 |
'Show items from invoices individually' => 'Artikel aus Rechnungen anzeigen', |
2574 | 2576 |
'Show mappings (csv_import)' => 'Spaltenzuordnungen anzeigen', |
2575 | 2577 |
'Show old dunnings' => 'Alte Mahnungen anzeigen', |
... | ... | |
2607 | 2609 |
'Skonto information' => 'Skonto Information', |
2608 | 2610 |
'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.', |
2609 | 2611 |
'Sold' => 'Verkauft', |
2612 |
'Sold order items' => 'Verkaufte Auftragsartikel', |
|
2610 | 2613 |
'Soldtotal does not make sense without any bsooqr options' => 'Option "Menge in gewählten Belegen" ohne gewählte Belege wird ignoriert.', |
2611 | 2614 |
'Solution' => 'Lösung', |
2612 | 2615 |
'Sort By' => 'Sortiert nach', |
menus/user/00-erp.yaml | ||
---|---|---|
348 | 348 |
module: dn.pl |
349 | 349 |
params: |
350 | 350 |
action: search |
351 |
- parent: ar_reports |
|
352 |
id: ar_order_item_search |
|
353 |
name: Order item search |
|
354 |
order: 750 |
|
355 |
access: sales_order_edit |
|
356 |
params: |
|
357 |
action: OrderItem/search |
|
351 | 358 |
- parent: ar_reports |
352 | 359 |
id: ar_reports_delivery_plan |
353 | 360 |
name: Delivery Plan |
templates/webpages/order_items_search/_order_item_list.html | ||
---|---|---|
1 |
[%- USE LxERP %] |
|
2 |
[%- USE T8 %] |
|
3 |
[%- USE L %] |
|
4 |
[%- USE HTML %] |
|
5 |
[%- USE P %] |
|
6 |
[% SET qty_round = 2 %] |
|
7 |
<table cellpadding="3px"> |
|
8 |
<tr class="listheading"> |
|
9 |
<th>[%- LxERP.t8("Part") %]</th> |
|
10 |
<th>[%- LxERP.t8("Customer") %]</th> |
|
11 |
<th>[%- LxERP.t8("Order") %]</th> |
|
12 |
<th>[%- LxERP.t8("Transdate") %]</th> |
|
13 |
<th>[%- LxERP.t8("Qty") %]</th> |
|
14 |
<th>[%- LxERP.t8("Delivered") %]</th> |
|
15 |
<th>[%- LxERP.t8("Price") %]</th> |
|
16 |
<th>[%- LxERP.t8("Discount") %] %</th> |
|
17 |
<th>[%- LxERP.t8("Delivery Order") %]</th> |
|
18 |
[% IF FORM.show_images %] |
|
19 |
<th>[%- LxERP.t8("Image") %]</th> |
|
20 |
[% END %] |
|
21 |
</tr> |
|
22 |
[% FOREACH order_item = SELF.orderitems %] |
|
23 |
<tr id="tr_[% loop.count %]" class="listrow[% loop.count % 2 %]"> |
|
24 |
<td> [% P.part(order_item.part, no_link => 0) %]</td> |
|
25 |
<td> [% P.customer(order_item.order.customer, no_link => 0) %]</td> |
|
26 |
<td class="numeric"> [% P.sales_order(order_item.order, no_link => 0) %]</td> |
|
27 |
<td> [% order_item.order.transdate.to_kivitendo %]</td> |
|
28 |
<td class="numeric [% IF order_item.delivered_qty == order_item.qty %]shipped[% ELSE %]not_shipped[% END %]"> |
|
29 |
[% LxERP.format_amount(order_item.qty, qty_round) %] [% order_item.unit | html %] |
|
30 |
</td> |
|
31 |
<td class="numeric"> [% LxERP.format_amount(order_item.delivered_qty, qty_round) %] [% order_item.unit | html %] </td> |
|
32 |
<td class="numeric"> [% order_item.sellprice_as_number %]</td> |
|
33 |
<td class="numeric"> [% order_item.discount_as_percent %]</td> |
|
34 |
<td> [% order_item.deliveryorders %]</td> |
|
35 |
[% IF FORM.show_images %] |
|
36 |
<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> |
|
37 |
[% END %] |
|
38 |
</tr> |
|
39 |
[% END %] |
|
40 |
</table> |
templates/webpages/order_items_search/order_items.html | ||
---|---|---|
1 |
[% USE HTML %] |
|
2 |
[%- USE LxERP %] |
|
3 |
[%- USE T8 %] |
|
4 |
[%- USE L %] |
|
5 |
|
|
6 |
[% SET size=50 %] |
|
7 |
[% SET show_images=0 %] |
|
8 |
|
|
9 |
<h1>[% title %]</h1> |
|
10 |
<div style="padding-bottom: 15px"> |
|
11 |
[% 'Filter' | $T8 %]: |
|
12 |
<form id="filter" name="filter" method="post" action="controller.pl"> |
|
13 |
<table> |
|
14 |
</tr> |
|
15 |
<td>[% 'Customer' | $T8 %]</td> |
|
16 |
<td>[% L.customer_vendor_picker('filter.order.customer.id', FORM.customer_id, type='customer', class="filter", size=size) %]</td> |
|
17 |
</tr> |
|
18 |
<tr> |
|
19 |
<td>[% 'Part' | $T8 %]</td> |
|
20 |
<td>[% L.input_tag('filter.part.all:substr:multi::ilike', FORM.part, size = size, class="filter") %]</td> |
|
21 |
</tr> |
|
22 |
<tr> |
|
23 |
<td>[% 'Order Number' | $T8 %]</td> |
|
24 |
<td>[% L.input_tag('filter.order.ordnumber:substr::ilike', FORM.ordnumber, size = 10, class="filter") %]</td> |
|
25 |
<tr> |
|
26 |
<tr> |
|
27 |
<td>[% 'Order Date' | $T8 %]</td> |
|
28 |
<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> |
|
29 |
<tr> |
|
30 |
<tr> |
|
31 |
<td>[% 'Description' | $T8 %]</td> |
|
32 |
<td>[% L.input_tag('filter.description:substr::ilike', filter.description_substr__ilike, size = size, class="filter") %]</td> |
|
33 |
</tr> |
|
34 |
<tr> |
|
35 |
<td>[% 'Long Description' | $T8 %]</td> |
|
36 |
<td>[% L.input_tag('filter.longdescription:substr::ilike', filter.longdescription_substr__ilike, size = size, class="filter") %] </tr> |
|
37 |
<tr> |
|
38 |
<td>[% 'Show images' | $T8 %]</td> |
|
39 |
<td>[% L.checkbox_tag('show_images', checked=show_images) %] </tr> |
|
40 |
</tr> |
|
41 |
</table> |
|
42 |
[% L.button_tag("this.form.reset(); refresh_plot();", LxERP.t8("Reset")) %] |
|
43 |
</form> |
|
44 |
|
|
45 |
<div id="orderitems" style="padding-top: 20px"> |
|
46 |
[% PROCESS 'order_items_search/_order_item_list.html' %] |
|
47 |
</div> |
|
48 |
|
|
49 |
|
|
50 |
<script type="text/javascript"> |
|
51 |
$(function() { |
|
52 |
[% IF FORM.customer_id %] |
|
53 |
$( "#filter_part_all_substr_multi_ilike" ).focus(); |
|
54 |
[% ELSE %] |
|
55 |
$( "#filter_order_customer_id_name" ).focus(); |
|
56 |
[% END %] |
|
57 |
|
|
58 |
addInputCallback($(".filter"), refresh_plot , 300 ); |
|
59 |
|
|
60 |
$('#show_images').change(function(){ |
|
61 |
refresh_plot(); |
|
62 |
}); |
|
63 |
}); |
|
64 |
|
|
65 |
|
|
66 |
function refresh_plot() { |
|
67 |
var filterdata = $('#filter').serialize() |
|
68 |
var url = './controller.pl?action=OrderItem/order_item_list_dynamic_table&' + filterdata; |
|
69 |
$.ajax({ |
|
70 |
url : url, |
|
71 |
type: 'POST', |
|
72 |
success: function(data){ |
|
73 |
$('#orderitems').html(data); |
|
74 |
} |
|
75 |
}) |
|
76 |
|
|
77 |
}; |
|
78 |
|
|
79 |
function addInputCallback(inputfield, callback, delay) { |
|
80 |
var timer = null; |
|
81 |
inputfield.on('keyup', function() { |
|
82 |
if (timer) { |
|
83 |
window.clearTimeout(timer); |
|
84 |
} |
|
85 |
timer = window.setTimeout( function() { |
|
86 |
timer = null; |
|
87 |
callback(); |
|
88 |
}, delay ); |
|
89 |
}); |
|
90 |
inputfield = null; |
|
91 |
} |
|
92 |
</script> |
Auch abrufbar als: Unified diff
Neue Maske: Auftragsartikelsuche
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.