c3b366a9921e7dcbbdf445a6c9601ce2072e6932
[kivitendo-erp.git] / SL / Presenter / Part.pm
1 package SL::Presenter::Part;
2
3 use strict;
4
5 use SL::DB::Part;
6 use SL::DB::PartClassification;
7 use SL::Locale::String qw(t8);
8
9 use Exporter qw(import);
10 our @EXPORT = qw(part_picker part select_classification classification_abbreviation type_abbreviation separate_abbreviation);
11
12 use Carp;
13
14 sub part {
15   my ($self, $part, %params) = @_;
16
17   $params{display} ||= 'inline';
18
19   croak "Unknown display type '$params{display}'" unless $params{display} =~ m/^(?:inline|table-cell)$/;
20
21   my $text = join '', (
22     $params{no_link} ? '' : '<a href="controller.pl?action=Part/edit&part.id=' . $self->escape($part->id) . '">',
23     $self->escape($part->partnumber),
24     $params{no_link} ? '' : '</a>',
25   );
26   return $self->escaped_text($text);
27 }
28
29 sub part_picker {
30   my ($self, $name, $value, %params) = @_;
31
32   $value = SL::DB::Manager::Part->find_by(id => $value) if $value && !ref $value;
33   my $id = delete($params{id}) || $self->name_to_id($name);
34   my $fat_set_item = delete $params{fat_set_item};
35
36   my @classes = $params{class} ? ($params{class}) : ();
37   push @classes, 'part_autocomplete';
38   push @classes, 'partpicker_fat_set_item' if $fat_set_item;
39
40   my $ret =
41     $self->input_tag($name, (ref $value && $value->can('id') ? $value->id : ''), class => "@classes", type => 'hidden', id => $id) .
42     join('', map { $params{$_} ? $self->input_tag("", delete $params{$_}, id => "${id}_${_}", type => 'hidden') : '' } qw(part_type unit convertible_unit)) .
43     $self->input_tag("", ref $value ? $value->displayable_name : '', id => "${id}_name", %params);
44
45   $::request->layout->add_javascripts('autocomplete_part.js');
46   $::request->presenter->need_reinit_widgets($id);
47
48   $self->html_tag('span', $ret, class => 'part_picker');
49 }
50
51 #
52 # shortcut for article type
53 #
54 sub type_abbreviation {
55   my ($self, $part_type) = @_;
56   $main::lxdebug->message(LXDebug->DEBUG2(),"parttype=".$part_type);
57   return $::locale->text('Assembly (typeabbreviation)')   if $part_type eq 'assembly';
58   return $::locale->text('Part (typeabbreviation)')       if $part_type eq 'part';
59   return $::locale->text('Assortment (typeabbreviation)') if $part_type eq 'assortment';
60   return $::locale->text('Service (typeabbreviation)');
61 }
62
63 #
64 # Translations for Abbreviations:
65 #
66 # $::locale->text('None (typeabbreviation)')
67 # $::locale->text('Purchase (typeabbreviation)')
68 # $::locale->text('Sales (typeabbreviation)')
69 # $::locale->text('Merchandise (typeabbreviation)')
70 # $::locale->text('Production (typeabbreviation)')
71 #
72 # and for descriptions
73 # $::locale->text('Purchase')
74 # $::locale->text('Sales')
75 # $::locale->text('Merchandise')
76 # $::locale->text('Production')
77
78 #
79 # shortcut for article type
80 #
81 sub classification_abbreviation {
82   my ($self, $id) = @_;
83   SL::DB::Manager::PartClassification->cache_all();
84   my $obj = SL::DB::PartClassification->load_cached($id);
85   $obj && $obj->abbreviation ? t8($obj->abbreviation) : '';
86 }
87
88 #
89 # generate selection tag
90 #
91 sub select_classification {
92   my ($self, $name, %attributes) = @_;
93   $attributes{value_key} = 'id';
94   $attributes{title_key} = 'description';
95   my $collection = SL::DB::Manager::PartClassification->get_all_sorted();
96   $_->description($::locale->text($_->description)) for @{ $collection };
97   return $self->select_tag( $name, $collection, %attributes );
98 }
99
100 1;
101
102 __END__
103
104 =encoding utf-8
105
106 =head1 NAME
107
108 SL::Presenter::Part - Part related presenter stuff
109
110 =head1 SYNOPSIS
111
112   # Create an html link for editing/opening a part/service/assembly
113   my $object = my $object = SL::DB::Manager::Part->get_first;
114   my $html   = SL::Presenter->get->part($object, display => 'inline');
115
116 see also L<SL::Presenter>
117
118 =head1 DESCRIPTION
119
120 see L<SL::Presenter>
121
122 =head1 FUNCTIONS
123
124 =over 2
125
126 =item C<part, $object, %params>
127
128 Returns a rendered version (actually an instance of
129 L<SL::Presenter::EscapedText>) of the part object C<$object>
130
131 C<%params> can include:
132
133 =over 4
134
135 =item * display
136
137 Either C<inline> (the default) or C<table-cell>. At the moment both
138 representations are identical and produce the part's name linked
139 to the corresponding 'edit' action.
140
141 =back
142
143 =back
144
145 =over 2
146
147 =item C<classification_abbreviation $classification_id>
148
149 Returns the shortcut of the classification
150
151 =back
152
153 =over 2
154
155 =item C<select_classification $name,%params>
156
157 Returns a HTML Select Tag with all available Classifications
158
159 C<%params> can include:
160
161 =over 4
162
163 =item * default
164
165 The Id of the selected item .
166
167 =back
168
169 =back
170
171 =over 2
172
173 =item C<part_picker $name, $value, %params>
174
175 All-in-one picker widget for parts. The name will be both id and name
176 of the resulting hidden C<id> input field (but the ID can be
177 overwritten with C<$params{id}>).
178
179 An additional dummy input will be generated which is used to find
180 parts. For a detailed description of its behaviour, see section
181 C<PART PICKER SPECIFICATION>.
182
183 C<$value> can be a parts id or a C<Rose::DB:Object> instance.
184
185 If C<%params> contains C<part_type> only parts of this type will be used
186 for autocompletion. You may comma separate multiple types as in
187 C<part,assembly>.
188
189 If C<%params> contains C<unit> only parts with this unit will be used
190 for autocompletion. You may comma separate multiple units as in
191 C<h,min>.
192
193 If C<%params> contains C<convertible_unit> only parts with a unit
194 that's convertible to unit will be used for autocompletion.
195
196 Obsolete parts will by default not be displayed for selection. However they are
197 accepted as default values and can persist during updates. As with other
198 selectors though, they are not selectable once overridden.
199
200 C<part_picker> will register it's javascript for inclusion in the next header
201 rendering. If you write a standard controller that only call C<render> once, it
202 will just work.  In case the header is generated in a different render call
203 (multiple blocks, ajax, old C<bin/mozilla> style controllers) you need to
204 include C<js/autocomplete_part.js> yourself.
205
206 =back
207
208 =head1 PART PICKER SPECIFICATION
209
210 The following list of design goals were applied:
211
212 =over 4
213
214 =item *
215
216 Parts should not be perceived by the user as distinct inputs of partnumber and
217 description but as a single object
218
219 =item *
220
221 Easy to use without documentation for novice users
222
223 =item *
224
225 Fast to use with keyboard for experienced users
226
227 =item *
228
229 Possible to use without any keyboard interaction for mouse (or touchscreen)
230 users
231
232 =item *
233
234 Must not leave the current page in event of ambiguity (cf. current select_item
235 mechanism)
236
237 =item *
238
239 Should be useable with hand scanners or similar alternative keyboard devices
240
241 =item *
242
243 Should not require a feedback/check loop in the common case
244
245 =item *
246
247 Should not be constrained to exact matches
248
249 =back
250
251 The implementation consists of the following parts which will be referenced later:
252
253 =over 4
254
255 =item 1
256
257 A hidden input (id input), used to hold the id of the selected part. The only
258 input that gets submitted
259
260 =item 2
261
262 An input (dummy input) containing a description of the currently selected part,
263 also used by the user to search for parts
264
265 =item 3
266
267 A jquery.autocomplete mechanism attached to the dummy field
268
269 =item 4
270
271 A popup layer for both feedback and input of additional data in case of
272 ambiguity.
273
274 =item 5
275
276 An internal status of the part picker, indicating whether id input and dummy
277 input are consistent. After leaving the dummy input the part picker must
278 place itself in a consistent status.
279
280 =item 6
281
282 A clickable icon (popup trigger) attached to the dummy input, which triggers the popup layer.
283
284 =back
285
286 =head1 BUGS
287
288 None atm :)
289
290 =head1 AUTHOR
291
292 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
293
294 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
295
296 =cut