Revision f49e9deb
Von Sven Schöling vor 12 Monaten hinzugefügt
- ID f49e9deb77039f7cac10d23ab93ebb9cd66b8870
- Vorgänger b0c61725
SL/Controller/Invoice.pm | ||
---|---|---|
package SL::Controller::Invoice;
|
||
|
||
use strict;
|
||
use parent qw(SL::Controller::Base);
|
||
|
||
use SL::DB::Invoice;
|
||
use SL::Helper::Flash qw(flash flash_later);
|
||
use SL::HTML::Util;
|
||
use SL::Presenter::Tag qw(select_tag hidden_tag div_tag);
|
||
use SL::Locale::String qw(t8);
|
||
use SL::SessionFile::Random;
|
||
use SL::PriceSource;
|
||
use SL::File;
|
||
use SL::YAML;
|
||
use SL::DB::Helper::RecordLink qw(set_record_link_conversions RECORD_ID RECORD_TYPE_REF RECORD_ITEM_ID RECORD_ITEM_TYPE_REF);
|
||
use SL::DB::Helper::TypeDataProxy;
|
||
use SL::DB::Helper::Record qw(get_object_name_from_type get_class_from_type);
|
||
use SL::Model::Record;
|
||
use SL::DB::Invoice::TypeData qw(:types);
|
||
use SL::DB::Order::TypeData qw(:types);
|
||
use SL::DB::DeliveryOrder::TypeData qw(:types);
|
||
use SL::DB::Reclamation::TypeData qw(:types);
|
||
|
||
use SL::Helper::CreatePDF qw(:all);
|
||
use SL::Helper::PrintOptions;
|
||
use SL::Helper::ShippedQty;
|
||
use SL::Helper::UserPreferences::DisplayPreferences;
|
||
use SL::Helper::UserPreferences::PositionsScrollbar;
|
||
use SL::Helper::UserPreferences::UpdatePositions;
|
||
|
||
use SL::Controller::Helper::GetModels;
|
||
|
||
use List::Util qw(first sum0);
|
||
use List::UtilsBy qw(sort_by uniq_by);
|
||
use List::MoreUtils qw(uniq any none pairwise first_index);
|
||
use File::Spec;
|
||
use Sort::Naturally;
|
||
|
||
use Rose::Object::MakeMethods::Generic
|
||
(
|
||
scalar => [ qw(item_ids_to_delete is_custom_shipto_to_delete) ],
|
||
'scalar --get_set_init' => [ qw(invoice valid_types type cv p all_price_factors
|
||
search_cvpartnumber show_update_button
|
||
part_picker_classification_ids
|
||
type_data) ],
|
||
);
|
||
|
||
|
||
# safety
|
||
__PACKAGE__->run_before('check_auth');
|
||
__PACKAGE__->run_before('check_auth_for_edit',
|
||
except => [ qw(edit price_popup load_second_rows) ]);
|
||
|
||
#
|
||
# actions
|
||
#
|
||
|
||
# add a newinvoice
|
||
sub action_add {
|
||
my ($self) = @_;
|
||
|
||
$self->invoice(SL::Model::Record->update_after_new($self->invoice));
|
||
|
||
$self->invoice->gldate($self->invoice->payment_terms ? $self->invoice->payment_terms->calc_date(reference_date => $self->invoice->transdate) : $self->invoice->transdate);
|
||
|
||
$self->pre_render();
|
||
|
||
if (!$::form->{form_validity_token}) {
|
||
$::form->{form_validity_token} = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_SALES_INVOICE_POST())->token;
|
||
}
|
||
|
||
$self->render(
|
||
'invoice/form',
|
||
title => $self->type_data->text('add'),
|
||
%{$self->{template_args}}
|
||
);
|
||
}
|
||
|
||
|
||
|
||
#
|
||
# helpers
|
||
#
|
||
|
||
sub init_valid_types {
|
||
$_[0]->type_data->valid_types;
|
||
}
|
||
|
||
sub init_type {
|
||
my ($self) = @_;
|
||
|
||
my $type = $self->invoice->record_type;
|
||
if (none { $type eq $_ } @{$self->valid_types}) {
|
||
die "Not a valid type for invoice";
|
||
}
|
||
|
||
$self->type($type);
|
||
}
|
||
|
||
sub init_cv {
|
||
my ($self) = @_;
|
||
|
||
return $self->type_data->properties('customervendor');
|
||
}
|
||
|
||
sub init_search_cvpartnumber {
|
||
my ($self) = @_;
|
||
|
||
my $user_prefs = SL::Helper::UserPreferences::PartPickerSearch->new();
|
||
my $search_cvpartnumber;
|
||
$search_cvpartnumber = !!$user_prefs->get_sales_search_customer_partnumber() if $self->cv eq 'customer';
|
||
$search_cvpartnumber = !!$user_prefs->get_purchase_search_makemodel() if $self->cv eq 'vendor';
|
||
|
||
return $search_cvpartnumber;
|
||
}
|
||
|
||
sub init_show_update_button {
|
||
my ($self) = @_;
|
||
|
||
!!SL::Helper::UserPreferences::UpdatePositions->new()->get_show_update_button();
|
||
}
|
||
|
||
sub init_p {
|
||
SL::Presenter->get;
|
||
}
|
||
|
||
sub init_invoice {
|
||
$_[0]->make_invoice;
|
||
}
|
||
|
||
sub init_all_price_factors {
|
||
SL::DB::Manager::PriceFactor->get_all;
|
||
}
|
||
|
||
sub init_part_picker_classification_ids {
|
||
my ($self) = @_;
|
||
|
||
return [ map { $_->id } @{ SL::DB::Manager::PartClassification->get_all(
|
||
where => $self->type_data->part_classification_query()) } ];
|
||
}
|
||
|
||
sub init_type_data {
|
||
my ($self) = @_;
|
||
SL::DB::Helper::TypeDataProxy->new('SL::DB::Invoice', $self->invoice->record_type);
|
||
}
|
||
|
||
sub check_auth {
|
||
my ($self) = @_;
|
||
$::auth->assert($self->type_data->rights('view'));
|
||
}
|
||
|
||
sub check_auth_for_edit {
|
||
my ($self) = @_;
|
||
$::auth->assert($self->type_data->rights('edit'));
|
||
}
|
||
|
||
|
||
#
|
||
# internal
|
||
#
|
||
|
||
|
||
sub pre_render {
|
||
my ($self) = @_;
|
||
|
||
$self->{all_taxzones} = SL::DB::Manager::TaxZone->get_all_sorted();
|
||
$self->{all_currencies} = SL::DB::Manager::Currency->get_all_sorted();
|
||
$self->{all_departments} = SL::DB::Manager::Department->get_all_sorted();
|
||
$self->{all_languages} = SL::DB::Manager::Language->get_all_sorted( query => [ or => [ obsolete => 0, id => $self->invoice->language_id ] ] );
|
||
$self->{all_employees} = SL::DB::Manager::Employee->get_all(where => [ or => [ id => $self->invoice->employee_id,
|
||
deleted => 0 ] ],
|
||
sort_by => 'name');
|
||
$self->{all_salesmen} = SL::DB::Manager::Employee->get_all(where => [ or => [ id => $self->invoice->salesman_id,
|
||
deleted => 0 ] ],
|
||
sort_by => 'name');
|
||
$self->{all_payment_terms} = SL::DB::Manager::PaymentTerm->get_all_sorted(where => [ or => [ id => $self->invoice->payment_id,
|
||
obsolete => 0 ] ]);
|
||
$self->{all_delivery_terms} = SL::DB::Manager::DeliveryTerm->get_valid($self->invoice->delivery_term_id);
|
||
$self->{current_employee_id} = SL::DB::Manager::Employee->current->id;
|
||
$self->{positions_scrollbar_height} = SL::Helper::UserPreferences::PositionsScrollbar->new()->get_height();
|
||
|
||
my $print_form = Form->new('');
|
||
$print_form->{type} = $self->type;
|
||
$print_form->{printers} = SL::DB::Manager::Printer->get_all_sorted;
|
||
$self->{print_options} = 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 => 0,
|
||
no_html => 0},
|
||
);
|
||
|
||
foreach my $item (@{$self->invoice->items}) {
|
||
my $price_source = SL::PriceSource->new(record_item => $item, record => $self->invoice);
|
||
$item->active_price_source( $price_source->price_from_source( $item->active_price_source ));
|
||
$item->active_discount_source($price_source->discount_from_source($item->active_discount_source));
|
||
}
|
||
|
||
|
||
# ?! TODO: does invoices need stock info?
|
||
# if (any { $self->type eq $_ } (INVOICE_TYPE(), INVOICE_FOR_ADVANCE_PAYMENT_TYPE(), INVOICE_FOR_ADVANCE_PAYMENT_STORNO_TYPE(), FINAL_INVOICE_TYPE(), INVOICE_STORNO_TYPE(), CREDIT_NOTE_TYPE(), CREDIT_NOTE_STORNO_TYPE())) {
|
||
# # Calculate shipped qtys here to prevent calling calculate for every item via the items method.
|
||
# # Do not use write_to_objects to prevent order->delivered to be set, because this should be
|
||
# # the value from db, which can be set manually or is set when linked delivery orders are saved.
|
||
# SL::Helper::ShippedQty->new->calculate($self->record)->write_to(\@{$self->order->items});
|
||
# }
|
||
|
||
if ($self->invoice->number && $::instance_conf->get_webdav) {
|
||
my $webdav = SL::Webdav->new(
|
||
type => $self->type,
|
||
number => $self->invoice->number,
|
||
);
|
||
my @all_objects = $webdav->get_all_objects;
|
||
@{ $self->{template_args}->{WEBDAV} } = map { { name => $_->filename,
|
||
type => t8('File'),
|
||
link => File::Spec->catfile($_->full_filedescriptor),
|
||
} } @all_objects;
|
||
}
|
||
|
||
# if ( (any { $self->type eq $_ } (SALES_QUOTATION_TYPE(), SALES_ORDER_INTAKE_TYPE(), SALES_ORDER_TYPE()))
|
||
# && $::instance_conf->get_transport_cost_reminder_article_number_id ) {
|
||
# $self->{template_args}->{transport_cost_reminder_article} = SL::DB::Part->new(id => $::instance_conf->get_transport_cost_reminder_article_number_id)->load;
|
||
# }
|
||
$self->{template_args}->{longdescription_dialog_size_percentage} = SL::Helper::UserPreferences::DisplayPreferences->new()->get_longdescription_dialog_size_percentage();
|
||
|
||
$self->get_item_cvpartnumber($_) for @{$self->invoice->items_sorted};
|
||
|
||
# $self->{template_args}->{num_phone_notes} = scalar @{ $self->order->phone_notes || [] };
|
||
|
||
$::request->{layout}->use_javascript("${_}.js") for qw(kivi.Validator kivi.SalesPurchase kivi.Invoice kivi.File
|
||
calculate_qty follow_up show_history);
|
||
$self->setup_edit_action_bar;
|
||
}
|
||
|
||
|
||
sub setup_edit_action_bar {
|
||
my ($self, %params) = @_;
|
||
|
||
my $change_never = $::instance_conf->get_is_changeable == 0;
|
||
my $change_on_same_day_only = $::instance_conf->get_is_changeable == 2 && $self->invoice->gldate->clone->truncate(to => 'day') != DateTime->today;
|
||
my $payments_balanced = 0; #($::form->{oldtotalpaid} == 0); # TODO: don't rely on form
|
||
my $has_storno = 0; # $self->invoice->linked_record('storno'); # TODO: linked record?
|
||
my $may_edit_create = $::auth->assert($self->type_data->rights('edit'), 'may fail');
|
||
my $factur_x_enabled = $self->invoice->customer && $self->invoice->customer->create_zugferd_invoices_for_this_customer;
|
||
my ($is_linked_bank_transaction, $warn_unlinked_delivery_order);
|
||
if ($::form->{id}
|
||
&& SL::DB::Default->get->payments_changeable != 0
|
||
&& SL::DB::Manager::BankTransactionAccTrans->find_by(ar_id => $::form->{id})) {
|
||
|
||
$is_linked_bank_transaction = 1;
|
||
}
|
||
if ($::instance_conf->get_warn_no_delivery_order_for_invoice && !$self->invoice->id) {
|
||
$warn_unlinked_delivery_order = 1 unless $::form->{convert_from_do_ids};
|
||
}
|
||
|
||
my $has_further_invoice_for_advance_payment;
|
||
if ($self->invoice->id && $self->invoice->is_type(INVOICE_FOR_ADVANCE_PAYMENT_TYPE())) {
|
||
my $lr = $self->invoice->linked_records(direction => 'to', to => ['Invoice']);
|
||
$has_further_invoice_for_advance_payment = any {'SL::DB::Invoice' eq ref $_ && "invoice_for_advance_payment" eq $_->type} @$lr;
|
||
}
|
||
|
||
my $has_final_invoice;
|
||
if ($self->invoice->id && $self->invoice->is_type(INVOICE_FOR_ADVANCE_PAYMENT_TYPE())) {
|
||
my $lr = $self->invoice->linked_records(direction => 'to', to => ['Invoice']);
|
||
$has_final_invoice = any {'SL::DB::Invoice' eq ref $_ && "final_invoice" eq $_->invoice_type} @$lr;
|
||
}
|
||
|
||
my $is_invoice_for_advance_payment_from_order;
|
||
if ($self->invoice->id && $self->invoice->is_type(INVOICE_FOR_ADVANCE_PAYMENT_TYPE())) {
|
||
my $lr = $self->invoice->linked_records(direction => 'from', from => ['Order']);
|
||
$is_invoice_for_advance_payment_from_order = scalar @$lr >= 1;
|
||
}
|
||
|
||
my $locked = 0; # TODO: get from... somewhere
|
||
|
||
# add readonly state in tmpl_vars
|
||
# $tmpl_var->{readonly} = !$may_edit_create ? 1
|
||
# : $form->{locked} ? 1
|
||
# : $form->{storno} ? 1
|
||
# : ($form->{id} && $change_never) ? 1
|
||
# : ($form->{id} && $change_on_same_day_only) ? 1
|
||
# : $is_linked_bank_transaction ? 1
|
||
# : 0;
|
||
|
||
|
||
|
||
for my $bar ($::request->layout->get('actionbar')) {
|
||
$bar->add(
|
||
action => [
|
||
t8('Update'),
|
||
submit => [ '#form', { action => "update" } ],
|
||
disabled => !$may_edit_create ? t8('You must not change this invoice.')
|
||
: $locked ? t8('The billing period has already been locked.')
|
||
: undef,
|
||
id => 'update_button',
|
||
accesskey => 'enter',
|
||
],
|
||
|
||
combobox => [
|
||
action => [
|
||
t8('Post'),
|
||
submit => [ '#form', { action => "post" } ],
|
||
checks => [ 'kivi.validate_form' ],
|
||
confirm => t8('The invoice is not linked with a sales delivery order. Post anyway?') x !!$warn_unlinked_delivery_order,
|
||
disabled => !$may_edit_create ? t8('You must not change this invoice.')
|
||
: $locked ? t8('The billing period has already been locked.')
|
||
: $self->invoice->storno ? t8('A canceled invoice cannot be posted.')
|
||
: $self->invoice->id && $change_never ? t8('Changing invoices has been disabled in the configuration.')
|
||
: $self->invoice->id && $change_on_same_day_only ? t8('Invoices can only be changed on the day they are posted.')
|
||
: $is_linked_bank_transaction ? t8('This transaction is linked with a bank transaction. Please undo and redo the bank transaction booking if needed.')
|
||
: undef,
|
||
],
|
||
action => [
|
||
t8('Post and Close'),
|
||
submit => [ '#form', { action => "post_and_close" } ],
|
||
checks => [ 'kivi.validate_form' ],
|
||
confirm => t8('The invoice is not linked with a sales delivery order. Post anyway?') x !!$warn_unlinked_delivery_order,
|
||
disabled => !$may_edit_create ? t8('You must not change this invoice.')
|
||
: $locked ? t8('The billing period has already been locked.')
|
||
: $self->invoice->storno ? t8('A canceled invoice cannot be posted.')
|
||
: $self->invoice->id && $change_never ? t8('Changing invoices has been disabled in the configuration.')
|
||
: $self->invoice->id && $change_on_same_day_only ? t8('Invoices can only be changed on the day they are posted.')
|
||
: $is_linked_bank_transaction ? t8('This transaction is linked with a bank transaction. Please undo and redo the bank transaction booking if needed.')
|
||
: undef,
|
||
],
|
||
action => [
|
||
t8('Post Payment'),
|
||
submit => [ '#form', { action => "post_payment" } ],
|
||
checks => [ 'kivi.validate_form' ],
|
||
disabled => !$may_edit_create ? t8('You must not change this invoice.')
|
||
: !$self->invoice->id ? t8('This invoice has not been posted yet.')
|
||
: $is_linked_bank_transaction ? t8('This transaction is linked with a bank transaction. Please undo and redo the bank transaction booking if needed.')
|
||
: undef,
|
||
only_if => $self->invoice->record_type ne "invoice_for_advance_payment",
|
||
],
|
||
action => [ t8('Mark as paid'),
|
||
submit => [ '#form', { action => "mark_as_paid" } ],
|
||
confirm => t8('This will remove the invoice from showing as unpaid even if the unpaid amount does not match the amount. Proceed?'),
|
||
disabled => !$may_edit_create ? t8('You must not change this invoice.')
|
||
: !$self->invoice->id ? t8('This invoice has not been posted yet.')
|
||
: undef,
|
||
only_if => ($::instance_conf->get_is_show_mark_as_paid && $self->invoice->record_type ne "invoice_for_advance_payment")
|
||
|| $self->invoice->record_type eq 'final_invoice',
|
||
],
|
||
], # end of combobox "Post"
|
||
|
||
combobox => [
|
||
action => [ t8('Storno'),
|
||
submit => [ '#form', { action => "storno" } ],
|
||
confirm => t8('Do you really want to cancel this invoice?'),
|
||
checks => [ 'kivi.validate_form' ],
|
||
disabled => !$may_edit_create ? t8('You must not change this invoice.')
|
||
: $locked ? t8('The billing period has already been locked.')
|
||
: !$self->invoice->id ? t8('This invoice has not been posted yet.')
|
||
: $self->invoice->storno ? t8('Cannot storno storno invoice!')
|
||
: !$payments_balanced ? t8('Cancelling is disallowed. Either undo or balance the current payments until the open amount matches the invoice amount')
|
||
: undef,
|
||
],
|
||
action => [ t8('Delete'),
|
||
submit => [ '#form', { action => "delete" } ],
|
||
confirm => t8('Do you really want to delete this object?'),
|
||
checks => [ 'kivi.validate_form' ],
|
||
disabled => !$may_edit_create ? t8('You must not change this invoice.')
|
||
: !$self->invoice->id ? t8('This invoice has not been posted yet.')
|
||
: $locked ? t8('The billing period has already been locked.')
|
||
: $change_never ? t8('Changing invoices has been disabled in the configuration.')
|
||
: $change_on_same_day_only ? t8('Invoices can only be changed on the day they are posted.')
|
||
: $has_storno ? t8('Can only delete the "Storno zu" part of the cancellation pair.')
|
||
: undef,
|
||
],
|
||
], # end of combobox "Storno"
|
||
|
||
'separator',
|
||
|
||
combobox => [
|
||
action => [ t8('Workflow') ],
|
||
action => [
|
||
t8('Use As New'),
|
||
submit => [ '#form', { action => "use_as_new" } ],
|
||
checks => [ 'kivi.validate_form' ],
|
||
disabled => !$may_edit_create ? t8('You must not change this invoice.')
|
||
: !$self->invoice->id ? t8('This invoice has not been posted yet.')
|
||
: undef,
|
||
],
|
||
action => [
|
||
t8('Further Invoice for Advance Payment'),
|
||
submit => [ '#form', { action => "further_invoice_for_advance_payment" } ],
|
||
checks => [ 'kivi.validate_form' ],
|
||
disabled => !$may_edit_create ? t8('You must not change this invoice.')
|
||
: !$self->invoice->id ? t8('This invoice has not been posted yet.')
|
||
: $has_further_invoice_for_advance_payment ? t8('This invoice has already a further invoice for advanced payment.')
|
||
: $has_final_invoice ? t8('This invoice has already a final invoice.')
|
||
: $is_invoice_for_advance_payment_from_order ? t8('This invoice was added from an order. See there.')
|
||
: undef,
|
||
only_if => $self->invoice->record_type eq "invoice_for_advance_payment",
|
||
],
|
||
action => [
|
||
t8('Final Invoice'),
|
||
submit => [ '#form', { action => "final_invoice" } ],
|
||
checks => [ 'kivi.validate_form' ],
|
||
disabled => !$may_edit_create ? t8('You must not change this invoice.')
|
||
: !$self->invoice->id ? t8('This invoice has not been posted yet.')
|
||
: $has_further_invoice_for_advance_payment ? t8('This invoice has a further invoice for advanced payment.')
|
||
: $has_final_invoice ? t8('This invoice has already a final invoice.')
|
||
: $is_invoice_for_advance_payment_from_order ? t8('This invoice was added from an order. See there.')
|
||
: undef,
|
||
only_if => $self->invoice->is_type("invoice_for_advance_payment"),
|
||
],
|
||
action => [
|
||
t8('Credit Note'),
|
||
submit => [ '#form', { action => "credit_note" } ],
|
||
checks => [ 'kivi.validate_form' ],
|
||
disabled => !$may_edit_create ? t8('You must not change this invoice.')
|
||
: $self->invoice->is_type("credit_note") ? t8('Credit notes cannot be converted into other credit notes.')
|
||
: !$self->invoice->id ? t8('This invoice has not been posted yet.')
|
||
: $self>invocie->storno ? t8('A canceled invoice cannot be used. Please undo the cancellation first.')
|
||
: undef,
|
||
],
|
||
action => [
|
||
t8('Sales Order'),
|
||
submit => [ '#form', { action => "order" } ],
|
||
checks => [ 'kivi.validate_form' ],
|
||
disabled => !$self->invoice->id ? t8('This invoice has not been posted yet.') : undef,
|
||
],
|
||
action => [
|
||
t8('Reclamation'),
|
||
submit => ['#form', { action => "sales_reclamation" }], # can't call Reclamation directly
|
||
disabled => !$self->invoice->id ? t8('This invoice has not been posted yet.') : undef,
|
||
only_if => ($self->invoice->is_type('invoice') && !$::form->{storno}),
|
||
],
|
||
], # end of combobox "Workflow"
|
||
|
||
combobox => [
|
||
action => [ t8('Export') ],
|
||
action => [
|
||
($self->invoice->id ? t8('Print') : t8('Preview')),
|
||
call => [ 'kivi.SalesPurchase.show_print_dialog', $self->invoice->id ? 'print' : 'preview' ],
|
||
checks => [ 'kivi.validate_form' ],
|
||
disabled => !$may_edit_create ? t8('You must not print this invoice.')
|
||
: !$self->invoice->id && $locked ? t8('The billing period has already been locked.')
|
||
: undef,
|
||
],
|
||
action => [ t8('Print and Post'),
|
||
call => [ 'kivi.SalesPurchase.show_print_dialog', 'print_and_post' ],
|
||
checks => [ 'kivi.validate_form' ],
|
||
confirm => t8('The invoice is not linked with a sales delivery order. Post anyway?') x !!$warn_unlinked_delivery_order,
|
||
disabled => !$may_edit_create ? t8('You must not change this invoice.')
|
||
: $locked ? t8('The billing period has already been locked.')
|
||
: $self->invoice->storno ? t8('A canceled invoice cannot be posted.')
|
||
: ($self->invoice->id && $change_never) ? t8('Changing invoices has been disabled in the configuration.')
|
||
: ($self->invoice->id && $change_on_same_day_only) ? t8('Invoices can only be changed on the day they are posted.')
|
||
: $is_linked_bank_transaction ? t8('This transaction is linked with a bank transaction. Please undo and redo the bank transaction booking if needed.')
|
||
: undef,
|
||
],
|
||
action => [ t8('E Mail'),
|
||
call => [ 'kivi.SalesPurchase.show_email_dialog' ],
|
||
checks => [ 'kivi.validate_form' ],
|
||
disabled => !$may_edit_create ? t8('You must not print this invoice.')
|
||
: !$self->invoice->id ? t8('This invoice has not been posted yet.')
|
||
: $self->invoice->customer->postal_invoice ? t8('This customer wants a postal invoices.')
|
||
: undef,
|
||
],
|
||
action => [ t8('Factur-X/ZUGFeRD'),
|
||
submit => [ '#form', { action => "download_factur_x_xml" } ],
|
||
checks => [ 'kivi.validate_form' ],
|
||
disabled => !$may_edit_create ? t8('You must not print this invoice.')
|
||
: !$self->invoice->id ? t8('This invoice has not been posted yet.')
|
||
: !$factur_x_enabled ? t8('Creating Factur-X/ZUGFeRD invoices is not enabled for this customer.')
|
||
: undef,
|
||
],
|
||
], # end of combobox "Export"
|
||
|
||
combobox => [
|
||
action => [ t8('more') ],
|
||
action => [
|
||
t8('History'),
|
||
call => [ 'set_history_window', $self->invoice->id * 1, 'glid' ],
|
||
disabled => !$self->invoice->id ? t8('This invoice has not been posted yet.') : undef,
|
||
],
|
||
action => [
|
||
t8('Follow-Up'),
|
||
call => [ 'follow_up_window' ],
|
||
disabled => !$self->invoice->id ? t8('This invoice has not been posted yet.') : undef,
|
||
],
|
||
action => [
|
||
t8('Drafts'),
|
||
call => [ 'kivi.Draft.popup', 'is', 'invoice', $::form->{draft_id}, $::form->{draft_description} ],
|
||
disabled => !$may_edit_create ? t8('You must not change this invoice.')
|
||
: $self->invoice->id ? t8('This invoice has already been posted.')
|
||
: $locked ? t8('The billing period has already been locked.')
|
||
: undef,
|
||
],
|
||
], # end of combobox "more"
|
||
);
|
||
}
|
||
}
|
||
|
||
# load or create a new order object
|
||
#
|
||
# And assign changes from the form to this object.
|
||
# If the order is loaded from db, check if items are deleted in the form,
|
||
# remove them form the object and collect them for removing from db on saving.
|
||
# Then create/update items from form (via make_item) and add them.
|
||
sub make_invoice {
|
||
my ($self) = @_;
|
||
|
||
# add_items adds items to an order with no items for saving, but they cannot
|
||
# be retrieved via items until the order is saved. Adding empty items to new
|
||
# order here solves this problem.
|
||
my $invoice;
|
||
$invoice = SL::DB::Invoice->new(id => $::form->{id})->load(with => [ 'invoiceitems', 'invoiceitems.part' ]) if $::form->{id};
|
||
$invoice ||= SL::DB::Invoice->new(invoiceitems => [],
|
||
record_type => $::form->{type},
|
||
currency_id => $::instance_conf->get_currency_id(),);
|
||
|
||
my $cv_id_method = $invoice->type_data->properties('customervendor'). '_id';
|
||
if (!$::form->{id} && $::form->{$cv_id_method}) {
|
||
$invoice->$cv_id_method($::form->{$cv_id_method});
|
||
$invoice = SL::Model::Record->update_after_customer_vendor_change($invoice);
|
||
}
|
||
|
||
my $form_invoiceitems = delete $::form->{invoice}->{invoiceitems};
|
||
|
||
$invoice->assign_attributes(%{$::form->{invoice}});
|
||
|
||
$self->setup_custom_shipto_from_form($invoice, $::form);
|
||
|
||
# remove deleted items
|
||
$self->item_ids_to_delete([]);
|
||
foreach my $idx (reverse 0..$#{$invoice->invoiceitems}) {
|
||
my $item = $invoice->invoiceitems->[$idx];
|
||
if (none { $item->id == $_->{id} } @{$form_invoiceitems}) {
|
||
splice @{$invoice->invoiceitems}, $idx, 1;
|
||
push @{$self->item_ids_to_delete}, $item->id;
|
||
}
|
||
}
|
||
|
||
my @items;
|
||
my $pos = 1;
|
||
foreach my $form_attr (@{$form_invoiceitems}) {
|
||
my $item = make_item($invoice, $form_attr);
|
||
$item->position($pos);
|
||
push @items, $item;
|
||
$pos++;
|
||
}
|
||
$invoice->add_items(grep {!$_->id} @items);
|
||
|
||
return $invoice;
|
||
}
|
||
|
||
# create or update items from form
|
||
#
|
||
# Make item objects from form values. For items already existing read from db.
|
||
# Create a new item else. And assign attributes.
|
||
sub make_item {
|
||
my ($record, $attr) = @_;
|
||
|
||
my $item;
|
||
$item = first { $_->id == $attr->{id} } @{$record->items} if $attr->{id};
|
||
|
||
my $is_new = !$item;
|
||
|
||
# add_custom_variables adds cvars to an orderitem with no cvars for saving, but
|
||
# they cannot be retrieved via custom_variables until the order/orderitem is
|
||
# saved. Adding empty custom_variables to new orderitem here solves this problem.
|
||
$item ||= SL::DB::InvoiceItem->new(custom_variables => []);
|
||
|
||
$item->assign_attributes(%$attr);
|
||
|
||
if ($is_new) {
|
||
my $texts = get_part_texts($item->part, $record->language_id);
|
||
$item->longdescription($texts->{longdescription}) if !defined $attr->{longdescription};
|
||
$item->project_id($record->globalproject_id) if !defined $attr->{project_id};
|
||
$item->lastcost($record->is_sales ? $item->part->lastcost : 0) if !defined $attr->{lastcost_as_number};
|
||
}
|
||
|
||
return $item;
|
||
}
|
||
|
||
# setup custom shipto from form
|
||
#
|
||
# The dialog returns form variables starting with 'shipto' and cvars starting
|
||
# with 'shiptocvar_'.
|
||
# Mark it to be deleted if a shipto from master data is selected
|
||
# (i.e. order has a shipto).
|
||
# Else, update or create a new custom shipto. If the fields are empty, it
|
||
# will not be saved on save.
|
||
sub setup_custom_shipto_from_form {
|
||
my ($self, $record, $form) = @_;
|
||
|
||
if ($record->shipto) {
|
||
$self->is_custom_shipto_to_delete(1);
|
||
} else {
|
||
my $custom_shipto = $record->custom_shipto || $record->custom_shipto(SL::DB::Shipto->new(module => 'AR', custom_variables => []));
|
||
|
||
my $shipto_cvars = {map { my ($key) = m{^shiptocvar_(.+)}; $key => delete $form->{$_}} grep { m{^shiptocvar_} } keys %$form};
|
||
my $shipto_attrs = {map { $_ => delete $form->{$_}} grep { m{^shipto} } keys %$form};
|
||
|
||
$custom_shipto->assign_attributes(%$shipto_attrs);
|
||
$custom_shipto->cvar_by_name($_)->value($shipto_cvars->{$_}) for keys %$shipto_cvars;
|
||
}
|
||
}
|
||
|
||
1;
|
SL/DB/Invoice.pm | ||
---|---|---|
use SL::DB::Helper::RecordLink qw(RECORD_ID RECORD_TYPE_REF RECORD_ITEM_ID RECORD_ITEM_TYPE_REF);
|
||
use SL::DB::Helper::SalesPurchaseInvoice;
|
||
use SL::DB::Helper::TransNumberGenerator;
|
||
use SL::DB::Helper::TypeDataProxy;
|
||
use SL::DB::Helper::ZUGFeRD qw(:CREATE);
|
||
use SL::Locale::String qw(t8);
|
||
|
||
... | ... | |
__PACKAGE__->before_save('_before_save_set_invnumber');
|
||
__PACKAGE__->after_save('_after_save_link_records');
|
||
|
||
use Rose::Object::MakeMethods::Generic (
|
||
'scalar --get_set_init' => [ qw(record_type) ],
|
||
);
|
||
|
||
# hooks
|
||
|
||
sub _before_save_set_invnumber {
|
||
... | ... | |
sub items { goto &invoiceitems; }
|
||
sub add_items { goto &add_invoiceitems; }
|
||
sub record_number { goto &invnumber; };
|
||
sub record_type { goto &invoice_type; };
|
||
#sub record_type { goto &invoice_type; };
|
||
|
||
sub is_sales {
|
||
# For compatibility with Order, DeliveryOrder
|
||
... | ... | |
}
|
||
}
|
||
|
||
sub init_record_type {
|
||
goto &invoice_type;
|
||
}
|
||
|
||
sub invoice_type {
|
||
my ($self) = @_;
|
||
|
||
... | ... | |
goto &customer;
|
||
}
|
||
|
||
sub is_type {
|
||
return shift->record_type eq shift;
|
||
}
|
||
|
||
sub number {
|
||
my $self = shift;
|
||
|
||
my $nr_key = $self->type_data->properties('nr_key');
|
||
return $self->$nr_key(@_);
|
||
}
|
||
|
||
sub link {
|
||
my ($self) = @_;
|
||
|
||
... | ... | |
return $self->netamount; # already matches base currency
|
||
}
|
||
|
||
sub type_data {
|
||
SL::DB::Helper::TypeDataProxy->new(ref $_[0], $_[0]->record_type);
|
||
}
|
||
|
||
1;
|
||
|
||
__END__
|
SL/DB/Invoice/TypeData.pm | ||
---|---|---|
worflow_needed => 0,
|
||
},
|
||
defaults => {
|
||
# TODO
|
||
# aka: duedate
|
||
reqdate => sub {
|
||
#$invoice->payment_terms ? $invoice->payment_terms->calc_date(reference_date => $::form->{invdate})->to_kivitendo : $invoice->transdate;
|
||
DateTime->today
|
||
},
|
||
},
|
||
part_classification_query => [ "used_for_sales" => 1 ],
|
||
part_classification_query => [ "used_for_sale" => 1 ],
|
||
rights => {
|
||
# TODO
|
||
edit => "invoice_edit",
|
||
view => "invoice_edit | sales_invoice_view",
|
||
},
|
||
features => {
|
||
price_tax => 1,
|
||
... | ... | |
defaults => {
|
||
# TODO
|
||
},
|
||
part_classification_query => [ "used_for_sales" => 1 ],
|
||
part_classification_query => [ "used_for_sale" => 1 ],
|
||
rights => {
|
||
# TODO
|
||
},
|
||
... | ... | |
defaults => {
|
||
# TODO
|
||
},
|
||
part_classification_query => [ "used_for_sales" => 1 ],
|
||
part_classification_query => [ "used_for_sale" => 1 ],
|
||
rights => {
|
||
# TODO
|
||
},
|
||
... | ... | |
defaults => {
|
||
# TODO
|
||
},
|
||
part_classification_query => [ "used_for_sales" => 1 ],
|
||
part_classification_query => [ "used_for_sale" => 1 ],
|
||
rights => {
|
||
# TODO
|
||
},
|
||
... | ... | |
defaults => {
|
||
# TODO
|
||
},
|
||
part_classification_query => [ "used_for_sales" => 1 ],
|
||
part_classification_query => [ "used_for_sale" => 1 ],
|
||
rights => {
|
||
# TODO
|
||
},
|
||
... | ... | |
defaults => {
|
||
# TODO
|
||
},
|
||
part_classification_query => [ "used_for_sales" => 1 ],
|
||
part_classification_query => [ "used_for_sale" => 1 ],
|
||
rights => {
|
||
# TODO
|
||
},
|
||
... | ... | |
defaults => {
|
||
# TODO
|
||
},
|
||
part_classification_query => [ "used_for_sales" => 1 ],
|
||
part_classification_query => [ "used_for_sale" => 1 ],
|
||
rights => {
|
||
# TODO
|
||
},
|
SL/Model/Record.pm | ||
---|---|---|
|
||
$new_record->transdate(DateTime->now_local());
|
||
|
||
my $default_reqdate = $new_record->type_data->defaults('reqdate');
|
||
$new_record->reqdate($default_reqdate);
|
||
if ($new_record->can('reqdate')) {
|
||
my $default_reqdate = $new_record->type_data->defaults('reqdate');
|
||
$new_record->reqdate($default_reqdate);
|
||
}
|
||
|
||
return $new_record;
|
||
}
|
templates/webpages/invoice/form.html | ||
---|---|---|
[%- USE T8 %]
|
||
[%- USE LxERP %]
|
||
[%- USE L %]
|
||
[%- USE HTML %]
|
||
|
||
[% IF FORM.workflow_email_journal_id %]
|
||
[% SET FORM.title = LxERP.t8("Email Journal Workflow") _ " - " _ FORM.title %]
|
||
[% END %]
|
||
<h1>[% FORM.title %] <span id='nr_in_title'>[%- SELF.invoice.number -%]</span></h1>
|
||
|
||
<div id="print_options" style="display:none">
|
||
<form method="post" action="controller.pl" id="print_options_form">
|
||
[% SELF.print_options %]
|
||
<br>
|
||
[% L.button_tag('kivi.Order.print()', LxERP.t8('Print')) %]
|
||
<a href="#" onclick="$('#print_options').dialog('close');">[% LxERP.t8("Cancel") %]</a>
|
||
</form>
|
||
</div>
|
||
|
||
<div id="shipto_dialog" class="hidden"></div>
|
||
|
||
<form method="post" action="controller.pl" id="invoice_form"
|
||
data-transport-cost-reminder-article-id="[% HTML.escape(transport_cost_reminder_article.id) %]"
|
||
data-transport-cost-reminder-article-description="[% HTML.escape(transport_cost_reminder_article.displayable_name) %]">
|
||
[% L.hidden_tag('callback', FORM.callback) %]
|
||
[% L.hidden_tag('type', FORM.type) %]
|
||
[% L.hidden_tag('id', SELF.invoice.id) %]
|
||
[% L.hidden_tag('converted_from_record_type_ref', SELF.invoice.converted_from_record_type_ref) %]
|
||
[% L.hidden_tag('converted_from_record_id', SELF.invoice.converted_from_record_id) %]
|
||
[% L.hidden_tag('email_journal_id', FORM.email_journal_id) %]
|
||
[% L.hidden_tag('email_attachment_id', FORM.email_attachment_id) %]
|
||
[% L.hidden_tag('workflow_email_journal_id', FORM.workflow_email_journal_id) %]
|
||
[% L.hidden_tag('workflow_email_attachment_id', FORM.workflow_email_attachment_id) %]
|
||
[% L.hidden_tag('workflow_email_callback', FORM.workflow_email_callback) %]
|
||
|
||
[% IF !SELF.invoice.id %]
|
||
[% L.hidden_tag('form_validity_token', FORM.form_validity_token) %]
|
||
[% END %]
|
||
|
||
[%- INCLUDE 'common/flash.html' %]
|
||
|
||
<div class="tabwidget" id="invoice_tabs">
|
||
<ul>
|
||
<li><a href="#ui-tabs-basic-data">[% 'Basic Data' | $T8 %]</a></li>
|
||
[% IF FORM.email_attachment_id || FORM.workflow_email_attachment_id %]
|
||
<li><a href="controller.pl?action=EmailJournal/attachment_preview&attachment_id=[% HTML.url(FORM.email_attachment_id || FORM.workflow_email_attachment_id) %]">[% 'Email Attachment Preview' | $T8 %]</a></li>
|
||
[% END %]
|
||
[%- IF INSTANCE_CONF.get_webdav %]
|
||
<li><a href="#ui-tabs-webdav">[% 'WebDAV' | $T8 %]</a></li>
|
||
[%- END %]
|
||
[%- IF SELF.invoice.id AND INSTANCE_CONF.get_doc_storage %]
|
||
<li><a href="controller.pl?action=File/list&file_type=document&object_type=[% HTML.escape(FORM.type) %]&object_id=[% HTML.url(SELF.invoice.id) %]">[% 'Documents' | $T8 %]</a></li>
|
||
<li><a href="controller.pl?action=File/list&file_type=attachment&object_type=[% HTML.escape(FORM.type) %]&object_id=[% HTML.url(SELF.invoice.id) %]">[% 'Attachments' | $T8 %]</a></li>
|
||
[%- END %]
|
||
[%- IF SELF.invoice.id %]
|
||
<li><a href="controller.pl?action=RecordLinks/ajax_list&object_model=Invoice&object_id=[% HTML.url(SELF.invoice.id) %]">[% 'Linked Records' | $T8 %]</a></li>
|
||
[%- END %]
|
||
[% IF SELF.invoice.id %]
|
||
<li><a href="#ui-tabs-phone-notes">[% 'Phone Notes' | $T8 %]<span id="num_phone_notes">[%- num_phone_notes ? ' (' _ num_phone_notes _ ')' : '' -%]</span></a></li>
|
||
[% END %]
|
||
</ul>
|
||
|
||
[% PROCESS "invoice/tabs/basic_data.html" %]
|
||
[% PROCESS 'webdav/_list.html' %]
|
||
<div id="ui-tabs-1">
|
||
[%- LxERP.t8("Loading...") %]
|
||
</div>
|
||
[% IF SELF.invoice.id %]
|
||
<div id="ui-tabs-phone-notes">
|
||
[% PROCESS "invoice/tabs/phone_notes.html" %]
|
||
</div>
|
||
[% END %]
|
||
<div id="shipto_inputs" class="hidden">
|
||
[%- PROCESS 'common/_ship_to_dialog.html'
|
||
vc_obj=SELF.invoice.customervendor
|
||
cs_obj=SELF.invoice.custom_shipto
|
||
cvars=SELF.invoice.custom_shipto.cvars_by_config
|
||
id_selector='#invoice_shipto_id' %]
|
||
</div>
|
||
|
||
</div>
|
||
</form>
|
templates/webpages/invoice/tabs/_business_info_row.html | ||
---|---|---|
[%- USE T8 %][%- USE HTML %]
|
||
|
||
<tr id='business_info_row' [%- IF !SELF.order.customervendor.business_id %]style='display:none'[%- END %]>
|
||
<th align="right">[%- IF SELF.cv == 'customer' -%]
|
||
[%- 'Customer type' | $T8 -%]
|
||
[%- ELSE -%]
|
||
[%- 'Vendor type' | $T8 -%]
|
||
[%- END -%]</th>
|
||
<td>[% HTML.escape(SELF.order.customervendor.business.description) %]; [% 'Trade Discount' | $T8 %] [% SELF.order.customervendor.business.discount_as_percent %] %</td>
|
||
</tr>
|
templates/webpages/invoice/tabs/_item_input.html | ||
---|---|---|
[%- USE T8 %][%- USE HTML %][%- USE LxERP %][%- USE L %][%- USE P %]
|
||
|
||
<div>
|
||
<table id="input_row_table_id">
|
||
<thead>
|
||
<tr class="listheading">
|
||
<th class="listheading" nowrap >[%- '+' | $T8 %] </th>
|
||
<th class="listheading" nowrap >[%- 'position' | $T8 %] </th>
|
||
<th class="listheading" nowrap >[%- 'Part' | $T8 %] </th>
|
||
<th class="listheading" nowrap >[%- 'Description' | $T8 %] </th>
|
||
<th class="listheading" nowrap width="5" >[%- 'Qty' | $T8 %] </th>
|
||
<th class="listheading" nowrap width="15">[%- 'Price' | $T8 %] </th>
|
||
<th class="listheading" nowrap width="5" >[%- 'Discount' | $T8 %] </th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr valign="top" class="listrow">
|
||
<td class="tooltipster-html" title="[%- 'Create a new part' | $T8 -%]">
|
||
[% SET type_options = [[ 'part', LxERP.t8('Part') ], [ 'assembly', LxERP.t8('Assembly') ], [ 'service', LxERP.t8('Service') ] ] %]
|
||
[%- IF INSTANCE_CONF.get_feature_experimental_assortment %]
|
||
[%- type_options.push([ 'assortment', LxERP.t8('Assortment')]) %]
|
||
[%- END %]
|
||
[% L.select_tag('add_item.create_part_type', type_options) %]
|
||
[% L.button_tag('kivi.Order.create_part()', LxERP.t8('+')) %]
|
||
</td>
|
||
<td>[% L.input_tag('add_item.position', '', size = 5, class="add_item_input numeric") %]</td>
|
||
<td>
|
||
[%- SET PARAM_KEY = SELF.cv == "customer" ? 'with_customer_partnumber' : 'with_makemodel' -%]
|
||
[%- SET PARAM_VAL = SELF.search_cvpartnumber -%]
|
||
[% P.part.picker('add_item.parts_id', SELF.created_part, style='width: 300px', class="add_item_input",
|
||
multiple_pos_input=1,
|
||
action={set_multi_items='kivi.Order.add_multi_items'},
|
||
classification_id=SELF.part_picker_classification_ids.as_list.join(','),
|
||
$PARAM_KEY=PARAM_VAL) %]</td>
|
||
<td>[% L.input_tag('add_item.description', SELF.created_part.description, class="add_item_input") %]</td>
|
||
<td>
|
||
[% L.input_tag('add_item.qty_as_number', '', placeholder="1", size = 5, class="add_item_input numeric") %]
|
||
[% L.hidden_tag('add_item.unit', SELF.created_part.unit, class="add_item_input") %]
|
||
</td>
|
||
[%- SET price = '' %]
|
||
[%- IF SELF.created_part %]
|
||
[%- SET price = LxERP.format_amount(((SELF.type == 'sales_quotation' || SELF.type == 'sales_order_intake' || SELF.type == 'sales_order') ? SELF.created_part.sellprice : SELF.created_part.lastcost), -2) -%]
|
||
[%- END %]
|
||
<td>[% L.input_tag('add_item.sellprice_as_number', price, size = 10, class="add_item_input numeric tooltipster-html") %]</td>
|
||
<td>[% L.input_tag('add_item.discount_as_percent', '', size = 5, class="add_item_input numeric tooltipster-html") %]</td>
|
||
<td>[% L.button_tag('kivi.Order.add_item()', LxERP.t8('Add part')) %]</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
templates/webpages/invoice/tabs/_price_sources_dialog.html | ||
---|---|---|
[%- USE T8 %]
|
||
[%- USE HTML %]
|
||
[%- USE L %]
|
||
[%- USE LxERP %]
|
||
[% SET best_price = price_source.best_price %]
|
||
[% SET best_discount = price_source.best_discount %]
|
||
[% SET price_editable = 0 %]
|
||
[% IF (FORM.type == "sales_order" || FORM.type == "sales_order_intake" || FORM.type == "sales_quotation") %]
|
||
[% SET price_editable = AUTH.assert('sales_edit_prices', 1) %]
|
||
[% END %]
|
||
[% IF (FORM.type == "purchase_order" || FORM.type == "purchase_order_confirmation" || FORM.type == "request_quotation" || FORM.type == "purchase_quotation_intake") %]
|
||
[% SET price_editable = AUTH.assert('purchase_edit_prices', 1) %]
|
||
[% END %]
|
||
[% SET exfactor = price_source.record.exchangerate ? 1 / price_source.record.exchangerate : 1 %]
|
||
[% SET exnoshow = price_source.record.currency_id==INSTANCE_CONF.get_currency_id %]
|
||
[% SET places = exnoshow ? -2 : 5 %]
|
||
<h2>[% 'Prices' | $T8 %]</h2>
|
||
|
||
<table>
|
||
<tr class='listheading'>
|
||
<th></th>
|
||
<th>[% 'Price Source' | $T8 %]</th>
|
||
<th>[% 'Price' | $T8 %]</th>
|
||
<th [%- IF exnoshow -%]style='display:none'[%- END %]>
|
||
[% 'Price' | $T8 -%]/[%- price_source.record.currency.name %]
|
||
</th>
|
||
<th>[% 'Best Price' | $T8 %]</th>
|
||
<th>[% 'Details' | $T8 %]</th>
|
||
</tr>
|
||
<tr class='listrow'>
|
||
[%- IF price_source.record_item.active_price_source %]
|
||
<td>[% L.button_tag('kivi.Order.update_price_source(\'' _ FORM.item_id _ '\', \'\', \'' _ LxERP.t8('None (PriceSource)') _ '\', \'\', ' _ price_editable _ ')', LxERP.t8('Select')) %]</td>
|
||
[%- ELSE %]
|
||
<td><b>[% 'Selected' | $T8 %]</b></td>
|
||
[%- END %]
|
||
<td>[% 'None (PriceSource)' | $T8 %]</td>
|
||
<td>-</td>
|
||
<td [%- IF exnoshow -%]style='display:none'[%- END %]>-</td>
|
||
<td></td>
|
||
<td></td>
|
||
</tr>
|
||
[%- FOREACH price IN price_source.available_prices %]
|
||
<tr class='listrow'>
|
||
[%- IF price_source.record_item.active_price_source != price.source %]
|
||
<td>[% L.button_tag('kivi.Order.update_price_source(\'' _ FORM.item_id _ '\', \'' _ price.source _ '\', \'' _ price.source_description _ '\', \'' _ LxERP.format_amount(price.price * exfactor, places) _ '\', ' _ price_editable _ ')', LxERP.t8('Select')) %]</td>
|
||
[%- ELSIF price_source.record_item.sellprice * 1 != price.price * 1 %]
|
||
<td>[% L.button_tag('kivi.Order.update_price_source(\'' _ FORM.item_id _ '\', \'' _ price.source _ '\', \'' _ price.source_description _ '\', \'' _ LxERP.format_amount(price.price * exfactor, places) _ '\', ' _ price_editable _ ')', LxERP.t8('Update Price')) %]</td>
|
||
[%- ELSE %]
|
||
<td><b>[% 'Selected' | $T8 %]</b></td>
|
||
[% END %]
|
||
<td>[% price.source_description | html %]</td>
|
||
<td>[% price.price_as_number %]</td>
|
||
<td [%- IF exnoshow -%]style='display:none'[%- END %]>
|
||
[% LxERP.format_amount(price.price * exfactor, places) %]
|
||
</td>
|
||
[% IF price.source == best_price.source %]
|
||
<td align='center'>•</td>
|
||
[% ELSE %]
|
||
<td></td>
|
||
[% END %]
|
||
<td>[% price.description | html %]</td>
|
||
</tr>
|
||
[%- END %]
|
||
</table>
|
||
|
||
<h2>[% 'Discounts' | $T8 %]</h2>
|
||
|
||
<table>
|
||
<tr class='listheading'>
|
||
<th></th>
|
||
<th>[% 'Price Source' | $T8 %]</th>
|
||
<th>[% 'Discount' | $T8 %]</th>
|
||
<th>[% 'Best Discount' | $T8 %]</th>
|
||
<th>[% 'Details' | $T8 %]</th>
|
||
</tr>
|
||
<tr class='listrow'>
|
||
[%- IF price_source.record_item.active_discount_source %]
|
||
<td>[% L.button_tag('kivi.Order.update_discount_source(\'' _ FORM.item_id _ '\', \'\', \'' _ LxERP.t8('None (PriceSource Discount)') _ '\', \'\', ' _ price_editable _ ')', LxERP.t8('Select')) %]</td>
|
||
[%- ELSE %]
|
||
<td><b>[% 'Selected' | $T8 %]</b></td>
|
||
[%- END %]
|
||
<td>[% 'None (PriceSource Discount)' | $T8 %]</td>
|
||
<td>-</td>
|
||
<td></td>
|
||
<td></td>
|
||
</tr>
|
||
[%- FOREACH price IN price_source.available_discounts %]
|
||
<tr class='listrow'>
|
||
[%- IF price_source.record_item.active_discount_source != price.source %]
|
||
<td>[% L.button_tag('kivi.Order.update_discount_source(\'' _ FORM.item_id _ '\', \'' _ price.source _ '\', \'' _ price.source_description _ '\', \'' _ price.discount_as_percent _ '\', ' _ price_editable _ ')', LxERP.t8('Select')) %]</td>
|
||
[%- ELSIF price_source.record_item.discount * 1 != price.discount * 1 %]
|
||
<td>[% L.button_tag('kivi.Order.update_discount_source(\'' _ FORM.item_id _ '\', \'' _ price.source _ '\', \'' _ price.source_description _ '\', \'' _ price.discount_as_percent _ '\', ' _ price_editable _ ')', LxERP.t8('Update Discount')) %]</td>
|
||
[%- ELSE %]
|
||
<td><b>[% 'Selected' | $T8 %]</b></td>
|
||
[% END %]
|
||
<td>[% price.source_description | html %]</td>
|
||
<td>[% price.discount_as_percent %] %</td>
|
||
[% IF price.source == best_discount.source %]
|
||
<td align='center'>•</td>
|
||
[% ELSE %]
|
||
<td></td>
|
||
[% END %]
|
||
<td>[% price.description | html %]</td>
|
||
</tr>
|
||
[%- END %]
|
||
</table>
|
templates/webpages/invoice/tabs/_purchase_delivery_order_item_selection.html | ||
---|---|---|
[%- USE LxERP -%][%- USE L -%][%- USE HTML -%]
|
||
<div>
|
||
[% L.button_tag("kivi.Order.convert_to_purchase_delivery_order_item_selection()", LxERP.t8("Create delivery order")) %]
|
||
</div>
|
||
|
||
<div>
|
||
<table>
|
||
<thead>
|
||
<tr class="listheading">
|
||
<th>[% L.checkbox_tag("item_position_selection_checkall", checkall="input.item_position_selection_checkall", checked="checked") %]</th>
|
||
<th>[% LxERP.t8("Position") %]</th>
|
||
<th>[% LxERP.t8("Partnumber") %]</th>
|
||
<th>[% LxERP.t8("Vendor Part Number") %]</th>
|
||
<th>[% LxERP.t8("Description") %]</th>
|
||
<th>[% LxERP.t8("Qty") %]</th>
|
||
<th>[% LxERP.t8("Unit") %]</th>
|
||
</tr>
|
||
</thead>
|
||
|
||
<tbody id="item_position_selection" class="row_entry listrow">
|
||
[% FOREACH item = ITEMS %]
|
||
<tr class="listrow">
|
||
[% SET pos = loop.count - 1 %]
|
||
<td>[% L.checkbox_tag("selected_item_positions[+]", class="item_position_selection_checkall", value=loop.count - 1, checked="checked") %]</td>
|
||
<td align="right">[% loop.count %]</td>
|
||
<td>[% HTML.escape(item.partnumber) %]</td>
|
||
<td>[% HTML.escape(item.vendor_partnumber) %]</td>
|
||
<td>[% HTML.escape(item.description) %]</td>
|
||
<td align="right">[% HTML.escape(item.qty_as_number) %]</td>
|
||
<td>[% HTML.escape(item.unit) %]</td>
|
||
</tr>
|
||
[% END %]
|
||
</tbody>
|
||
</table>
|
||
</div>
|
templates/webpages/invoice/tabs/_row.html | ||
---|---|---|
[%- USE T8 %]
|
||
[%- USE HTML %]
|
||
[%- USE LxERP %]
|
||
[%- USE L %]
|
||
[%- USE P %]
|
||
|
||
<tbody class="row_entry listrow" data-position="[%- HTML.escape(ITEM.position) -%]"[%- IF MYCONFIG.show_form_details -%] data-expanded="1"[%- END -%]>
|
||
<tr>
|
||
<td align="center">
|
||
[%- IF MYCONFIG.show_form_details %]
|
||
[% L.img_tag(src="image/collapse.svg",
|
||
alt=LxERP.t8('Hide details'), title=LxERP.t8('Hide details'), class="expand") %]
|
||
[%- ELSE %]
|
||
[% L.img_tag(src="image/expand.svg",
|
||
alt=LxERP.t8('Show details'), title=LxERP.t8('Show details'), class="expand") %]
|
||
[%- END %]
|
||
[% L.hidden_tag("orderitem_ids[+]", ID) %]
|
||
[% L.hidden_tag("converted_from_record_item_type_ref[+]", ITEM.converted_from_record_item_type_ref) %]
|
||
[% L.hidden_tag("converted_from_record_item_id[+]", ITEM.converted_from_record_item_id) %]
|
||
[% L.hidden_tag("order.orderitems[+].id", ITEM.id, id='item_' _ ID) %]
|
||
[% L.hidden_tag("order.orderitems[].parts_id", ITEM.parts_id) %]
|
||
[% L.hidden_tag("purchase_basket_item_ids[+]", ITEM.purchase_basket_item_ids) %]
|
||
</td>
|
||
<td>
|
||
<div name="position" class="numeric">
|
||
[% HTML.escape(ITEM.position) %]
|
||
</div>
|
||
</td>
|
||
<td align="center">
|
||
<img src="image/updown.png" alt="[%- LxERP.t8('reorder item') %]" class="dragdrop">
|
||
</td>
|
||
<td align="center">
|
||
[%- L.button_tag("kivi.Order.delete_order_item_row(this)",
|
||
LxERP.t8("X"),
|
||
confirm=LxERP.t8("Are you sure?")) %]
|
||
</td>
|
||
[%- IF SELF.show_update_button -%]
|
||
<td align="center">
|
||
[%- L.img_tag(src="image/rotate_cw.svg",
|
||
alt=LxERP.t8('Update from master data'),
|
||
title= LxERP.t8('Update from master data'),
|
||
onclick="if (!confirm('" _ LxERP.t8("Are you sure to update this position from master data?") _ "')) return false; kivi.Order.update_row_from_master_data(this);",
|
||
id='update_from_master') %]
|
||
</td>
|
||
[%- END -%]
|
||
<td>
|
||
<div name="partnumber">
|
||
[%- P.link_tag(SELF.url_for(controller='Part', action='edit', 'part.id'=ITEM.part.id), ITEM.part.partnumber, target="_blank", title=LxERP.t8('Open in new window')) -%]
|
||
</div>
|
||
</td>
|
||
[%- IF SELF.search_cvpartnumber -%]
|
||
<td>
|
||
<div name="cvpartnumber">[% HTML.escape(ITEM.cvpartnumber) %]</div>
|
||
</td>
|
||
[%- END -%]
|
||
<td>
|
||
<div name="partclassification">[% ITEM.part.presenter.typeclass_abbreviation %]</div>
|
||
</td>
|
||
<td>
|
||
[% L.areainput_tag("order.orderitems[].description",
|
||
ITEM.description,
|
||
size='40',
|
||
style='width: 300px') %]
|
||
[%- L.hidden_tag("order.orderitems[].longdescription", ITEM.longdescription) %]
|
||
[%- L.button_tag("kivi.Order.show_longdescription_dialog(this)", LxERP.t8("L")) %]
|
||
</td>
|
||
[%- IF (SELF.type == "sales_order_intake" || SELF.type == "sales_order" || SELF.type == "purchase_order" || SELF.type == "purchase_order_confirmation") -%]
|
||
<td nowrap>
|
||
[%- L.div_tag(LxERP.format_amount(ITEM.shipped_qty, 2, 0) _ ' ' _ ITEM.unit, name="shipped_qty", class="numeric") %]
|
||
</td>
|
||
[%- END -%]
|
||
<td nowrap>
|
||
[%- L.input_tag("order.orderitems[].qty_as_number",
|
||
ITEM.qty_as_number,
|
||
size = 5,
|
||
class="recalc reformat_number numeric") %]
|
||
[%- IF ITEM.part.formel -%]
|
||
[%- L.button_tag("kivi.Order.show_calculate_qty_dialog(this)", LxERP.t8("*/")) %]
|
||
[%- L.hidden_tag("formula[+]", ITEM.part.formel) -%]
|
||
[%- END -%]
|
||
</td>
|
||
<td>
|
||
[%- L.select_tag("order.orderitems[].price_factor_id",
|
||
SELF.all_price_factors,
|
||
default = ITEM.price_factor_id,
|
||
title_key = 'description',
|
||
with_empty = 1,
|
||
class="recalc") %]
|
||
</td>
|
||
<td nowrap>
|
||
[%- L.select_tag("order.orderitems[].unit",
|
||
ITEM.part.available_units,
|
||
default = ITEM.unit,
|
||
title_key = 'name',
|
||
value_key = 'name',
|
||
class = 'unitselect') %]
|
||
</td>
|
||
<td>
|
||
[%- L.button_tag("kivi.Order.price_chooser_item_row(this)",
|
||
ITEM.active_price_source.source_description _ ' | ' _ ITEM.active_discount_source.source_description,
|
||
name = "price_chooser_button") %]
|
||
</td>
|
||
[% SET RIGHT_TO_EDIT_PRICES = 0 %]
|
||
[% IF (SELF.type == "sales_order_intake" || SELF.type == "sales_order" || SELF.type == "sales_quotation") %]
|
||
[% SET RIGHT_TO_EDIT_PRICES = AUTH.assert('sales_edit_prices', 1) %]
|
||
[% END %]
|
||
[% IF (SELF.type == "purchase_order" || SELF.type == "purchase_order_confirmation" || SELF.type == "request_quotation" || SELF.type == "purchase_quotation_intake") %]
|
||
[% SET RIGHT_TO_EDIT_PRICES = AUTH.assert('purchase_edit_prices', 1) %]
|
||
[% END %]
|
||
<td>
|
||
[%- L.hidden_tag("order.orderitems[].active_price_source", ITEM.active_price_source.source) %]
|
||
[%- SET EDIT_PRICE = (RIGHT_TO_EDIT_PRICES && ITEM.active_price_source.source == '') %]
|
||
<div name="editable_price" [%- IF !EDIT_PRICE %]style="display:none"[%- END %] class="numeric">
|
||
[%- L.input_tag("order.orderitems[].sellprice_as_number",
|
||
ITEM.sellprice_as_number,
|
||
size = 10,
|
||
disabled=(EDIT_PRICE? '' : 1),
|
||
class="recalc reformat_number numeric") %]
|
||
</div>
|
||
<div name="not_editable_price" [%- IF EDIT_PRICE %]style="display:none"[%- END %]>
|
||
[%- L.div_tag(ITEM.sellprice_as_number, name="sellprice_text", class="numeric") %]
|
||
[%- L.hidden_tag("order.orderitems[].sellprice_as_number",
|
||
ITEM.sellprice_as_number,
|
||
disabled=(EDIT_PRICE? 1 : '')) %]
|
||
</div>
|
||
</td>
|
||
<td>
|
||
[%- L.hidden_tag("order.orderitems[].active_discount_source", ITEM.active_discount_source.source) %]
|
||
[%- SET EDIT_DISCOUNT = (RIGHT_TO_EDIT_PRICES && ITEM.active_discount_source.source == '') %]
|
||
<div name="editable_discount" [%- IF !EDIT_DISCOUNT %]style="display:none"[%- END %] class="numeric">
|
||
[%- L.input_tag("order.orderitems[].discount_as_percent",
|
||
ITEM.discount_as_percent,
|
||
size = 5,
|
||
disabled=(EDIT_DISCOUNT? '' : 1),
|
||
class="recalc reformat_number numeric") %]
|
||
</div>
|
||
<div name="not_editable_discount" [%- IF EDIT_DISCOUNT %]style="display:none"[%- END %]>
|
||
[%- L.div_tag(ITEM.discount_as_percent, name="discount_text", class="numeric") %]
|
||
[%- L.hidden_tag("order.orderitems[].discount_as_percent",
|
||
ITEM.discount_as_percent,
|
||
disabled=(EDIT_DISCOUNT? 1 : '')) %]
|
||
</div>
|
||
</td>
|
||
<td align="right">
|
||
[%- L.div_tag(LxERP.format_amount(ITEM.linetotal, 2, 0), name="linetotal") %]
|
||
</td>
|
||
|
||
</tr>
|
||
|
||
<tr [%- IF !MYCONFIG.show_form_details -%]style="display:none"[%- END -%]>
|
||
<td colspan="100%">
|
||
[%- IF MYCONFIG.show_form_details || ITEM.render_second_row %]
|
||
<div name="second_row" data-loaded="1">
|
||
[%- PROCESS order/tabs/_second_row.html ITEM=ITEM TYPE=SELF.type %]
|
||
</div>
|
||
[%- ELSE %]
|
||
<div name="second_row" id="second_row_[% ID %]">
|
||
[%- LxERP.t8("Loading...") %]
|
||
</div>
|
||
[%- END %]
|
||
</td>
|
||
</tr>
|
||
|
||
</tbody>
|
templates/webpages/invoice/tabs/_second_row.html | ||
---|---|---|
[%- USE T8 %]
|
||
[%- USE HTML %]
|
||
[%- USE LxERP %]
|
||
[%- USE L %]
|
||
[%- USE P %]
|
||
|
||
<table>
|
||
<tr><td colspan="100%">
|
||
[%- IF (TYPE == "sales_order_intake" || TYPE == "sales_order" || TYPE == "purchase_order") %]
|
||
<b>[%- 'Serial No.' | $T8 %]</b>
|
||
[%- L.input_tag("order.orderitems[].serialnumber", ITEM.serialnumber, size = 15 "data-validate"="trimmed_whitespaces") %]
|
||
[%- END %]
|
||
<b>[%- 'Project' | $T8 %]</b>
|
||
[% P.project.picker("order.orderitems[].project_id", ITEM.project_id, size = 15) %]
|
||
[%- IF (TYPE == "sales_order_intake" || TYPE == "sales_order" || TYPE == "purchase_order") %]
|
||
<b>[%- 'Reqdate' | $T8 %]</b>
|
||
[% L.date_tag("order.orderitems[].reqdate_as_date", ITEM.reqdate_as_date) %]
|
||
[%- END %]
|
||
<b>[%- 'Subtotal' | $T8 %]</b>
|
||
[% L.yes_no_tag("order.orderitems[].subtotal", ITEM.subtotal) %]
|
||
[%- IF TYPE == "sales_order" %]
|
||
<b>[%- 'Recurring billing' | $T8 %]</b>
|
||
[% L.select_tag("order.orderitems[].recurring_billing_mode", [[ 'always', LxERP.t8('always') ], [ 'once', LxERP.t8('once') ], [ 'never', LxERP.t8('never') ]], default=ITEM.recurring_billing_mode) %]
|
||
[%- END %]
|
||
[%- IF (TYPE == "sales_order_intake" || TYPE == "sales_order" || TYPE == "sales_quotation") %]
|
||
<b>[%- 'Ertrag' | $T8 %]</b>
|
||
<span name="linemargin">
|
||
<span[%- IF ITEM.marge_total < 0 -%] class="plus0"[%- END -%]>
|
||
[%- LxERP.format_amount(ITEM.marge_total, 2, 0) %]
|
||
[%- LxERP.format_amount(ITEM.marge_percent, 2, 0) %]%
|
||
</span>
|
||
</span>
|
||
<b>[%- 'LP' | $T8 %]</b>
|
||
[%- LxERP.format_amount(ITEM.part.listprice, 2, 0) %]
|
||
<b>[%- 'EK' | $T8 %]</b>
|
||
[%- L.input_tag("order.orderitems[].lastcost_as_number",
|
||
ITEM.lastcost_as_number,
|
||
size = 5,
|
||
class="recalc reformat_number numeric") %]
|
||
[%- END %]
|
||
<b>[%- 'On Hand' | $T8 %]</b>
|
||
<span class="numeric[%- IF ITEM.part.onhand < ITEM.part.rop -%] plus0[%- END -%]">
|
||
[%- ITEM.part.onhand_as_number -%] [%- ITEM.part.unit -%]
|
||
</span>
|
||
<b>[%- 'Optional' | $T8 %]</b>
|
||
[%- L.yes_no_tag("order.orderitems[].optional", ITEM.optional
|
||
class="recalc") %]
|
||
</td></tr>
|
||
|
||
<tr>
|
||
[%- SET n = 0 %]
|
||
[%- FOREACH var = ITEM.cvars_by_config %]
|
||
[%- NEXT UNLESS (var.config.processed_flags.editable && ITEM.part.cvar_by_name(var.config.name).is_valid) %]
|
||
[%- SET n = n + 1 %]
|
||
<th>
|
||
[% var.config.description %]
|
||
</th>
|
||
<td>
|
||
[% L.hidden_tag('order.orderitems[].custom_variables[+].config_id', var.config.id) %]
|
||
[% L.hidden_tag('order.orderitems[].custom_variables[].id', var.id) %]
|
||
[% L.hidden_tag('order.orderitems[].custom_variables[].sub_module', var.sub_module) %]
|
||
[% INCLUDE 'common/render_cvar_input.html' var_name='order.orderitems[].custom_variables[].unparsed_value' %]
|
||
</td>
|
||
[%- IF (n % (MYCONFIG.form_cvars_nr_cols || 3)) == 0 %]
|
||
|
||
</tr><tr>
|
||
[%- END %]
|
||
[%- END %]
|
||
</tr>
|
||
</table>
|
templates/webpages/invoice/tabs/_tax_row.html | ||
---|---|---|
[%- USE T8 %]
|
||
[%- USE HTML %]
|
||
[%- USE LxERP %]
|
||
[%- USE L %]
|
||
|
||
<tr class="tax_row">
|
||
<th align="right">[%- IF TAXINCLUDED %][%- 'Including' | $T8 %] [%- END %][%- TAX.tax.taxdescription %] [% TAX.tax.rate_as_percent %]%</th>
|
||
<td align="right">[%- LxERP.format_amount(TAX.amount, 2, 0) %]</td>
|
||
</tr>
|
||
[%- IF TAXINCLUDED %]
|
||
<tr class="tax_row">
|
||
<th align="right">[%- 'Net amount' | $T8 %]</th>
|
||
<td align="right">[%- LxERP.format_amount(TAX.netamount, 2, 0) %]</td>
|
||
</tr>
|
||
[%- END%]
|
templates/webpages/invoice/tabs/basic_data.html | ||
---|---|---|
[%- USE T8 %]
|
||
[%- USE HTML %]
|
||
[%- USE LxERP %]
|
||
[%- USE L %]
|
||
[%- USE P %]
|
||
|
||
[%- INCLUDE 'generic/set_longdescription.html' %]
|
||
|
||
<div id="ui-tabs-basic-data">
|
||
<table width="100%">
|
||
<tr valign="top">
|
||
<td>
|
||
<table width="100%">
|
||
<tr>
|
||
<th align="right">[%- SELF.cv == "customer" ? LxERP.t8('Customer') : LxERP.t8('Vendor') -%]</th>
|
||
[% SET cv_id = SELF.cv _ '_id' %]
|
||
<td>
|
||
[% P.customer_vendor.picker("invoice.${SELF.cv}" _ '_id', SELF.invoice.$cv_id, type=SELF.cv, show_details="1",
|
||
style='width: 300px') %]
|
||
</td>
|
||
</tr>
|
||
|
||
<tr id='cp_row' [%- IF !SELF.invoice.${SELF.cv}.contacts.size %]style='display:none'[%- END %]>
|
||
<th align="right">[% 'Contact Person' | $T8 %]</th>
|
||
<td>[% L.select_tag('invoice.cp_id',
|
||
SELF.invoice.${SELF.cv}.contacts,
|
||
default=SELF.invoice.cp_id,
|
||
title_key='full_name_dep',
|
||
value_key='cp_id',
|
||
with_empty=1,
|
||
style='width: 300px') %]</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<th align="right">[% 'Shipping Address' | $T8 %]</th>
|
||
<td>
|
||
<span id='shipto_selection' [%- IF !SELF.invoice.${SELF.cv}.shipto.size %]style='display:none'[%- END %]>
|
||
[% shiptos = [ { shipto_id => "", displayable_id => LxERP.t8("No/individual shipping address") } ] ;
|
||
FOREACH s = SELF.invoice.${SELF.cv}.shipto ;
|
||
shiptos.push(s) ;
|
||
END ;
|
||
L.select_tag('invoice.shipto_id',
|
||
shiptos,
|
||
default=SELF.invoice.shipto_id,
|
||
title_key='displayable_id',
|
||
value_key='shipto_id',
|
||
with_empty=0,
|
||
style='width: 300px') %]
|
||
</span>
|
||
[% L.button_tag("kivi.invoice.edit_custom_shipto()", LxERP.t8("Custom shipto")) %]
|
||
</td>
|
||
</tr>
|
||
|
||
[%- IF SELF.cv == "customer" %]
|
||
<tr id="billing_address_row"[% IF !SELF.invoice.customer.additional_billing_addresses.as_list.size %] style="display:none"[% END %]>
|
||
<th align="right">[% 'Custom Billing Address' | $T8 %]</th>
|
||
<td>
|
||
[% L.select_tag('invoice.billing_address_id',
|
||
SELF.invoice.customer.additional_billing_addresses,
|
||
default=SELF.invoice.billing_address_id,
|
||
title_key='displayable_id',
|
Auch abrufbar als: Unified diff
PoC: Invoice Controller
Das hier ist der Versuch mit dem aktuellen TypeData und Model::Record
Unterbau einen Invoice Controller zu bauen, vor allem zu
Dokumentationszwecken.
Dieser Branch ist NICHT zum mergen gedacht.
Erkenntnisse, was alles anders ist oder angefasst werde muss:
Im Controller:
- $self->order
> $self>invoice- Mit Tamino und Bernd besprochen, sollte eher generisch $self->record
sein
- ValidityToken::SCOPE_ORDER_SAVE
> SCOPE_SALES_INVOICE_POST>{order}- Da gab es wohl schon einmal Diskussionen ob die überhaupt scopes
haben müssen. Ansonsten, nach TypeData verschieben.
- init_type Fehlermeldung: "not a valid type foe order"
- type_data Proxy muss mit der richtigen Klasse SL::DB::Invoice erstellt
werden.
- Items müssen SL::DB::InvoiceItem statt SL::DB::OrderItem sein
- $::form
> $::form>{invoice}- auch hier - vereinheitlichen auf $::form->{record}?
- $::form->{orderitems}
> $::form>{invoiceitems}- dito.
- setup_custom_shipto module OE -> AR
- javascripte müssen umgebogen werden und evtl frontend checks neu
gebaut werden:
- kivi.Order.check_cv
- kivi.Order.check_duplicate_parts
- kivi.Order.check_valid_reqdate
- kivi.Order.check_transport_cost_article_presence
- kivi.Order.check_cusordnumber_presence
- kivi.Order.check_has_final_invoice - unnötig
- kivi.Order.check_invoice_advance_payment - unnötig
Features:
- close_quotations - gibt es in Invoice nicht
- periodic_invoices - gibt es in Invoice nicht
- basket_from_from - (sic!) gibt es in Invoice nicht
- shipped_qty - Invoice macht im Moment nichts mit Lager im Frontend
- transport_cost_reminder - gibt es in Invoice nicht
- phone_notes - gibt es in Invoice nicht
- subversion / final_version - gibt es in Invoice nicht als Zieltypen
im workflow
- not_order_locked - Waren nicht nicht mehr eingekauft werden dürfen
- ja/nein?
In SL::DB::Invoice und TypeDate
- Invoice kennt bereits ein type, was aber nicht das gleiche ist wie
das record_type. Das muss in der Datenbank gefixt werden.
- duedate/reqdate default belegung fuktioniert so nicht, ist von
payment_terms abhängig
- gldate belegung funktioniert so nicht
- parts_classification
Dazu ware in is.pl sehr viele Flags in Form, die da eigentlich nicht
hingehören:
- locked
- readonly
- storno
- storno_id
- postal_invoice
- is_gldate_ready (gldate today)
- payment_balanced (oldpaidtotal paidtotal)