use Clone qw(clone);
use SL::DB::Part;
+use SL::DB::PartsGroup;
use SL::Controller::Helper::GetModels;
use SL::Locale::String qw(t8);
use SL::JSON;
use Data::Dumper;
use DateTime;
use SL::DB::History;
+use SL::DB::Helper::ValidateAssembly qw(validate_assembly);
use SL::CVar;
+use SL::MoreCommon qw(save_form);
use Carp;
use Rose::Object::MakeMethods::Generic (
sub action_add_part {
my ($self, %params) = @_;
+ $::form->{callback} = $self->url_for(action => 'add_part') unless $::form->{callback};
$self->part( SL::DB::Part->new_part );
$self->add;
};
sub action_add_service {
my ($self, %params) = @_;
+ $::form->{callback} = $self->url_for(action => 'add_service') unless $::form->{callback};
$self->part( SL::DB::Part->new_service );
$self->add;
};
sub action_add_assembly {
my ($self, %params) = @_;
+ $::form->{callback} = $self->url_for(action => 'add_assembly') unless $::form->{callback};
$self->part( SL::DB::Part->new_assembly );
$self->add;
};
sub action_add_assortment {
my ($self, %params) = @_;
+ $::form->{callback} = $self->url_for(action => 'add_assortment') unless $::form->{callback};
$self->part( SL::DB::Part->new_assortment );
$self->add;
};
+sub action_add_from_record {
+ my ($self) = @_;
+
+ check_has_valid_part_type($::form->{part}{part_type});
+
+ $self->parse_form;
+ $self->add;
+}
+
sub action_add {
my ($self) = @_;
return $self->js->error(t8('The document has been changed by another user. Please reopen it in another window and copy the changes to the new window'))->render;
}
- if ( $is_new and !$::form->{part}{partnumber} ) {
- $self->check_next_transnumber_is_free or return $self->js->error(t8('The next partnumber in the number range already exists!'))->render;
+ if ( $is_new
+ && $::form->{part}{partnumber}
+ && SL::DB::Manager::Part->find_by(partnumber => $::form->{part}{partnumber})
+ ) {
+ return $self->js->error(t8('The partnumber is already being used'))->render;
}
$self->parse_form;
1;
}) or return $self->js->error(t8('The item couldn\'t be saved!') . " " . $self->part->db->error )->render;
- flash_later('info', $is_new ? t8('The item has been created.') : t8('The item has been saved.'));
+ ;
+ flash_later('info', $is_new ? t8('The item has been created.') . " " . $self->part->displayable_name : t8('The item has been saved.'));
+
+ if ( $::form->{callback} ) {
+ $self->redirect_to($::form->unescape($::form->{callback}) . '&new_parts_id=' . $self->part->id);
- # reload item, this also resets last_modification!
- $self->redirect_to(controller => 'Part', action => 'edit', 'part.id' => $self->part->id);
+ } else {
+ # default behaviour after save: reload item, this also resets last_modification!
+ $self->redirect_to(controller => 'Part', action => 'edit', 'part.id' => $self->part->id);
+ }
}
sub action_save_as_new {
}) or return $self->js->error(t8('The item couldn\'t be deleted!') . " " . $self->part->db->error)->render;
flash_later('info', t8('The item has been deleted.'));
- my @redirect_params = (
- controller => 'controller.pl',
- action => 'LoginScreen/user_login'
- );
- $self->redirect_to(@redirect_params);
+ if ( $::form->{callback} ) {
+ $self->redirect_to($::form->unescape($::form->{callback}));
+ } else {
+ my @redirect_params = (
+ controller => 'controller.pl',
+ action => 'LoginScreen/user_login'
+ );
+ $self->redirect_to(@redirect_params);
+ }
}
sub action_use_as_new {
my ($self, %params) = @_;
$self->_set_javascript;
+ $self->_setup_form_action_bar;
my (%assortment_vars, %assembly_vars);
%assortment_vars = %{ $self->prepare_assortment_render_vars } if $self->part->is_assortment;
$self->render(
'part/form',
title => $title_hash{$self->part->part_type},
- show_edit_buttons => $::auth->assert('part_service_assembly_edit'),
%assortment_vars,
%assembly_vars,
translations_map => { map { ($_->language_id => $_) } @{$self->part->translations} },
->html('#items_sum_diff', $::form->format_amount(\%::myconfig, $sum_diff, 2, 0))
->html('#items_sellprice_sum_basic', $::form->format_amount(\%::myconfig, $sellprice_sum, 2, 0))
->html('#items_lastcost_sum_basic', $::form->format_amount(\%::myconfig, $lastcost_sum, 2, 0))
- ->render();
+ ->no_flash_clear->render();
}
sub action_add_multi_assortment_items {
my $item_objects = $self->parse_add_items_to_objects(part_type => 'assortment');
my $html = $self->render_assortment_items_to_html($item_objects);
- $self->js->run('kivi.Part.close_multi_items_dialog')
+ $self->js->run('kivi.Part.close_picker_dialogs')
->append('#assortment_rows', $html)
->run('kivi.Part.renumber_positions')
->run('kivi.Part.assortment_recalc')
my ($self) = @_;
my $item_objects = $self->parse_add_items_to_objects(part_type => 'assembly');
- my $html = $self->render_assembly_items_to_html($item_objects);
+ my @checked_objects;
+ foreach my $item (@{$item_objects}) {
+ my $errstr = validate_assembly($item->part,$self->part);
+ $self->js->flash('error',$errstr) if $errstr;
+ push (@checked_objects,$item) unless $errstr;
+ }
+
+ my $html = $self->render_assembly_items_to_html(\@checked_objects);
- $self->js->run('kivi.Part.close_multi_items_dialog')
+ $self->js->run('kivi.Part.close_picker_dialogs')
->append('#assembly_rows', $html)
->run('kivi.Part.renumber_positions')
->run('kivi.Part.assembly_recalc')
->html('#items_lastcost_sum_basic', $::form->format_amount(\%::myconfig, $items_lastcost_sum, 2, 0))
->render;
}
+
sub action_add_assembly_item {
my ($self) = @_;
carp('Too many objects passed to add_assembly_item') if @{$::form->{add_items}} > 1;
my $add_item_id = $::form->{add_items}->[0]->{parts_id};
+
my $duplicate_warning = 0; # duplicates are allowed, just warn
if ( $add_item_id && grep { $add_item_id == $_->parts_id } @{ $self->assembly_items } ) {
$duplicate_warning++;
my $number_of_items = scalar @{$self->assembly_items};
my $item_objects = $self->parse_add_items_to_objects(part_type => 'assembly');
+ if ($add_item_id ) {
+ foreach my $item (@{$item_objects}) {
+ my $errstr = validate_assembly($item->part,$self->part);
+ return $self->js->flash('error',$errstr)->render if $errstr;
+ }
+ }
+
+
my $html = $self->render_assembly_items_to_html($item_objects, $number_of_items);
$self->js->flash('info', t8("This part has already been added.")) if $duplicate_warning;
}
sub action_show_multi_items_dialog {
- require SL::DB::PartsGroup;
$_[0]->render('part/_multi_items_dialog', { layout => 0 },
- part_type => 'assortment',
- partfilter => '', # can I get at the current input of the partpicker here?
- all_partsgroups => SL::DB::Manager::PartsGroup->get_all);
+ all_partsgroups => SL::DB::Manager::PartsGroup->get_all
+ );
}
sub action_multi_items_update_result {
# if someone types something, and hits enter, assume he entered the full name.
# if something matches, treat that as sole match
- # unfortunately get_models can't do more than one per package atm, so we d it
- # the oldfashioned way.
+ # since we need a second get models instance with different filters for that,
+ # we only modify the original filter temporarily in place
if ($::form->{prefer_exact}) {
+ local $::form->{filter}{'all::ilike'} = delete local $::form->{filter}{'all:substr:multi::ilike'};
+
+ my $exact_models = SL::Controller::Helper::GetModels->new(
+ controller => $self,
+ sorted => 0,
+ paginated => { per_page => 2 },
+ with_objects => [ qw(unit_obj classification) ],
+ );
my $exact_matches;
- if (1 == scalar @{ $exact_matches = SL::DB::Manager::Part->get_all(
- query => [
- obsolete => 0,
- SL::DB::Manager::Part->type_filter($::form->{filter}{part_type}),
- or => [
- description => { ilike => $::form->{filter}{'all:substr:multi::ilike'} },
- partnumber => { ilike => $::form->{filter}{'all:substr:multi::ilike'} },
- ]
- ],
- limit => 2,
- ) }) {
+ if (1 == scalar @{ $exact_matches = $exact_models->get }) {
$self->parts($exact_matches);
}
}
}
sub action_part_picker_search {
- $_[0]->render('part/part_picker_search', { layout => 0 }, parts => $_[0]->parts);
+ $_[0]->render('part/part_picker_search', { layout => 0 });
}
sub action_part_picker_result {
- $_[0]->render('part/_part_picker_result', { layout => 0 });
+ $_[0]->render('part/_part_picker_result', { layout => 0 }, parts => $_[0]->parts);
}
sub action_show {
check_has_valid_part_type($self->part->part_type);
$self->_set_javascript;
+ $self->_setup_form_action_bar;
my %title_hash = ( part => t8('Add Part'),
assembly => t8('Add Assembly'),
$self->render(
'part/form',
- title => $title_hash{$self->part->part_type},
- show_edit_buttons => $::auth->assert('part_service_assembly_edit'),
+ title => $title_hash{$self->part->part_type},
);
}
sub _set_javascript {
my ($self) = @_;
- $::request->layout->use_javascript("${_}.js") for qw(kivi.Part kivi.PriceRule ckeditor/ckeditor ckeditor/adapters/jquery);
+ $::request->layout->use_javascript("${_}.js") for qw(kivi.Part kivi.File kivi.PriceRule ckeditor/ckeditor ckeditor/adapters/jquery);
$::request->layout->add_javascripts_inline("\$(function(){kivi.PriceRule.load_price_rules_for_part(@{[ $self->part->id ]})});") if $self->part->id;
}
my $params = delete($::form->{part}) || { };
delete $params->{id};
- # never overwrite existing partnumber, should be a read-only field anyway
- delete $params->{partnumber} if $self->part->partnumber;
$self->part->assign_attributes(%{ $params});
$self->part->bin_id(undef) unless $self->part->warehouse_id;
partnumber => t8('Partnumber'),
description => t8('Description'),
},
- with_objects => [ qw(unit_obj) ],
+ with_objects => [ qw(unit_obj classification) ],
);
}
SL::Controller::Helper::GetModels->new(
controller => $_[0],
model => 'Part',
- with_objects => [ qw(unit_obj partsgroup) ],
+ with_objects => [ qw(unit_obj partsgroup classification) ],
disable_plugin => 'paginated',
source => $::form->{multi_items},
sorted => {
my ($self) = @_;
return 1 unless $::form->{part}{part_type} eq 'assortment';
- # skip check for existing parts that have been used
+ # skip item check for existing assortments that have been used
return 1 if ($self->part->id and !$self->part->orphaned);
# new or orphaned parts must have items in $::form->{assortment_items}
return 1 unless $::form->{part}->{part_type} eq 'assembly';
+ # skip item check for existing assembly that have been used
+ return 1 if ($self->part->id and !$self->part->orphaned);
+
unless ( $::form->{assembly_items} and scalar @{$::form->{assembly_items}} ) {
$self->js->run('kivi.Part.set_tab_active_by_name', 'assembly_tab')
->focus('#add_assembly_item_name')
}
# general checking functions
-sub check_next_transnumber_is_free {
- my ($self) = @_;
-
- my ($next_transnumber, $count);
- $self->part->db->with_transaction(sub {
- $next_transnumber = $self->part->get_next_trans_number;
- $count = SL::DB::Manager::Part->get_all_count(where => [ partnumber => $next_transnumber ]);
- return 1;
- }) or die $@;
- $count ? return 0 : return 1;
-}
sub check_part_id {
die t8("Can't load item without a valid part.id") . "\n" unless $::form->{part}{id};
return \@item_objects;
}
+sub _setup_form_action_bar {
+ my ($self) = @_;
+
+ my $may_edit = $::auth->assert('part_service_assembly_edit', 'may fail');
+
+ for my $bar ($::request->layout->get('actionbar')) {
+ $bar->add(
+ combobox => [
+ action => [
+ t8('Save'),
+ call => [ 'kivi.Part.save' ],
+ disabled => !$may_edit ? t8('You do not have the permissions to access this function.') : undef,
+ accesskey => 'enter',
+ ],
+ action => [
+ t8('Use as new'),
+ call => [ 'kivi.Part.use_as_new' ],
+ disabled => !$self->part->id ? t8('The object has not been saved yet.')
+ : !$may_edit ? t8('You do not have the permissions to access this function.')
+ : undef,
+ ],
+ ], # end of combobox "Save"
+
+ action => [
+ t8('Delete'),
+ call => [ 'kivi.Part.delete' ],
+ confirm => t8('Do you really want to delete this object?'),
+ disabled => !$self->part->id ? t8('This object has not been saved yet.')
+ : !$may_edit ? t8('You do not have the permissions to access this function.')
+ : !$self->part->orphaned ? t8('This object has already been used.')
+ : undef,
+ ],
+
+ 'separator',
+
+ action => [
+ t8('History'),
+ call => [ 'kivi.Part.open_history_popup' ],
+ disabled => !$self->part->id ? t8('This object has not been saved yet.')
+ : !$may_edit ? t8('You do not have the permissions to access this function.')
+ : undef,
+ ],
+ );
+ }
+}
+
1;
__END__