Skip to content

202410 feat part printing #400

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
298 changes: 243 additions & 55 deletions SL/Controller/Part.pm
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ use SL::DB::PriceRuleItem;
use SL::DB::PurchaseBasketItem;
use SL::DB::Shop;
use SL::Helper::Flash;
use SL::Helper::CreatePDF qw(:all);
use SL::Helper::PrintOptions;
use SL::Helper::UserPreferences::PartPickerSearch;
use SL::JSON;
use SL::MIME;
use SL::Locale::String qw(t8);
use SL::MoreCommon qw(save_form);
use SL::Presenter::EscapedText qw(escape is_escaped);
Expand All @@ -40,6 +42,7 @@ use Rose::Object::MakeMethods::Generic (
customerprices
orphaned
assortment assortment_items assembly assembly_items
print_options
all_pricegroups all_translations all_partsgroups all_units
all_buchungsgruppen all_payment_terms all_warehouses
parts_classification_filter
Expand Down Expand Up @@ -113,76 +116,123 @@ sub action_add {
sub action_save {
my ($self, %params) = @_;

# checks that depend only on submitted $::form
$self->check_form or return $self->js->render;
my $is_new = !$self->part->id;

my $is_new = !$self->part->id; # $ part gets loaded here
$self->save_with_render_error() or return;

# check that the part hasn't been modified
unless ( $is_new ) {
$self->check_part_not_modified or
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;
}
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);

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;
} else {
# default behaviour after save: reload item, this also resets last_modification!
$self->redirect_to(controller => 'Part', action => 'edit', 'part.id' => $self->part->id);
}
}

$self->parse_form;
sub action_save_and_print {
my ($self) = @_;

my @errors = $self->part->validate;
return $self->js->error(@errors)->render if @errors;
$self->save_with_render_error() or return;
$self->js_reset_part_after_save();
$self->js->flash('info', t8('The item has been saved.'));

my $formname = $::form->{print_options}->{formname};
my $format = $::form->{print_options}->{format};
my $media = $::form->{print_options}->{media};
my $printer_id = $::form->{print_options}->{printer_id};
my $copies = $::form->{print_options}->{copies};
my $language;
if ($::form->{print_options}->{language_id}) {
$language = SL::DB::Manager::Language->find_by(
id => $::form->{print_options}->{language_id}
);
}

if ($is_new) {
# Ensure CVars that should be enabled by default actually are when
# creating new parts.
my @default_valid_configs =
grep { ! $_->{flag_defaults_to_invalid} }
grep { $_->{module} eq 'IC' }
@{ CVar->get_configs() };
eval {

$::form->{"cvar_" . $_->{name} . "_valid"} = 1 for @default_valid_configs;
} else {
$self->{lastcost_modified} = $self->check_lastcost_modified;
}
my $template_ext;
my $template_type;
if ($format =~ /(opendocument|oasis)/i) {
$template_ext = 'odt';
$template_type = 'OpenDocument';
} elsif ($format =~ m{html}i) {
$template_ext = 'html';
$template_type = 'HTML';
}

# $self->part has been loaded, parsed and validated without errors and is ready to be saved
$self->part->db->with_transaction(sub {
# search for the template
my ($template_file, @template_files) = SL::Helper::CreatePDF->find_template(
name => $formname,
extension => $template_ext,
language => $language,
printer_id => $printer_id,
);
if (!defined $template_file) {
die t8('Cannot find matching template for this print request. Please contact your template maintainer. I tried these: #1.', join ', ', map { "'$_'"} @template_files);
}

$self->part->save(cascade => 1);
$self->part->set_lastcost_assemblies_and_assortiments if $self->{lastcost_modified};
my $print_form = Form->new('');
$print_form->{part} = $self->part;

SL::DB::History->new(
trans_id => $self->part->id,
snumbers => 'partnumber_' . $self->part->partnumber,
employee_id => SL::DB::Manager::Employee->current->id,
what_done => 'part',
addition => 'SAVED',
)->save();
# for doc_filename
$print_form->{type} = 'part';
$print_form->{formname} = $formname;
$print_form->{format} = $format;
$print_form->{partnumber} = $self->part->partnumber;
$print_form->{language} = $language && ("_" .
($language->template_code || $language->description)
);

CVar->save_custom_variables(
dbh => $self->part->db->dbh,
module => 'IC',
trans_id => $self->part->id,
variables => $::form, # $::form->{cvar} would be nicer
save_validity => 1,
# for tempalte variables
$print_form->{template_meta}->{language} = $language;
$print_form->{media} = $media;
$print_form->{media} = 'file' if $print_form->{media} eq 'screen';
my $default = SL::DB::Default->get;
$print_form->{employee_company} = $default->company;
$print_form->{currency} = $default->currency->name;

my $document = SL::Helper::CreatePDF->create_pdf(
format => $format,
template_type => $template_type,
template => $template_file,
variables => $print_form,
variable_content_types => {
notes => 'html',
# TODO: html cvars
},
);

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.') . " " . $self->part->displayable_name : t8('The item has been saved.'));
if ($media eq 'screen') {
my $doc_filename = $print_form->generate_attachment_filename();

$self->send_file(
\$document,
type => SL::MIME->mime_type_from_ext($doc_filename),
name => $doc_filename,
js_no_render => 1,
);
} elsif ($media eq 'printer') {
my $printer = SL::DB::Printer->new(id => $printer_id)->load;
$printer->print_document(
copies => $copies,
content => $document,
);

$self->js->flash('info', t8('The document has been sent to the printer \'#1\'.', $printer->printer_description));
} else {
die t8('Media \'#1\' is not supported yet/anymore.', $media);
}

if ( $::form->{callback} ) {
$self->redirect_to($::form->unescape($::form->{callback}) . '&new_parts_id=' . $self->part->id);
1;
} or do {
$self->js
->flash('error', t8("Creating the PDF failed!"))
->flash('error', $@);
};

} else {
# default behaviour after save: reload item, this also resets last_modification!
$self->redirect_to(controller => 'Part', action => 'edit', 'part.id' => $self->part->id);
}
$self->js->render();
}

sub action_save_and_purchase_order {
Expand Down Expand Up @@ -1242,6 +1292,95 @@ sub build_bin_select {
);
}

sub save {
my ($self) = @_;
my @errors = ();

my $is_new = !$self->part->id;

# check that the part hasn't been modified
unless ( $is_new ) {
$self->check_part_not_modified or
return [t8('The document has been changed by another user. Please reopen it in another window and copy the changes to the new window')];
}

$self->parse_form;

@errors = $self->part->validate;
return \@errors if @errors;

if ($is_new) {
# Ensure CVars that should be enabled by default actually are when
# creating new parts.
my @default_valid_configs =
grep { ! $_->{flag_defaults_to_invalid} }
grep { $_->{module} eq 'IC' }
@{ CVar->get_configs() };

$::form->{"cvar_" . $_->{name} . "_valid"} = 1 for @default_valid_configs;
} else {
$self->{lastcost_modified} = $self->check_lastcost_modified;
}

# $self->part has been loaded, parsed and validated without errors and is ready to be saved
$self->part->db->with_transaction(sub {

$self->part->save(cascade => 1);
$self->part->set_lastcost_assemblies_and_assortiments if $self->{lastcost_modified};

SL::DB::History->new(
trans_id => $self->part->id,
snumbers => 'partnumber_' . $self->part->partnumber,
employee_id => SL::DB::Manager::Employee->current->id,
what_done => 'part',
addition => 'SAVED',
)->save();

CVar->save_custom_variables(
dbh => $self->part->db->dbh,
module => 'IC',
trans_id => $self->part->id,
variables => $::form, # $::form->{cvar} would be nicer
save_validity => 1,
);

1;
}) || push(@errors, $self->part->db->error);

return \@errors;
}

sub save_with_render_error {
my ($self) = @_;

# checks that depend only on submitted $::form
if (!$self->check_form) {
$self->js->render();
return 0;
}

my $errors = $self->save();

if (scalar @{ $errors }) {
$self->js->error(t8('The item couldn\'t be saved!'));
$self->js->error(@{ $errors });
$self->js->render();
return 0;
}

return 1;
}

sub js_reset_part_after_save {
my ($self) = @_;

$self->part->load();
$self->js
->val('#part_id', $self->part->id)
->val('#part_partnumber', $self->part->partnumber)
->val('#part_weight_as_number', $self->part->weight_as_number)
->val('#last_modification', $self->part->last_modification . "");
}

# get_set_inits for partpicker

Expand Down Expand Up @@ -1504,6 +1643,24 @@ sub init_parts_classification_filter {
die "no query rules for parts_classification_type " . $::form->{parts_classification_type};
}

sub init_print_options {

my $print_form = Form->new('');
$print_form->{type} = 'part';
$print_form->{printers} = SL::DB::Manager::Printer->get_all_sorted;
$print_form->{languages} = SL::DB::Manager::Language->get_all_sorted;

return SL::Helper::PrintOptions->get_print_options(
form => $print_form,
options => {dialog_name_prefix => 'print_options.',
show_headers => 1,
no_queue => 1,
no_postscript => 1,
no_opendocument => 1,
no_html => 1},
);
}

# simple checks to run on $::form before saving

sub form_check_part_description_exists {
Expand Down Expand Up @@ -1766,7 +1923,13 @@ sub _setup_form_action_bar {
combobox => [
action => [
t8('Export'),
only_if => $self->part->is_assembly || $self->part->is_assortment,
],
action => [
t8('Save and print'),
call => [ 'kivi.Part.show_print_options' ],
disabled => !$may_edit ? t8('You do not have the permissions to access this function.') : undef,
checks => [ 'kivi.validate_form' ],
only_if => !$::form->{inline_create},
],
action => [
$self->part->is_assembly ? t8('Assembly items') : t8('Assortment items'),
Expand Down Expand Up @@ -1934,6 +2097,11 @@ template.

Saves the current part and then reloads the edit page for the part.

=item C<action_save_and_print>

Saves the current part, prints the selected template and then reloads the edit
page for the part.

=item C<action_use_as_new>

Takes the information from the current part, plus any modifications made on the
Expand Down Expand Up @@ -2133,6 +2301,26 @@ Depending on the price_type the lastcost sum or sellprice sum is returned.

Doesn't work for recursive items.

=item C<save>

Helper function for saving the part object in $self->part. Returns a error list
on failure.

=item C<save_with_render_error>

Helper function for checking the form, saving the part object and error
rendering. Returns 1 on success and 0 when a error is rendered.

It can be called like this:

$self->save_with_render_error() or return;

=item C<js_reset_part_after_save>

Helper function for updating changed values after save in the form via js. It
chances 'part_id', 'part_partnumber', 'part_weight_as_number' and
'last_modification'.

=back

=head1 GET SET INITS
Expand Down
3 changes: 3 additions & 0 deletions SL/Form.pm
Original file line number Diff line number Diff line change
Expand Up @@ -1123,6 +1123,8 @@ sub get_formname_translation {
letter => $main::locale->text('Letter'),
ic_supply => $main::locale->text('Intra-Community supply'),
statement => $main::locale->text('Statement'),
part_info => $main::locale->text('Part info'),
part_label => $main::locale->text('Part label'),
);

$main::lxdebug->leave_sub();
Expand Down Expand Up @@ -1152,6 +1154,7 @@ sub get_number_prefix_for_type {
: ($self->{type} =~ /_quotation/) ? 'quo'
: ($self->{type} =~ /_delivery_order$/) ? 'do'
: ($self->{type} =~ /letter/) ? 'letter'
: ($self->{type} =~ /part/) ? 'part'
: 'ord';

# better default like this?
Expand Down
Loading