633f68e1f9b257865014691ed3dc6b807151de72
[kivitendo-erp.git] / SL / Controller / SimpleSystemSetting.pm
1 package SL::Controller::SimpleSystemSetting;
2
3 use strict;
4 use utf8;
5
6 use parent qw(SL::Controller::Base);
7
8 use SL::Helper::Flash;
9 use SL::Locale::String;
10 use SL::DB::Default;
11 use SL::System::Process;
12
13 use Rose::Object::MakeMethods::Generic (
14   scalar                  => [ qw(type config) ],
15   'scalar --get_set_init' => [ qw(defaults object all_objects class manager_class list_attributes list_url supports_reordering) ],
16 );
17
18 __PACKAGE__->run_before('check_type_and_auth');
19 __PACKAGE__->run_before('setup_javascript', only => [ qw(add create edit update delete) ]);
20
21 # Make locales.pl happy: $self->render("simple_system_setting/_default_form")
22
23 my %supported_types = (
24   bank_account => {
25     # Make locales.pl happy: $self->render("simple_system_setting/_bank_account_form")
26     class  => 'BankAccount',
27     titles => {
28       list => t8('Bank accounts'),
29       add  => t8('Add bank account'),
30       edit => t8('Edit bank account'),
31     },
32     list_attributes => [
33       { method => 'name',                                      title => t8('Name'), },
34       { method => 'iban',                                      title => t8('IBAN'), },
35       { method => 'bank',                                      title => t8('Bank'), },
36       { method => 'bank_code',                                 title => t8('Bank code'), },
37       { method => 'bank_account_id',                           title => t8('Bank Account Id Number (Swiss)'), },
38       { method => 'bic',                                       title => t8('BIC'), },
39       {                                                        title => t8('Use for Factur-X/ZUGFeRD'), formatter => sub { $_[0]->use_for_zugferd ? t8('yes') : t8('no') } },
40       {                                                        title => t8('Use for Swiss QR-Bill'), formatter => sub { $_[0]->use_for_qrbill ? t8('yes') : t8('no') } },
41       { method => 'reconciliation_starting_date_as_date',      title => t8('Date'),    align => 'right' },
42       { method => 'reconciliation_starting_balance_as_number', title => t8('Balance'), align => 'right' },
43     ],
44   },
45
46   business => {
47     # Make locales.pl happy: $self->render("simple_system_setting/_business_form")
48     class  => 'Business',
49     titles => {
50       list => t8('Businesses'),
51       add  => t8('Add business'),
52       edit => t8('Edit business'),
53     },
54     list_attributes => [
55       { method => 'description',         title => t8('Description'), },
56       {                                  title => t8('Discount'), formatter => sub { $_[0]->discount_as_percent . ' %' }, align => 'right' },
57       { method => 'customernumberinit',  title => t8('Customernumberinit'), },
58     ],
59   },
60
61   contact_department => {
62     class  => 'ContactDepartment',
63     auth   => 'config',
64     titles => {
65       list => t8('Contact Departments'),
66       add  => t8('Add department'),
67       edit => t8('Edit department'),
68     },
69   },
70
71   contact_title => {
72     class  => 'ContactTitle',
73     auth   => 'config',
74     titles => {
75       list => t8('Contact Titles'),
76       add  => t8('Add title'),
77       edit => t8('Edit title'),
78     },
79   },
80
81   department => {
82     class  => 'Department',
83     titles => {
84       list => t8('Departments'),
85       add  => t8('Add department'),
86       edit => t8('Edit department'),
87     },
88   },
89
90   greeting => {
91     class  => 'Greeting',
92     auth   => 'config',
93     titles => {
94       list => t8('Greetings'),
95       add  => t8('Add greeting'),
96       edit => t8('Edit greeting'),
97     },
98   },
99
100   language => {
101     # Make locales.pl happy: $self->render("simple_system_setting/_language_form")
102     class  => 'Language',
103     titles => {
104       list => t8('Languages'),
105       add  => t8('Add language'),
106       edit => t8('Edit language'),
107     },
108     list_attributes => [
109       { method => 'description',   title => t8('Description'), },
110       { method => 'template_code', title => t8('Template Code'), },
111       { method => 'article_code',  title => t8('Article Code'), },
112       {                            title => t8('Number Format'), formatter => sub { $_[0]->output_numberformat || t8('use program settings') } },
113       {                            title => t8('Date Format'),   formatter => sub { $_[0]->output_dateformat   || t8('use program settings') } },
114       {                            title => t8('Long Dates'),    formatter => sub { $_[0]->output_longdates ? t8('yes') : t8('no') } },
115     ],
116   },
117
118   part_classification => {
119     # Make locales.pl happy: $self->render("simple_system_setting/_part_classification_form")
120     class  => 'PartClassification',
121     titles => {
122       list => t8('Part classifications'),
123       add  => t8('Add part classification'),
124       edit => t8('Edit part classification'),
125     },
126     list_attributes => [
127       { title => t8('Description'),       formatter => sub { t8($_[0]->description) } },
128       { title => t8('Type abbreviation'), formatter => sub { t8($_[0]->abbreviation) } },
129       { title => t8('Used for Purchase'), formatter => sub { $_[0]->used_for_purchase ? t8('yes') : t8('no') } },
130       { title => t8('Used for Sale'),     formatter => sub { $_[0]->used_for_sale     ? t8('yes') : t8('no') } },
131       { title => t8('Report separately'), formatter => sub { $_[0]->report_separate   ? t8('yes') : t8('no') } },
132     ],
133   },
134
135   parts_group => {
136     # Make locales.pl happy: $self->render("simple_system_setting/_parts_group_form")
137     class  => 'PartsGroup',
138     titles => {
139       list => t8('Partsgroups'),
140       add  => t8('Add partsgroup'),
141       edit => t8('Edit partsgroup'),
142     },
143     list_attributes => [
144       { method => 'partsgroup', title => t8('Description') },
145       { method => 'obsolete',   title => t8('Obsolete'), formatter => sub { $_[0]->obsolete ? t8('yes') : t8('no') } },
146     ],
147   },
148
149   price_factor => {
150     # Make locales.pl happy: $self->render("simple_system_setting/_price_factor_form")
151     class  => 'PriceFactor',
152     titles => {
153       list => t8('Price Factors'),
154       add  => t8('Add Price Factor'),
155       edit => t8('Edit Price Factor'),
156     },
157     list_attributes => [
158       { method => 'description',      title => t8('Description') },
159       { method => 'factor_as_number', title => t8('Factor'), align => 'right' },
160     ],
161   },
162
163   pricegroup => {
164     # Make locales.pl happy: $self->render("simple_system_setting/_pricegroup_form")
165     class  => 'Pricegroup',
166     titles => {
167       list => t8('Pricegroups'),
168       add  => t8('Add pricegroup'),
169       edit => t8('Edit pricegroup'),
170     },
171     list_attributes => [
172       { method => 'pricegroup', title => t8('Description') },
173       { method => 'obsolete',   title => t8('Obsolete'), formatter => sub { $_[0]->obsolete ? t8('yes') : t8('no') } },
174     ],
175   },
176
177   project_status => {
178     class  => 'ProjectStatus',
179     titles => {
180       list => t8('Project statuses'),
181       add  => t8('Add project status'),
182       edit => t8('Edit project status'),
183     },
184   },
185
186   project_type => {
187     class  => 'ProjectType',
188     titles => {
189       list => t8('Project types'),
190       add  => t8('Add project type'),
191       edit => t8('Edit project type'),
192     },
193   },
194
195   requirement_spec_acceptance_status => {
196     # Make locales.pl happy: $self->render("simple_system_setting/_requirement_spec_acceptance_status_form")
197     class  => 'RequirementSpecAcceptanceStatus',
198     titles => {
199       list => t8('Acceptance Statuses'),
200       add  => t8('Add acceptance status'),
201       edit => t8('Edit acceptance status'),
202     },
203     list_attributes => [
204       { method => 'name',        title => t8('Name') },
205       { method => 'description', title => t8('Description') },
206     ],
207   },
208
209   requirement_spec_complexity => {
210     class  => 'RequirementSpecComplexity',
211     titles => {
212       list => t8('Complexities'),
213       add  => t8('Add complexity'),
214       edit => t8('Edit complexity'),
215     },
216   },
217
218   requirement_spec_predefined_text => {
219     # Make locales.pl happy: $self->render("simple_system_setting/_requirement_spec_predefined_text_form")
220     class  => 'RequirementSpecPredefinedText',
221     titles => {
222       list => t8('Pre-defined Texts'),
223       add  => t8('Add pre-defined text'),
224       edit => t8('Edit pre-defined text'),
225     },
226     list_attributes => [
227       { method => 'description', title => t8('Description') },
228       { method => 'title',       title => t8('Title') },
229       {                          title => t8('Content'),                 formatter => sub { my $t = $_[0]->text_as_stripped_html; length($t) > 50 ? substr($t, 0, 50) . '…' : $t } },
230       {                          title => t8('Useable for text blocks'), formatter => sub { $_[0]->useable_for_text_blocks ? t8('yes') : t8('no') } },
231       {                          title => t8('Useable for sections'),    formatter => sub { $_[0]->useable_for_sections    ? t8('yes') : t8('no') } },
232     ],
233   },
234
235   requirement_spec_risk => {
236     class  => 'RequirementSpecRisk',
237     titles => {
238       list => t8('Risk levels'),
239       add  => t8('Add risk level'),
240       edit => t8('Edit risk level'),
241     },
242   },
243
244   requirement_spec_status => {
245     # Make locales.pl happy: $self->render("simple_system_setting/_requirement_spec_status_form")
246     class  => 'RequirementSpecStatus',
247     titles => {
248       list => t8('Requirement Spec Statuses'),
249       add  => t8('Add requirement spec status'),
250       edit => t8('Edit requirement spec status'),
251     },
252     list_attributes => [
253       { method => 'name',        title => t8('Name') },
254       { method => 'description', title => t8('Description') },
255     ],
256   },
257
258   requirement_spec_type => {
259     # Make locales.pl happy: $self->render("simple_system_setting/_requirement_spec_type_form")
260     class  => 'RequirementSpecType',
261     titles => {
262       list => t8('Requirement Spec Types'),
263       add  => t8('Add requirement spec type'),
264       edit => t8('Edit requirement spec type'),
265     },
266     list_attributes => [
267       { method => 'description',                  title => t8('Description') },
268       { method => 'section_number_format',        title => t8('Section number format') },
269       { method => 'function_block_number_format', title => t8('Function block number format') },
270     ],
271   },
272
273   time_recording_article => {
274     # Make locales.pl happy: $self->render("simple_system_setting/_time_recording_article_form")
275     class  => 'TimeRecordingArticle',
276     auth   => 'config',
277     titles => {
278       list => t8('Time Recording Articles'),
279       add  => t8('Add time recording article'),
280       edit => t8('Edit time recording article'),
281     },
282     list_attributes => [
283       { title => t8('Article'), formatter => sub { $_[0]->part->displayable_name } },
284     ],
285   },
286
287 );
288
289 my @default_list_attributes = (
290   { method => 'description', title => t8('Description') },
291 );
292
293 #
294 # actions
295 #
296
297 sub action_list {
298   my ($self) = @_;
299
300   $self->setup_list_action_bar;
301   $self->render('simple_system_setting/list', title => $self->config->{titles}->{list});
302 }
303
304 sub action_new {
305   my ($self) = @_;
306
307   $self->object($self->class->new);
308   $self->render_form(title => $self->config->{titles}->{add});
309 }
310
311 sub action_edit {
312   my ($self) = @_;
313
314   $self->render_form(title => $self->config->{titles}->{edit});
315 }
316
317 sub action_create {
318   my ($self) = @_;
319
320   $self->object($self->class->new);
321   $self->create_or_update;
322 }
323
324 sub action_update {
325   my ($self) = @_;
326
327   $self->create_or_update;
328 }
329
330 sub action_delete {
331   my ($self) = @_;
332
333   if ($self->object->can('orphaned') && !$self->object->orphaned) {
334     flash_later('error', t8('The object is in use and cannot be deleted.'));
335
336   } elsif ( eval { $self->object->delete; 1; } ) {
337     flash_later('info',  t8('The object has been deleted.'));
338
339   } else {
340     flash_later('error', t8('The object is in use and cannot be deleted.'));
341   }
342
343   $self->redirect_to($self->list_url);
344 }
345
346 sub action_reorder {
347   my ($self) = @_;
348
349   $self->class->reorder_list(@{ $::form->{object_id} || [] });
350   $self->render(\'', { type => 'json' });
351 }
352
353 #
354 # filters
355 #
356
357 sub check_type_and_auth {
358   my ($self) = @_;
359
360   $self->type($::form->{type});
361   $self->config($supported_types{$self->type}) || die "Unsupported type";
362
363   $::auth->assert($self->config->{auth} || 'config');
364
365   my $pm = (map { s{::}{/}g; "${_}.pm" } $self->class)[0];
366   require $pm;
367
368   my $setup = "setup_" . $self->type;
369   $self->$setup if $self->can($setup);
370
371   1;
372 }
373
374 sub setup_javascript {
375   $::request->layout->use_javascript("${_}.js") for qw(ckeditor/ckeditor ckeditor/adapters/jquery);
376 }
377
378 sub init_class               { "SL::DB::"          . $_[0]->config->{class}                  }
379 sub init_manager_class       { "SL::DB::Manager::" . $_[0]->config->{class}                  }
380 sub init_object              { $_[0]->class->new(id => $::form->{id})->load                  }
381 sub init_all_objects         { $_[0]->manager_class->get_all_sorted                          }
382 sub init_list_url            { $_[0]->url_for(action => 'list', type => $_[0]->type)         }
383 sub init_supports_reordering { $_[0]->class->new->can('reorder_list')                        }
384 sub init_defaults            { SL::DB::Default->get                                          }
385
386 sub init_list_attributes {
387   my ($self) = @_;
388
389   my $method = "list_attributes_" . $self->type;
390
391   return $self->$method if $self->can($method);
392   return $self->config->{list_attributes} // \@default_list_attributes;
393 }
394
395 #
396 # helpers
397 #
398
399 sub create_or_update {
400   my ($self) = @_;
401   my $is_new = !$self->object->id;
402
403   my $params = delete($::form->{object}) || { };
404
405   $self->object->assign_attributes(%{ $params });
406
407   my @errors;
408
409   push @errors, $self->object->validate if $self->object->can('validate');
410
411   if (@errors) {
412     flash('error', @errors);
413     return $self->render_form(title => $self->config->{titles}->{$is_new ? 'add' : 'edit'});
414   }
415
416   $self->object->save;
417
418   flash_later('info', $is_new ? t8('The object has been created.') : t8('The object has been saved.'));
419
420   $self->redirect_to($self->list_url);
421 }
422
423 sub render_form {
424   my ($self, %params) = @_;
425
426   my $sub_form_template = SL::System::Process->exe_dir . '/templates/webpages/simple_system_setting/_' . $self->type . '_form.html';
427
428   $self->setup_render_form_action_bar;
429   $self->render(
430     'simple_system_setting/form',
431     %params,
432     sub_form_template => (-f $sub_form_template ? $self->type : 'default'),
433   );
434 }
435
436 #
437 # type-specific helper functions
438 #
439
440 sub setup_requirement_spec_acceptance_status {
441   my ($self) = @_;
442
443   no warnings 'once';
444   $self->{valid_names} = \@SL::DB::RequirementSpecAcceptanceStatus::valid_names;
445 }
446
447 sub setup_requirement_spec_status {
448   my ($self) = @_;
449
450   no warnings 'once';
451   $self->{valid_names} = \@SL::DB::RequirementSpecStatus::valid_names;
452 }
453
454 sub setup_language {
455   my ($self) = @_;
456
457   $self->{numberformats} = [ '1,000.00', '1000.00', '1.000,00', '1000,00', "1'000.00" ];
458   $self->{dateformats}   = [ qw(mm/dd/yy dd/mm/yy dd.mm.yy yyyy-mm-dd) ];
459 }
460
461 #
462 # action bar
463 #
464
465 sub setup_list_action_bar {
466   my ($self, %params) = @_;
467
468   for my $bar ($::request->layout->get('actionbar')) {
469     $bar->add(
470       link => [
471         t8('Add'),
472         link => $self->url_for(action => 'new', type => $self->type),
473       ],
474     );
475   }
476 }
477
478 sub setup_render_form_action_bar {
479   my ($self) = @_;
480
481   my $is_new         = !$self->object->id;
482   my $can_be_deleted = !$is_new
483                     && (!$self->object->can("orphaned")       || $self->object->orphaned)
484                     && (!$self->object->can("can_be_deleted") || $self->object->can_be_deleted);
485
486   for my $bar ($::request->layout->get('actionbar')) {
487     $bar->add(
488       action => [
489         t8('Save'),
490         submit    => [ '#form', { action => 'SimpleSystemSetting/' . ($is_new ? 'create' : 'update') } ],
491         checks    => [ 'kivi.validate_form' ],
492         accesskey => 'enter',
493       ],
494
495       action => [
496         t8('Delete'),
497         submit   => [ '#form', { action => 'SimpleSystemSetting/delete' } ],
498         confirm  => t8('Do you really want to delete this object?'),
499         disabled => $is_new          ? t8('This object has not been saved yet.')
500                   : !$can_be_deleted ? t8('The object is in use and cannot be deleted.')
501                   :                    undef,
502       ],
503
504       link => [
505         t8('Abort'),
506         link => $self->list_url,
507       ],
508     );
509   }
510   $::request->layout->add_javascripts('kivi.Validator.js');
511 }
512
513 1;
514
515 __END__
516
517 =encoding utf-8
518
519 =head1 NAME
520
521 SL::Controller::SimpleSystemSettings — a common CRUD controller for
522 various settings in the "System" menu
523
524 =head1 AUTHOR
525
526 Moritz Bunkus <m.bunkus@linet-services.de>
527
528 =cut