}
};
+ my ($cvar_where, @cvar_values) = CVar->build_filter_query('module' => 'CT',
+ 'trans_id_field' => 'c.id',
+ 'filter' => $form,
+ );
+ if ($cvar_where) {
+ $where .= qq| AND ($cvar_where)|;
+ push @values, @cvar_values;
+ }
+
my @a = qw(transdate invnumber name);
push @a, "employee" if $form->{l_employee};
my $sortdir = !defined $form->{sortdir} ? 'ASC' : $form->{sortdir} ? 'ASC' : 'DESC';
use Time::HiRes qw(gettimeofday);
use List::MoreUtils qw(uniq);
use YAML;
+use Regexp::IPv6 qw($IPv6_re);
use SL::Auth::ColumnInformation;
use SL::Auth::Constants qw(:all);
my $api_token_cookie = $self->get_api_token_cookie;
my $cookie_is_bad = !$cookie || $cookie->{is_expired};
$cookie_is_bad ||= $api_token_cookie && ($api_token_cookie ne $cookie->{api_token}) if $api_token_cookie;
- $cookie_is_bad ||= $cookie->{ip_address} ne $ENV{REMOTE_ADDR} if !$api_token_cookie;
+ $cookie_is_bad ||= $cookie->{ip_address} ne $ENV{REMOTE_ADDR} if !$api_token_cookie && $ENV{REMOTE_ADDR} !~ /^$IPv6_re$/;
if ($cookie_is_bad) {
$self->destroy_session();
return $self->session_restore_result($cookie ? SESSION_EXPIRED() : SESSION_NONE());
my $locale = $main::locale;
my @all_rights = (
- ["--crm", $locale->text("CRM optional software")],
- ["crm_search", $locale->text("CRM search")],
- ["crm_new", $locale->text("CRM create customers, vendors and contacts")],
- ["crm_service", $locale->text("CRM services")],
- ["crm_admin", $locale->text("CRM admin")],
- ["crm_adminuser", $locale->text("CRM user")],
- ["crm_adminstatus", $locale->text("CRM status")],
- ["crm_email", $locale->text("CRM send email")],
- ["crm_termin", $locale->text("CRM termin")],
- ["crm_opportunity", $locale->text("CRM opportunity")],
- ["crm_knowhow", $locale->text("CRM know how")],
- ["crm_follow", $locale->text("CRM follow up")],
- ["crm_notices", $locale->text("CRM notices")],
- ["crm_other", $locale->text("CRM other")],
["--master_data", $locale->text("Master Data")],
["customer_vendor_edit", $locale->text("Create customers and vendors. Edit all vendors. Edit only customers where salesman equals employee (login)")],
["customer_vendor_all_edit", $locale->text("Create customers and vendors. Edit all vendors. Edit all customers")],
"city" => "ct.city",
"country" => "ct.country",
"discount" => "ct.discount",
+ "insertdate" => "ct.itime",
"salesman" => "e.name",
- "payment" => "pt.description"
+ "payment" => "pt.description",
+ "pricegroup" => "pg.pricegroup",
);
$form->{sort} ||= "name";
}
my $sortdir = !defined $form->{sortdir} ? 'ASC' : $form->{sortdir} ? 'ASC' : 'DESC';
- if ($sortorder !~ /(business|id|discount)/ && !$join_records) {
+ if ($sortorder !~ /(business|id|discount|itime)/ && !$join_records) {
$sortorder = "lower($sortorder) ${sortdir}";
} else {
$sortorder .= " ${sortdir}";
push(@values, conv_i($form->{salesman_id}));
}
+ if($form->{insertdatefrom}) {
+ $where .= qq| AND (ct.itime::DATE >= ?)|;
+ push@values, conv_date($form->{insertdatefrom});
+ }
+
+ if($form->{insertdateto}) {
+ $where .= qq| AND (ct.itime::DATE <= ?)|;
+ push @values, conv_date($form->{insertdateto});
+ }
+
# Nur Kunden finden, bei denen ich selber der Verkäufer bin
# Gilt nicht für Lieferanten
if ($cv eq 'customer' && !$main::auth->assert('customer_vendor_all_edit', 1)) {
push @values, $form->{addr_zipcode} . '%';
}
+ my $pg_select = $form->{l_pricegroup} ? qq|, pg.pricegroup as pricegroup | : '';
+ my $pg_join = $form->{l_pricegroup} ? qq|LEFT JOIN pricegroup pg ON (ct.klass = pg.id) | : '';
my $query =
- qq|SELECT ct.*, b.description AS business, e.name as salesman, |.
+ qq|SELECT ct.*, ct.itime::DATE AS insertdate, b.description AS business, e.name as salesman, | .
qq| pt.description as payment | .
+ $pg_select .
(qq|, NULL AS invnumber, NULL AS ordnumber, NULL AS quonumber, NULL AS invid, NULL AS module, NULL AS formtype, NULL AS closed | x!! $join_records) .
qq|FROM $cv ct | .
qq|LEFT JOIN business b ON (ct.business_id = b.id) | .
qq|LEFT JOIN employee e ON (ct.salesman_id = e.id) | .
qq|LEFT JOIN payment_terms pt ON (ct.payment_id = pt.id) | .
+ $pg_join .
qq|WHERE $where|;
my @saved_values = @values;
push(@values, @saved_values);
$query .=
qq| UNION | .
- qq|SELECT ct.*, b.description AS business, e.name as salesman, |.
- qq| pt.description as payment, | .
- qq| a.invnumber, a.ordnumber, a.quonumber, a.id AS invid, | .
+ qq|SELECT ct.*, ct.itime::DATE AS insertdate, b.description AS business, e.name as salesman, | .
+ qq| pt.description as payment | .
+ $pg_select .
+ qq|, a.invnumber, a.ordnumber, a.quonumber, a.id AS invid, | .
qq| '$module' AS module, 'invoice' AS formtype, | .
qq| (a.amount = a.paid) AS closed | .
qq|FROM $cv ct | .
qq|LEFT JOIN business b ON (ct.business_id = b.id) | .
qq|LEFT JOIN employee e ON (ct.salesman_id = e.id) | .
qq|LEFT JOIN payment_terms pt ON (ct.payment_id = pt.id) | .
+ $pg_join .
qq|WHERE $where AND (a.invoice = '1')|;
}
push(@values, @saved_values);
$query .=
qq| UNION | .
- qq|SELECT ct.*, b.description AS business, e.name as salesman, |.
- qq| pt.description as payment, | .
- qq| ' ' AS invnumber, o.ordnumber, o.quonumber, o.id AS invid, | .
+ qq|SELECT ct.*, ct.itime::DATE AS insertdate, b.description AS business, e.name as salesman, | .
+ qq| pt.description as payment | .
+ $pg_select .
+ qq|, ' ' AS invnumber, o.ordnumber, o.quonumber, o.id AS invid, | .
qq| 'oe' AS module, 'order' AS formtype, o.closed | .
qq|FROM $cv ct | .
qq|JOIN oe o ON (o.${cv}_id = ct.id) | .
qq|LEFT JOIN business b ON (ct.business_id = b.id) | .
qq|LEFT JOIN employee e ON (ct.salesman_id = e.id) | .
qq|LEFT JOIN payment_terms pt ON (ct.payment_id = pt.id) | .
+ $pg_join .
qq|WHERE $where AND (o.quotation = '0')|;
}
push(@values, @saved_values);
$query .=
qq| UNION | .
- qq|SELECT ct.*, b.description AS business, e.name as salesman, | .
- qq| pt.description as payment, | .
- qq| ' ' AS invnumber, o.ordnumber, o.quonumber, o.id AS invid, | .
+ qq|SELECT ct.*, ct.itime::DATE AS insertdate, b.description AS business, e.name as salesman, | .
+ qq| pt.description as payment | .
+ $pg_select .
+ qq|, ' ' AS invnumber, o.ordnumber, o.quonumber, o.id AS invid, | .
qq| 'oe' AS module, 'quotation' AS formtype, o.closed | .
qq|FROM $cv ct | .
qq|JOIN oe o ON (o.${cv}_id = ct.id) | .
qq|LEFT JOIN business b ON (ct.business_id = b.id) | .
qq|LEFT JOIN employee e ON (ct.salesman_id = e.id) | .
qq|LEFT JOIN payment_terms pt ON (ct.payment_id = pt.id) | .
+ $pg_join .
qq|WHERE $where AND (o.quotation = '1')|;
}
}
reinit_widgets => 0, # kivi.reinit_widgets()
run => -1, # kivi.run(<TARGET>, <ARGS>)
run_once_for => 3, # kivi.run_once_for(<TARGET>, <ARGS>)
+
+ scroll_into_view => 1, # $(<TARGET>)[0].scrollIntoView()
);
sub AUTOLOAD {
=head1 OVERVIEW
The clipboard can store an unlimited number of copies of
-Rose::DB::Object instance. The instances are dumped into trees using
+Rose::DB::Object instances. The instances are dumped into trees using
L<Rose::DB::Object::Helpers/as_tree>. How much of such an object is
copied depends on its type. For example, a dump of a customer object
might also include the dumps of the shipping address and contact
C<SL::Clipboard::RequirementSpecItem>. These support classes must
inherit from L<SL::Clipboard::Base> which offers almost a full set of
support functions so that the actual specialized class has to do very
-litte.
+little.
As the clipboard is session-based its contents will be lost when the
session expires (either due to timeouts or to the user logging off).
=item C<copy $object>
Creates a dumped copy of C<$object> and stores that copy in the
-session. An unlimited number of copies of differeing types can be
+session. An unlimited number of copies of differing types can be
made.
Returns the instance of the copied object, a sub-class of
=item C<get_entry [$type]>
Returns the most recently clipped object whose type matches the
-regular expression C<$type>. If C<$type> is not given then then then
+regular expression C<$type>. If C<$type> is not given then the
most recently copied object is returned.
If no such object exists C<undef> is returned instead.
=item C<to_object>
-Converts the dumped representation back to an Rose::DB::Object
+Converts the dumped representation back to a Rose::DB::Object
instance. Several columns of the newly created object are cleared by
C<to_object> itself: the primary key columns (if any) and the columns
C<itime> and C<mtime> (if the object has such columns).
This function should not be overwritten by sub-classes. Instead,
-functions can overwrite C<_fix_object> which can be used for sanitzing
+functions can overwrite C<_fix_object> which can be used for sanitizing
the newly created object before handing it back to the caller.
=item C<type>
($path, $number) = ("einkaufslieferscheine", $form->{donumber});
} elsif ($form->{type} eq "credit_note") {
($path, $number) = ("gutschriften", $form->{invnumber});
+ } elsif ($form->{type} eq "letter") {
+ ($path, $number) = ("briefe", $form->{letternumber} );
} elsif ($form->{vc} eq "customer") {
($path, $number) = ("rechnungen", $form->{invnumber});
} elsif ($form->{vc} eq "vendor") {
# checks
foreach my $item (qw(tmpdir tmpfile type)){
next if $form->{$item};
- $::lxdebug->message(LXDebug::WARN(), 'Missing parameter');
+ $::lxdebug->message(LXDebug::WARN(), 'Missing parameter:' . $item);
$::form->error($::locale->text("Missing parameter for WebDAV file copy"));
}
if (! $webdav_folder){
$::lxdebug->leave_sub();
+ $::lxdebug->message(LXDebug::WARN(), 'Cannot check correct WebDAV folder');
$::form->error($::locale->text("Cannot check correct WebDAV folder"));
return undef;
}
# maybe the path does not exist (automatic printing), see #2446
if (!-d $complete_path) {
- # we need a chdir and restore old dir
+ # we need a chdir and restore old dir
my $current_dir = POSIX::getcwd();
chdir("$form->{cwd}");
mkdir_with_parents($webdav_folder);
$self->register_handlers('callback' => $callback);
}
-sub get_callback {
+sub get_callback_params {
my ($self, %override_params) = @_;
my %default_params = $self->_run_handlers('callback', action => $self->controller->action_name);
+}
+
+sub get_callback {
+ my ($self, %override_params) = @_;
+
+ my %default_params = $self->get_callback_params(%override_params);
return $self->controller->url_for(%default_params, %override_params);
}
unit => $self->unit,
transfer_type => 'stock',
chargenumber => $::form->{chargenumber},
+ bestbefore => $::form->{bestbefore},
ean => $::form->{ean},
comment => $::form->{comment},
});
$self->make_filter_summary;
- my $projects = $self->models->get;
-
$self->prepare_report;
- $self->report_generator_list_objects(report => $self->{report}, objects => $projects);
+ $self->report_generator_list_objects(report => $self->{report}, objects => $self->models->get);
}
sub action_new {
map { $column_defs{$_}->{text} ||= $::locale->text( $self->models->get_sort_spec->{$_}->{title} ) } keys %column_defs;
- if ( $report->{options}{output_format} =~ /^(pdf|csv)$/i ) {
- $self->models->disable_plugin('paginated');
- }
$report->set_options(
std_column_visibility => 1,
controller_class => 'Project',
output_format => 'HTML',
- raw_top_info_text => $self->render('project/report_top', { output => 0 }),
- raw_bottom_info_text => $self->render('project/report_bottom', { output => 0 }),
title => $::locale->text('Projects'),
allow_pdf_export => 1,
allow_csv_export => 1,
$report->set_column_order(@columns);
$report->set_export_options(qw(list filter));
$report->set_options_from_form;
+ $self->models->disable_plugin('paginated') if $report->{options}{output_format} =~ /^(pdf|csv)$/i;
$self->models->set_report_generator_sort_options(report => $report, sortable_columns => \@sortable);
$report->set_options(
+ raw_top_info_text => $self->render('project/report_top', { output => 0 }),
raw_bottom_info_text => $self->render('project/report_bottom', { output => 0 }),
);
}
use constant CONFIG_VARS => qw(copies countrycode dateformat timeformat default_media default_printer_id
email favorites fax hide_cvar_search_options mandatory_departments menustyle name
numberformat show_form_details signature stylesheet taxincluded_checked tel
- template_format vclimit focus_position form_cvars_nr_cols);
+ template_format vclimit focus_position form_cvars_nr_cols item_multiselect);
__PACKAGE__->meta->add_relationship(
groups => {
sub data_as_hash {
my $self = shift;
+
+ $self->data(YAML::Dump($_[0])) if @_;
+
return {} if !$self->data;
return $self->data if ref($self->{data}) eq 'HASH';
return YAML::Load($self->{data}) if !ref($self->{data});
sub set_data {
my ($self, %data) = @_;
- my $data = YAML::Load($self->data);
- $data->{$_} = $data{$_} for keys %data;
- $self->data(YAML::Dump($data));
+ $self->data(YAML::Dump({
+ %{ $self->data_as_hash },
+ %data,
+ }));
$self;
}
use SL::DB::MetaSetup::DeliveryOrder;
use SL::DB::Manager::DeliveryOrder;
use SL::DB::Helper::AttrHTML;
+use SL::DB::Helper::AttrSorted;
use SL::DB::Helper::FlattenToForm;
use SL::DB::Helper::LinkedRecords;
use SL::DB::Helper::TransNumberGenerator;
__PACKAGE__->meta->initialize;
__PACKAGE__->attr_html('notes');
+__PACKAGE__->attr_sorted('items');
__PACKAGE__->before_save('_before_save_set_donumber');
sub items { goto &orderitems; }
sub add_items { goto &add_orderitems; }
-sub items_sorted {
- my ($self) = @_;
-
- return [ sort {$a->position <=> $b->position } @{ $self->items } ];
-}
-
sub sales_order {
my $self = shift;
my %params = @_;
An alias for C<deliver_orer_items> for compatibility with other
sales/purchase models.
-=item C<items_sorted>
-
-Returns the delivery order items sorted by their ID (same order they
-appear in the frontend delivery order masks).
-
=item C<new_from $source, %params>
Creates a new C<SL::DB::DeliveryOrder> instance and copies as much
use SL::DB::Invoice;
use SL::DB::InvoiceItem;
use SL::DB::Language;
+use SL::DB::Letter;
+use SL::DB::LetterDraft;
use SL::DB::MakeModel;
use SL::DB::Note;
use SL::DB::Object::Hooks;
--- /dev/null
+package SL::DB::Helper::AttrSorted;
+
+use Carp;
+use List::Util qw(max);
+
+use strict;
+
+use parent qw(Exporter);
+our @EXPORT = qw(attr_sorted);
+
+sub attr_sorted {
+ my ($package, @attributes) = @_;
+
+ _make_sorted($package, $_) for @attributes;
+}
+
+sub _make_sorted {
+ my ($package, $attribute) = @_;
+
+ my %params = ref($attribute) eq 'HASH' ? %{ $attribute } : ( unsorted => $attribute );
+ my $unsorted_sub = $params{unsorted};
+ my $sorted_sub = $params{sorted} // $params{unsorted} . '_sorted';
+ my $position_sub = $params{position} // 'position';
+
+ no strict 'refs';
+
+ *{ $package . '::' . $sorted_sub } = sub {
+ my ($self) = @_;
+
+ croak 'not an accessor' if @_ > 1;
+
+ my $next_position = (max map { $_->$position_sub // 0 } @{ $self->$unsorted_sub }) + 1;
+ return [
+ map { $_->[1] }
+ sort { $a->[0] <=> $b->[0] }
+ map { [ $_->$position_sub // ($next_position++), $_ ] }
+ @{ $self->$unsorted_sub }
+ ];
+ };
+}
+
+1;
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::DB::Helper::AttrSorted - Attribute helper for sorting to-many
+relationships by a positional attribute
+
+=head1 SYNOPSIS
+
+ # In a Rose model:
+ use SL::DB::Helper::AttrSorted;
+ __PACKAGE__->attr_sorted('items');
+
+ # Use in controller or whereever:
+ my $items = @{ $invoice->items_sorted };
+
+=head1 OVERVIEW
+
+Creates a function that returns a sorted relationship. Requires that
+the linked objects have some kind of positional column.
+
+Items for which no position has been set (e.g. because they haven't
+been saved yet) are sorted last but kept in the order they appear in
+the unsorted list.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<attr_sorted @attributes>
+
+Package method. Call with the names of the attributes for which the
+helper methods should be created. Each attribute name can be either a
+scalar or a hash reference if you need custom options.
+
+If it's a hash reference then the following keys are supported:
+
+=over 2
+
+=item * C<unsorted> is the name of the relationship accessor that
+returns the list to be sorted. This is required, and if only a scalar
+is given instead of a hash reference then that scalar value is
+interpreted as C<unsorted>.
+
+=item * C<sorted> is the name of the new function to create. It
+defaults to the unsorted name postfixed with C<_sorted>.
+
+=item * C<position> must be a function name to be called on the
+objects to be sorted. It is supposed to return either C<undef> (no
+position has been set yet) or a numeric value. Defaults to C<position>.
+
+=back
+
+=back
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut
inventory => 'inventory',
invoice => 'invoice_item',
language => 'language',
+ letter => 'letter',
+ letter_draft => 'letter_draft',
makemodel => 'make_model',
notes => 'note',
orderitems => 'order_item',
-# This file has been auto-generated only because it didn't exist.
-# Feel free to modify it at will; it will not be overwritten automatically.
-
package SL::DB::Invoice;
use strict;
use SL::DB::Manager::Invoice;
use SL::DB::Helper::Payment qw(:ALL);
use SL::DB::Helper::AttrHTML;
+use SL::DB::Helper::AttrSorted;
use SL::DB::Helper::FlattenToForm;
use SL::DB::Helper::LinkedRecords;
use SL::DB::Helper::PriceTaxCalculator;
__PACKAGE__->meta->initialize;
__PACKAGE__->attr_html('notes');
+__PACKAGE__->attr_sorted('items');
__PACKAGE__->before_save('_before_save_set_invnumber');
sub items { goto &invoiceitems; }
sub add_items { goto &add_invoiceitems; }
-sub items_sorted {
- my ($self) = @_;
-
- return [ sort {$a->position <=> $b->position } @{ $self->items } ];
-}
-
sub is_sales {
# For compatibility with Order, DeliveryOrder
croak 'not an accessor' if @_ > 1;
--- /dev/null
+# This file has been auto-generated only because it didn't exist.
+# Feel free to modify it at will; it will not be overwritten automatically.
+
+package SL::DB::Letter;
+
+use strict;
+
+use SL::DB::MetaSetup::Letter;
+
+__PACKAGE__->meta->initialize;
+
+# Creates get_all, get_all_count, get_all_iterator, delete_all and update_all.
+__PACKAGE__->meta->make_manager_class;
+
+1;
--- /dev/null
+# This file has been auto-generated only because it didn't exist.
+# Feel free to modify it at will; it will not be overwritten automatically.
+
+package SL::DB::LetterDraft;
+
+use strict;
+
+use SL::DB::MetaSetup::LetterDraft;
+
+__PACKAGE__->meta->initialize;
+
+# Creates get_all, get_all_count, get_all_iterator, delete_all and update_all.
+__PACKAGE__->meta->make_manager_class;
+
+1;
is_transfer_out => { type => 'boolean', default => 'false', not_null => 1 },
itime => { type => 'timestamp', default => 'now()' },
language_id => { type => 'integer' },
+ letternumber => { type => 'integer' },
max_future_booking_interval => { type => 'integer', default => 360 },
mtime => { type => 'timestamp' },
normalize_part_descriptions => { type => 'boolean', default => 'true' },
--- /dev/null
+# This file has been auto-generated. Do not modify it; it will be overwritten
+# by rose_auto_create_model.pl automatically.
+package SL::DB::Letter;
+
+use strict;
+
+use base qw(SL::DB::Object);
+
+__PACKAGE__->meta->table('letter');
+
+__PACKAGE__->meta->columns(
+ id => { type => 'integer', not_null => 1, sequence => 'id' },
+ vc_id => { type => 'integer', not_null => 1 },
+ rcv_name => { type => 'text' },
+ rcv_contact => { type => 'text' },
+ rcv_address => { type => 'text' },
+ rcv_countrycode => { type => 'text' },
+ rcv_zipcode => { type => 'text' },
+ rcv_city => { type => 'text' },
+ letternumber => { type => 'text' },
+ jobnumber => { type => 'text' },
+ text_created_for => { type => 'text' },
+ date => { type => 'text' },
+ subject => { type => 'text' },
+ greeting => { type => 'text' },
+ body => { type => 'text' },
+ close => { type => 'text' },
+ company_name => { type => 'text' },
+ employee_id => { type => 'integer' },
+ employee_position => { type => 'text' },
+ salesman_id => { type => 'integer' },
+ salesman_position => { type => 'text' },
+ itime => { type => 'timestamp', default => 'now()' },
+ mtime => { type => 'timestamp' },
+ rcv_country => { type => 'text' },
+ page_created_for => { type => 'text' },
+ cp_id => { type => 'integer' },
+);
+
+__PACKAGE__->meta->primary_key_columns([ 'id' ]);
+
+__PACKAGE__->meta->allow_inline_column_values(1);
+
+__PACKAGE__->meta->foreign_keys(
+ employee => {
+ class => 'SL::DB::Employee',
+ key_columns => { employee_id => 'id' },
+ },
+
+ salesman => {
+ class => 'SL::DB::Employee',
+ key_columns => { salesman_id => 'id' },
+ },
+);
+
+# __PACKAGE__->meta->initialize;
+
+1;
+;
--- /dev/null
+# This file has been auto-generated. Do not modify it; it will be overwritten
+# by rose_auto_create_model.pl automatically.
+package SL::DB::LetterDraft;
+
+use strict;
+
+use base qw(SL::DB::Object);
+
+__PACKAGE__->meta->table('letter_draft');
+
+__PACKAGE__->meta->columns(
+ body => { type => 'text' },
+ close => { type => 'text' },
+ company_name => { type => 'text' },
+ cp_id => { type => 'integer' },
+ date => { type => 'date' },
+ employee_id => { type => 'integer' },
+ employee_position => { type => 'text' },
+ greeting => { type => 'text' },
+ id => { type => 'integer', not_null => 1, sequence => 'id' },
+ intnotes => { type => 'text' },
+ itime => { type => 'timestamp', default => 'now()' },
+ jobnumber => { type => 'text' },
+ letternumber => { type => 'text' },
+ mtime => { type => 'timestamp' },
+ page_created_for => { type => 'text' },
+ rcv_address => { type => 'text' },
+ rcv_city => { type => 'text' },
+ rcv_contact => { type => 'text' },
+ rcv_country => { type => 'text' },
+ rcv_countrycode => { type => 'text' },
+ rcv_name => { type => 'text' },
+ rcv_zipcode => { type => 'text' },
+ reference => { type => 'text' },
+ salesman_id => { type => 'integer' },
+ salesman_position => { type => 'text' },
+ subject => { type => 'text' },
+ text_created_for => { type => 'text' },
+ vc_id => { type => 'integer', not_null => 1 },
+);
+
+__PACKAGE__->meta->primary_key_columns([ 'id' ]);
+
+__PACKAGE__->meta->allow_inline_column_values(1);
+
+__PACKAGE__->meta->foreign_keys(
+ contact => {
+ class => 'SL::DB::Contact',
+ key_columns => { cp_id => 'cp_id' },
+ },
+
+ employee => {
+ class => 'SL::DB::Employee',
+ key_columns => { employee_id => 'id' },
+ },
+
+ salesman => {
+ class => 'SL::DB::Employee',
+ key_columns => { salesman_id => 'id' },
+ },
+);
+
+1;
+;
use SL::DB::MetaSetup::Order;
use SL::DB::Manager::Order;
use SL::DB::Helper::AttrHTML;
+use SL::DB::Helper::AttrSorted;
use SL::DB::Helper::FlattenToForm;
use SL::DB::Helper::LinkedRecords;
use SL::DB::Helper::PriceTaxCalculator;
__PACKAGE__->meta->initialize;
__PACKAGE__->attr_html('notes');
+__PACKAGE__->attr_sorted('items');
__PACKAGE__->before_save('_before_save_set_ord_quo_number');
sub items { goto &orderitems; }
sub add_items { goto &add_orderitems; }
-sub items_sorted {
- my ($self) = @_;
-
- return [ sort {$a->position <=> $b->position } @{ $self->items } ];
-}
-
sub type {
my $self = shift;
my @errors;
push @errors, $::locale->text('The name must not be empty.') if !$self->name;
push @errors, $::locale->text('Price or discount must not be zero.') if !$self->price && !$self->discount && !$self->reduction;
- push @errors, $::locale->text('Pirce rules must have at least one rule.') if !@{[ $self->items ]};
+ push @errors, $::locale->text('Price rules must have at least one rule.') if !@{[ $self->items ]};
+ push @errors, $_->validate for $self->items;
return @errors;
}
: do { die "unknown type $type" }
}
+sub validate {
+ my ($self) = @_;
+
+ my @errors;
+ push @errors, t8('Rule for part must not be empty') if $self->type eq 'part' && !$self->value_int;
+ push @errors, t8('Rule for customer must not be empty') if $self->type eq 'customer' && !$self->value_int;
+ push @errors, t8('Rule for vendor must not be empty') if $self->type eq 'vendor' && !$self->value_int;
+
+ return @errors;
+}
+
1;
use SL::DB::MetaSetup::PurchaseInvoice;
use SL::DB::Manager::PurchaseInvoice;
use SL::DB::Helper::AttrHTML;
+use SL::DB::Helper::AttrSorted;
use SL::DB::Helper::LinkedRecords;
use SL::DB::Helper::Payment qw(:ALL);
use SL::Locale::String qw(t8);
__PACKAGE__->meta->initialize;
__PACKAGE__->attr_html('notes');
+__PACKAGE__->attr_sorted('items');
sub items { goto &invoiceitems; }
sub add_items { goto &add_invoiceitems; }
-sub items_sorted {
- my ($self) = @_;
-
- return [ sort {$a->position <=> $b->position } @{ $self->items } ];
-}
-
sub is_sales {
# For compatibility with Order, DeliveryOrder
croak 'not an accessor' if @_ > 1;
$query =
qq|INSERT INTO ar (id, invnumber, transdate, gldate, customer_id,
taxincluded, amount, netamount, paid, duedate,
- invoice, currency_id, notes,
+ invoice, currency_id, taxzone_id, notes,
employee_id)
VALUES (
?, -- id
(SELECT duedate FROM dunning WHERE dunning_id = ? LIMIT 1),
'f', -- invoice
(SELECT id FROM currencies WHERE name = ?), -- curr
+ --taxzone_id:
+ (SELECT taxzone_id FROM customer WHERE id =
+ (SELECT ar.customer_id
+ FROM dunning dn
+ LEFT JOIN ar ON (dn.trans_id = ar.id)
+ WHERE dn.dunning_id = ?
+ LIMIT 1)
+ ),
?, -- notes
-- employee_id:
(SELECT id FROM employee WHERE login = ?)
$amount,
$dunning_id, # duedate
$curr, # default currency
+ $dunning_id, # taxzone_id
sprintf($main::locale->text('Automatically created invoice for fee and interest for dunning %s'), $dunning_id), # notes
$::myconfig{login}); # employee_id
do_query($form, $dbh, $query, @values);
my $query =
qq|SELECT dord.id, dord.donumber, dord.ordnumber, dord.cusordnumber,
dord.transdate, dord.reqdate,
- ct.${vc}number, ct.name, dord.${vc}_id, dord.globalproject_id,
+ ct.${vc}number, ct.name, ct.business_id,
+ dord.${vc}_id, dord.globalproject_id,
dord.closed, dord.delivered, dord.shippingpoint, dord.shipvia,
- dord.transaction_description,
+ dord.transaction_description, dord.itime::DATE AS insertdate,
pr.projectnumber AS globalprojectnumber,
dep.description AS department,
e.name AS employee,
push @values, conv_i($form->{project_id}), conv_i($form->{project_id});
}
+ if ($form->{"business_id"}) {
+ push @where, qq|ct.business_id = ?|;
+ push @values, conv_i($form->{"business_id"});
+ }
+
if ($form->{"${vc}_id"}) {
push @where, qq|dord.${vc}_id = ?|;
push @values, $form->{"${vc}_id"};
push @values, conv_date($form->{reqdateto});
}
+ if($form->{insertdatefrom}) {
+ push @where, qq|dord.itime::DATE >= ?|;
+ push@values, conv_date($form->{insertdatefrom});
+ }
+
+ if($form->{insertdateto}) {
+ push @where, qq|dord.itime::DATE <= ?|;
+ push @values, conv_date($form->{insertdateto});
+ }
+
if (@where) {
$query .= " WHERE " . join(" AND ", map { "($_)" } @where);
}
"shipvia" => "dord.shipvia",
"transaction_description" => "dord.transaction_description",
"department" => "lower(dep.description)",
+ "insertdate" => "dord.itime",
);
my $sortdir = !defined $form->{sortdir} ? 'ASC' : $form->{sortdir} ? 'ASC' : 'DESC';
dord.shipto_id,
dord.globalproject_id, dord.delivered, dord.transaction_description,
dord.taxzone_id, dord.taxincluded, dord.terms, (SELECT cu.name FROM currencies cu WHERE cu.id=dord.currency_id) AS currency,
- dord.delivery_term_id
+ dord.delivery_term_id, dord.itime::DATE AS insertdate
FROM delivery_orders dord
JOIN ${vc} cv ON (dord.${vc}_id = cv.id)
LEFT JOIN employee e ON (dord.employee_id = e.id)
use File::Basename;
use List::MoreUtils qw(all);
use List::Util qw(first);
-use POSIX;
use SL::ArchiveZipFixes;
use SL::Auth;
use SL::Dispatcher::AuthHandler;
}
$::request->{layout} = SL::Layout::Dispatcher->new(style => 'admin');
- $::request->layout->no_menu(1);
$::auth->delete_session_value('admin_password');
$::auth->punish_wrong_login;
SL::Dispatcher::show_error('admin/adminlogin', 'password');
variables => $form,
save_validity => 1);
+ # Delete saved custom variable values for configs that have been
+ # marked invalid for this part.
+ $query = <<SQL;
+ DELETE FROM custom_variables
+ WHERE (config_id IN (
+ SELECT val.config_id
+ FROM custom_variables_validity val
+ LEFT JOIN custom_variable_configs val_cfg ON (val.config_id = val_cfg.id)
+ WHERE (val_cfg.module = 'IC')
+ AND (val.trans_id = ?)))
+ AND (trans_id = ?)
+SQL
+ do_query($form, $dbh, $query, ($form->{id}) x 2);
+
# commit
my $rc = $dbh->commit;
my @apoe_filters = qw(transdate);
my @like_filters = (@simple_filters, @invoice_oi_filters);
my @all_columns = (@simple_filters, @makemodel_filters, @apoe_filters, @project_filters, qw(serialnumber));
- my @simple_l_switches = (@all_columns, qw(notes listprice sellprice lastcost priceupdate weight unit rop image));
+ my @simple_l_switches = (@all_columns, qw(notes listprice sellprice lastcost priceupdate weight unit rop image shop insertdate));
my @oe_flags = qw(bought sold onorder ordered rfq quoted);
my @qsooqr_flags = qw(invnumber ordnumber quonumber trans_id name module qty);
my @deliverydate_flags = qw(deliverydate);
'ioi.id' => 'ioi_id',
'ioi.ioi' => 'ioi',
'projectdescription' => 'projectdescription',
+ 'insertdate' => 'insertdate',
);
my %real_column = (
projectdescription => 'description',
+ insertdate => 'itime::DATE',
);
if (($form->{searchitems} eq 'assembly') && $form->{l_lastcost}) {
}
}
+ # special case insertdate
+ if (grep { $form->{$_} } qw(insertdatefrom insertdateto)) {
+ $form->{"l_insertdate"} = 1;
+ push @select_tokens, 'insertdate';
+
+ my $token_builder = $make_token_builder->();
+ my $token = $token_builder->('insertdate');
+
+ for (qw(insertdatefrom insertdateto)) {
+ next unless $form->{$_};
+ push @where_tokens, sprintf "$token %s ?", /from$/ ? '>=' : '<=';
+ push @bind_vars, $form->{$_};
+ }
+ }
+
if ($form->{"partsgroup_id"}) {
$form->{"l_partsgroup"} = '1'; # show the column
push @where_tokens, "pg.id = ?";
push @bind_vars, $form->{"partsgroup_id"};
}
+ if ($form->{shop} ne '') {
+ $form->{l_shop} = '1'; # show the column
+ if ($form->{shop} eq '0' || $form->{shop} eq 'f') {
+ push @where_tokens, 'NOT p.shop';
+ $form->{shop} = 'f';
+ } else {
+ push @where_tokens, 'p.shop';
+ }
+ }
+
foreach (@like_filters) {
next unless $form->{$_};
$form->{"l_$_"} = '1'; # show the column
my $token_builder = $make_token_builder->(\%joins_needed);
- my @sort_cols = (@simple_filters, qw(id priceupdate onhand invnumber ordnumber quonumber name serialnumber soldtotal deliverydate));
+ my @sort_cols = (@simple_filters, qw(id priceupdate onhand invnumber ordnumber quonumber name serialnumber soldtotal deliverydate insertdate shop));
$form->{sort} = 'id' unless grep { $form->{"l_$_"} } grep { $form->{sort} eq $_ } @sort_cols; # sort by id if unknown or invisible column
my $sort_order = ($form->{revers} ? ' DESC' : ' ASC');
my $order_clause = " ORDER BY " . $token_builder->($form->{sort}) . ($form->{revers} ? ' DESC' : ' ASC');
if ($form->{searchitems} eq 'assembly' && $form->{bom}) {
$query =
qq|SELECT p.id, p.partnumber, p.description, a.qty AS onhand,
- p.unit, p.notes,
+ p.unit, p.notes, p.itime::DATE as insertdate,
p.sellprice, p.listprice, p.lastcost,
p.rop, p.weight, p.priceupdate,
p.image, p.drawing, p.microfiche,
v.email, v.cc, v.bcc, v.language_id, v.payment_id, v.delivery_term_id,
v.street, v.zipcode, v.city, v.country, v.taxzone_id, cu.name AS curr, v.direct_debit,
$duedate + COALESCE(pt.terms_netto, 0) AS duedate,
- b.description AS business
+ b.discount AS tradediscount, b.description AS business
FROM vendor v
LEFT JOIN business b ON (b.id = v.business_id)
LEFT JOIN payment_terms pt ON (v.payment_id = pt.id)
our @EXPORT = qw(t8);
-use overload '""' => \&translated;
+use overload
+ '""' => \&translated,
+ eq => \&my_eq;
sub translated {
my ($self) = @_;
return SL::Locale::String->new(untranslated => $string, args => [ @_ ]);
}
+sub my_eq {
+ $_[1] eq $_[0]->translated;
+}
+
sub TO_JSON {
return $_[0]->translated;
}
qq| o.closed, o.delivered, o.quonumber, o.cusordnumber, o.shippingpoint, o.shipvia, | .
qq| o.transaction_description, | .
qq| o.marge_total, o.marge_percent, | .
+ qq| o.itime::DATE AS insertdate, | .
qq| ex.$rate AS exchangerate, | .
qq| pr.projectnumber AS globalprojectnumber, | .
qq| e.name AS employee, s.name AS salesman, | .
push(@values, conv_date($form->{reqdateto}));
}
+ if($form->{insertdatefrom}) {
+ $query .= qq| AND o.itime::DATE >= ?|;
+ push(@values, conv_date($form->{insertdatefrom}));
+ }
+
+ if($form->{insertdateto}) {
+ $query .= qq| AND o.itime::DATE <= ?|;
+ push(@values, conv_date($form->{insertdateto}));
+ }
+
if ($form->{shippingpoint}) {
$query .= qq| AND o.shippingpoint ILIKE ?|;
push(@values, '%' . $form->{shippingpoint} . '%');
push @values, conv_date($form->{expected_billing_date_to});
}
+ my ($cvar_where, @cvar_values) = CVar->build_filter_query('module' => 'CT',
+ 'trans_id_field' => 'ct.id',
+ 'filter' => $form,
+ );
+ if ($cvar_where) {
+ $query .= qq| AND ($cvar_where)|;
+ push @values, @cvar_values;
+ }
+
my $sortdir = !defined $form->{sortdir} ? 'ASC' : $form->{sortdir} ? 'ASC' : 'DESC';
- my $sortorder = join(', ', map { "${_} ${sortdir} " } ("o.id", $form->sort_columns("transdate", $ordnumber, "name")));
+ my $sortorder = join(', ', map { "${_} ${sortdir} " } ("o.id", $form->sort_columns("transdate", $ordnumber, "name"), "o.itime"));
my %allowed_sort_columns = (
"transdate" => "o.transdate",
"reqdate" => "o.reqdate",
"shipvia" => "o.shipvia",
"transaction_description" => "o.transaction_description",
"shippingpoint" => "o.shippingpoint",
+ "insertdate" => "o.itime",
"taxzone" => "tz.description",
);
if ($form->{sort} && grep($form->{sort}, keys(%allowed_sort_columns))) {
- $sortorder = $allowed_sort_columns{$form->{sort}} . " ${sortdir}";
+ $sortorder = $allowed_sort_columns{$form->{sort}} . " ${sortdir}" . ", o.itime ${sortdir}";
}
$query .= qq| ORDER by | . $sortorder;
o.closed, o.reqdate, o.quonumber, o.department_id, o.cusordnumber,
d.description AS department, o.payment_id, o.language_id, o.taxzone_id,
o.delivery_customer_id, o.delivery_vendor_id, o.proforma, o.shipto_id,
- o.globalproject_id, o.delivered, o.transaction_description, o.delivery_term_id
- , o.order_probability, o.expected_billing_date
+ o.globalproject_id, o.delivered, o.transaction_description, o.delivery_term_id,
+ o.itime::DATE AS insertdate, o.order_probability, o.expected_billing_date
FROM oe o
JOIN ${vc} cv ON (o.${vc}_id = cv.id)
LEFT JOIN employee e ON (o.employee_id = e.id)
use parent 'SL::DB::Object';
use Rose::Object::MakeMethods::Generic (
scalar => [ qw(record_item record) ],
+ 'array --get_set_init' => [ qw(all_price_sources) ],
);
use List::UtilsBy qw(min_by max_by);
use SL::PriceSource::Price;
use SL::Locale::String;
-sub all_price_sources {
+sub init_all_price_sources {
my ($self) = @_;
- map {
+ [ map {
$_->new(record_item => $self->record_item, record => $self->record)
- } SL::PriceSource::ALL->all_enabled_price_sources
+ } SL::PriceSource::ALL->all_enabled_price_sources ]
}
sub price_from_source {
Each algorithm can access details of the record to realize dependencies on
part, customer, vendor, date, quantity etc, which was previously not possible.
-=head1 BACKGROUND AND PHILOSOPY
+=head1 BACKGROUND AND PHILOSOPHY
sql ledger and subsequently Lx-Office had three prices per part: sellprice,
listprice and lastcost. At the moment a part is loaded into a record, the
-applicable price is copied and after that free to be changed.
+applicable price is copied and after that it is free to be changed.
-Later on additional things joined. Various types of discount, vendor pricelists
+Later on additional things were added. Various types of discount, vendor pricelists
and the infamous price groups. The problem is not that those didn't work, the
-problem is, that they had to guess to much when to change a price with the
-available price from database, and when to leave the user entered price.
+problem is, that they had to guess too much when to change a price with the
+available price from the database, and when to leave the user entered price.
Unrelated to that, users asked for more ways to store special prices, based on
qty (block pricing, bulk discount), based on date (special offers), based on
)
}
- if ($business->id != $self->customer_vendor->business->id) {
+ if (!$self->customer_vendor->business ||
+ $business->id != $self->customer_vendor->business->id) {
return SL::PriceSource::Discount->new(
discount => $business->discount,
spec => $business->id,
description => t8('Business Discount'),
price_source => $self,
- invalid => t8('This discount is only valid for business #1', $business->full_description),
+ invalid => t8('This discount is only valid for business #1', $business->displayable_name),
)
}
=head1 NAME
-SL::PriceSource::Discount - contrainer to pass calculated discounts around
+SL::PriceSource::Discount - container to pass calculated discounts around
=head1 SYNOPSIS
);
# invalid discount
- SL::PriceSource::Dicount->new(
+ SL::PriceSource::Discount->new(
discount => $original_discount,
spec => $original_spec,
description => $original_description,
my $prices = SL::DB::Manager::Price->get_all(
query => [ parts_id => $item->parts_id, price => { gt => 0 } ],
with_objects => 'pricegroup',
- order_by => 'pricegroun.id',
+ order_by => 'pricegroup.id',
);
return () unless @$prices;
use parent qw(SL::PriceSource::Base);
use SL::DB::Vendor;
-use SL::PriceSource::Price;
+use SL::PriceSource::Discount;
use SL::Locale::String;
sub name { 'vendor_discount' }
return unless $self->record->vendor;
return unless $self->record->vendor->discount != 0;
- SL::PriceSource::Vendor->new(
+ SL::PriceSource::Discount->new(
discount => $self->record->vendor->discount,
spec => $self->record->vendor->id,
description => t8('Vendor Discount'),
use strict;
use parent qw(Rose::Object);
-use POSIX;
+use POSIX ();
sub separator { "_" }
$form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
$form->{SHOW_BUSINESS_TYPES} = scalar @{ $form->{ALL_BUSINESS_TYPES} } > 0;
+ $form->{CT_CUSTOM_VARIABLES} = CVar->get_configs('module' => 'CT');
+ ($form->{CT_CUSTOM_VARIABLES_FILTER_CODE},
+ $form->{CT_CUSTOM_VARIABLES_INCLUSION_CODE}) = CVar->render_search_options('variables' => $form->{CT_CUSTOM_VARIABLES},
+ 'include_prefix' => 'l_',
+ 'include_value' => 'Y');
+
# constants and subs for template
$form->{vc_keys} = sub { "$_[0]->{name}--$_[0]->{id}" };
datepaid due duedate transaction_description notes salesman employee shippingpoint shipvia
marge_total marge_percent globalprojectnumber customernumber country ustid taxzone payment_terms charts customertype);
+ my $ct_cvar_configs = CVar->get_configs('module' => 'CT');
+ my @ct_includeable_custom_variables = grep { $_->{includeable} } @{ $ct_cvar_configs };
+ my @ct_searchable_custom_variables = grep { $_->{searchable} } @{ $ct_cvar_configs };
+
+ my %column_defs_cvars = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @ct_includeable_custom_variables;
+ push @columns, map { "cvar_$_->{name}" } @ct_includeable_custom_variables;
+
my @hidden_variables = map { "l_${_}" } @columns;
push @hidden_variables, "l_subtotal", qw(open closed customer invnumber ordnumber cusordnumber transaction_description notes project_id transdatefrom transdateto employee_id salesman_id business_id);
+ push @hidden_variables, map { "cvar_$_->{name}" } @ct_searchable_custom_variables;
$href = build_std_url('action=ar_transactions', grep { $form->{$_} } @hidden_variables);
'payment_terms' => { 'text' => $locale->text('Payment Terms'), },
'charts' => { 'text' => $locale->text('Buchungskonto'), },
'customertype' => { 'text' => $locale->text('Customer type'), },
+ %column_defs_cvars,
);
foreach my $name (qw(id transdate duedate invnumber ordnumber cusordnumber name datepaid employee shippingpoint shipvia transaction_description)) {
$report->set_sort_indicator($form->{sort}, $form->{sortdir});
+ CVar->add_custom_variables_to_report('module' => 'CT',
+ 'trans_id_field' => 'customer_id',
+ 'configs' => $ct_cvar_configs,
+ 'column_defs' => \%column_defs,
+ 'data' => $form->{AR});
+
my @options;
if ($form->{customer}) {
push @options, $locale->text('Customer') . " : $form->{customer}";
}
}
+ if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
+ push @options, $locale->text('Insert Date');
+ push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1) if $form->{insertdatefrom};
+ push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1) if $form->{insertdateto};
+ };
+
my @columns = (
'id', 'name', "$form->{db}number", 'contact', 'phone', 'discount',
'fax', 'email', 'taxnumber', 'street', 'zipcode' , 'city',
'business', 'payment', 'invnumber', 'ordnumber', 'quonumber', 'salesman',
- 'country'
+ 'country', 'insertdate', 'pricegroup'
);
my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
'salesman' => { 'text' => $locale->text('Salesman'), },
'discount' => { 'text' => $locale->text('Discount'), },
'payment' => { 'text' => $locale->text('Payment Terms'), },
+ 'insertdate' => { 'text' => $locale->text('Insert Date'), },
+ 'pricegroup' => { 'text' => $locale->text('Pricegroup'), },
%column_defs_cvars,
);
my @hidden_variables = ( qw(
db status obsolete name contact email cp_name addr_street addr_zipcode
- addr_city addr_country business_id salesman_id
+ addr_city addr_country business_id salesman_id insertdateto insertdatefrom
), "$form->{db}number",
map({ "cvar_$_->{name}" } @searchable_custom_variables),
map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
#
#======================================================================
-use POSIX;
+use POSIX qw(strftime);
use SL::IS;
use SL::PE;
set_headings($form->{"id"} ? "edit" : "add");
+ $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
+
$form->{update} = 1;
my $payment_id;
if ($rows > 1) {
- select_item(mode => $mode);
+ select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
::end_of_request();
} else {
$form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
$form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
$form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"});
+ $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
}
display_form();
$form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
- $form->get_lists("projects" => { "key" => "ALL_PROJECTS",
- "all" => 1 },
- "departments" => "ALL_DEPARTMENTS",
- "$form->{vc}s" => "ALL_VC");
+ $form->get_lists("projects" => { "key" => "ALL_PROJECTS",
+ "all" => 1 },
+ "departments" => "ALL_DEPARTMENTS",
+ "$form->{vc}s" => "ALL_VC",
+ "business_types" => "ALL_BUSINESS_TYPES");
$form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
$form->{SHOW_VC_DROP_DOWN} = $myconfig{vclimit} > scalar @{ $form->{ALL_VC} };
shipvia globalprojectnumber
transaction_description department
open delivered
+ insertdate
);
$form->{l_open} = $form->{l_closed} = "Y" if ($form->{open} && $form->{closed});
my @hidden_variables = map { "l_${_}" } @columns;
push @hidden_variables, $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered donumber ordnumber serialnumber cusordnumber
transaction_description transdatefrom transdateto reqdatefrom reqdateto
- type vc employee_id salesman_id project_id);
+ type vc employee_id salesman_id project_id
+ insertdatefrom insertdateto business_id);
my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
'open' => { 'text' => $locale->text('Open'), },
'delivered' => { 'text' => $locale->text('Delivered'), },
'department' => { 'text' => $locale->text('Department'), },
+ 'insertdate' => { 'text' => $locale->text('Insert Date'), },
);
- foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department)) {
+ foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department insertdate)) {
my $sortdir = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
$column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
}
push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
}
push @options, $locale->text('Serial Number') . " : $form->{serialnumber}" if $form->{serialnumber};
+ if ($form->{business_id}) {
+ my $vc_type_label = $form->{vc} eq 'customer' ? $locale->text('Customer type') : $locale->text('Vendor type');
+ push @options, $vc_type_label . " : " . SL::DB::Business->new(id => $form->{business_id})->load->description;
+ }
if ($form->{transaction_description}) {
push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
}
push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1) if $form->{reqdatefrom};
push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{reqdateto}, 1) if $form->{reqdateto};
};
+ if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
+ push @options, $locale->text('Insert Date');
+ push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1) if $form->{insertdatefrom};
+ push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1) if $form->{insertdateto};
+ };
if ($form->{open}) {
push @options, $locale->text('Open');
}
'drawing' => { 'text' => $locale->text('Drawing'), },
'ean' => { 'text' => $locale->text('EAN'), },
'image' => { 'text' => $locale->text('Image'), },
+ 'insertdate' => { 'text' => $locale->text('Insert Date'), },
'invnumber' => { 'text' => $locale->text('Invoice Number'), },
'lastcost' => { 'text' => $locale->text('Last Cost'), },
'linetotallastcost' => { 'text' => $locale->text('Extended'), },
'transdate' => { 'text' => $locale->text('Transdate'), },
'unit' => { 'text' => $locale->text('Unit'), },
'weight' => { 'text' => $locale->text('Weight'), },
+ 'shop' => { 'text' => $locale->text('Shopartikel'), },
'projectnumber' => { 'text' => $locale->text('Project Number'), },
'projectdescription' => { 'text' => $locale->text('Project Description'), },
);
microfiche => $locale->text('Microfiche') . ": '$form->{microfiche}'",
l_soldtotal => $locale->text('Qty in Selected Records'),
ean => $locale->text('EAN') . ": '$form->{ean}'",
+ insertdatefrom => $locale->text('Insert Date') . ": " . $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1),
+ insertdateto => $locale->text('Insert Date') . ": " . $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1),
);
my @itemstatus_keys = qw(active obsolete orphaned onhand short);
my @callback_keys = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
- drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto ean);
+ drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto insertdatefrom insertdateto ean shop);
# calculate dependencies
for (@itemstatus_keys, @callback_keys) {
linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
priceupdate weight image drawing microfiche invnumber ordnumber quonumber
transdate name serialnumber deliverydate ean projectnumber projectdescription
+ insertdate shop
);
my $pricegroups = SL::DB::Manager::Pricegroup->get_all(sort => 'id');
%column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
- map { $column_defs{$_}->{align} = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal), @pricegroup_columns;
+ map { $column_defs{$_}->{align} = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal shop), @pricegroup_columns;
my @hidden_variables = (
- qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups),
+ qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups insertdatefrom insertdateto),
@itemstatus_keys,
@callback_keys,
map({ "cvar_$_->{name}" } @searchable_custom_variables),
my $callback = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
- my @sort_full = qw(partnumber description onhand soldtotal deliverydate);
+ my @sort_full = qw(partnumber description onhand soldtotal deliverydate insertdate shop);
my @sort_no_revers = qw(partsgroup bin priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
foreach my $col (@sort_full) {
$row->{weight}->{data} .= ' ' . $defaults->{weightunit};
+ # 'yes' and 'no' for boolean value shop
+ if ($form->{l_shop}) {
+ $row->{shop}{data} = $row->{shop}{data}? $::locale->text('yes') : $::locale->text('no');
+ }
+
if (!$ref->{assemblyitem}) {
foreach my $col (@subtotal_columns) {
$totals{$col} += $soldtotal * $ref->{$col};
if ($rows > 1) {
$form->{makemodel_rows}--;
- select_item(mode => 'IC');
+ select_item(mode => 'IC', pre_entered_qty => $form->parse_amount(\%myconfig, $form->{"qty_$i"}));
::end_of_request();
} else {
map { $form->{item_list}[$i]{$_} =~ s/\"/"/g }
$main::lxdebug->enter_sub();
my %params = @_;
- my $mode = $params{mode} || croak "Missing parameter 'mode'";
-
+ my $mode = $params{mode} || croak "Missing parameter 'mode'";
+ my $pre_entered_qty = $params{pre_entered_qty} || 1;
_check_io_auth();
my $previous_form = $::auth->save_form_in_session(form => $::form);
# delete action variable
delete @{$::form}{qw(action item_list)};
- print $::form->parse_html_template('io/select_item', { PREVIOUS_FORM => $previous_form,
- MODE => $mode,
- ITEM_LIST => \@item_list,
- IS_ASSEMBLY => $mode eq 'IC',
- IS_PURCHASE => $mode eq 'IS' });
+ print $::form->parse_html_template('io/select_item', { PREVIOUS_FORM => $previous_form,
+ MODE => $mode,
+ ITEM_LIST => \@item_list,
+ IS_ASSEMBLY => $mode eq 'IC',
+ IS_PURCHASE => $mode eq 'IS',
+ PRE_ENTERED_QTY => $pre_entered_qty, });
$main::lxdebug->leave_sub();
}
$::auth->restore_form_from_session($form->{select_item_previous_form} || croak('Missing previous form ID'), form => $form);
- my $mode = delete($form->{select_item_mode}) || croak 'Missing item selection mode';
- my $id = delete($form->{select_item_id}) || croak 'Missing item selection ID';
- my $i = $form->{ $mode eq 'IC' ? 'assembly_rows' : 'rowcount' };
+ my $mode = delete($form->{select_item_mode}) || croak 'Missing item selection mode';
+ my $row_key = $mode eq 'IC' ? 'assembly_rows' : 'rowcount';
+ my $curr_row = $form->{ $row_key };
- if ( $mode eq 'IC' ) {
- # assembly mode:
- # the qty variables of the existing assembly items are all still formatted, so we parse them here (1 .. $i-1)
- # including the qty of the just added part ($i)
- $form->{"qty_$_"} = $form->parse_amount(\%myconfig, $form->{"qty_$_"}) for (1 .. $i);
- };
+ my $row = $curr_row;
- $form->{"id_${i}"} = $id;
+ if ($myconfig{item_multiselect}) {
+ foreach (grep(/^select_qty_/, keys(%{ $form }))) {
+ next unless $form->{$_};
+ $_ =~ /^select_qty_(\d+)/;
+ $form->{"id_${row}"} = $1;
+ $form->{"qty_${row}"} = $form->{$_};
+ $row++;
+ }
+ } else {
+ $form->{"id_${row}"} = delete($form->{select_item_id}) || croak 'Missing item selection ID';
+ $row++;
+ }
+
+ map { $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) }
+ qw(sellprice listprice weight);
- if ($mode eq 'IS') {
- IS->retrieve_item(\%myconfig, \%$form);
- } elsif ($mode eq 'IR') {
- IR->retrieve_item(\%myconfig, \%$form);
- } elsif ($mode eq 'IC') {
- IC->assembly_item(\%myconfig, \%$form);
+ if ( $mode eq 'IC' ) {
+ # assembly mode:
+ # the qty variables of the existing assembly items are all still formatted, so we parse them here
+ # including the qty of the just added part
+ $form->{"qty_$_"} = $form->parse_amount(\%myconfig, $form->{"qty_$_"}) for (1 .. $row - 1);
} else {
- croak "Invalid item selection mode '${mode}'";
+ if ($myconfig{item_multiselect}) {
+ # other modes and multiselection:
+ # parse all newly entered qtys
+ $form->{"qty_$_"} = $form->parse_amount(\%myconfig, $form->{"qty_$_"}) for ($curr_row .. $row - 1);
+ }
}
- my $new_item = $form->{item_list}->[0] || croak "No item found for mode '${mode}' and ID '${id}'";
+ for my $i ($curr_row .. $row - 1) {
+ $form->{ $row_key } = $i;
- # if there was a price entered, override it
- my $sellprice;
- unless ( $mode eq 'IC' ) {
- $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
- };
+ my $id = $form->{"id_${i}"};
- my @new_fields =
- qw(id partnumber description sellprice listprice inventory_accno
- income_accno expense_accno bin unit weight assembly taxaccounts
- partsgroup formel longdescription not_discountable partnotes lastcost
- price_factor_id price_factor);
+ delete $form->{item_list};
- my $ic_cvar_configs = CVar->get_configs(module => 'IC');
- push @new_fields, map { "ic_cvar_$_->{name}" } @{ $ic_cvar_configs };
+ if ($mode eq 'IS') {
+ IS->retrieve_item(\%myconfig, \%$form);
+ } elsif ($mode eq 'IR') {
+ IR->retrieve_item(\%myconfig, \%$form);
+ } elsif ($mode eq 'IC') {
+ IC->assembly_item(\%myconfig, \%$form);
+ } else {
+ croak "Invalid item selection mode '${mode}'";
+ }
- map { $form->{"${_}_$i"} = $new_item->{$_} } @new_fields;
+ my $new_item = $form->{item_list}->[0] || croak "No item found for mode '${mode}' and ID '${id}'";
- if (my $record = _make_record()) {
- my $price_source = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
- my $best_price = $price_source->best_price;
+ # if there was a price entered, override it
+ my $sellprice;
+ unless ( $mode eq 'IC' ) {
+ $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
+ };
- if ($best_price) {
- $::form->{"sellprice_$i"} = $best_price->price;
- $::form->{"active_price_source_$i"} = $best_price->source;
- }
+ my @new_fields =
+ qw(id partnumber description sellprice listprice inventory_accno
+ income_accno expense_accno bin unit weight assembly taxaccounts
+ partsgroup formel longdescription not_discountable partnotes lastcost
+ price_factor_id price_factor);
+
+ my $ic_cvar_configs = CVar->get_configs(module => 'IC');
+ push @new_fields, map { "ic_cvar_$_->{name}" } @{ $ic_cvar_configs };
- my $best_discount = $price_source->best_discount;
+ map { $form->{"${_}_$i"} = $new_item->{$_} } @new_fields;
- if ($best_discount) {
- $::form->{"discount_$i"} = $best_discount->discount;
- $::form->{"active_discount_source_$i"} = $best_discount->source;
+ if (my $record = _make_record()) {
+ my $price_source = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
+ my $best_price = $price_source->best_price;
+
+ if ($best_price) {
+ $::form->{"sellprice_$i"} = $best_price->price;
+ $::form->{"active_price_source_$i"} = $best_price->source;
+ }
+
+ my $best_discount = $price_source->best_discount;
+
+ if ($best_discount) {
+ $::form->{"discount_$i"} = $best_discount->discount;
+ $::form->{"active_discount_source_$i"} = $best_discount->source;
+ }
}
- }
- $form->{"marge_price_factor_$i"} = $new_item->{price_factor};
+ $form->{"marge_price_factor_$i"} = $new_item->{price_factor};
- if ($form->{"part_payment_id_$i"} ne "") {
- $form->{payment_id} = $form->{"part_payment_id_$i"};
- }
+ if ($form->{"part_payment_id_$i"} ne "") {
+ $form->{payment_id} = $form->{"part_payment_id_$i"};
+ }
- my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
- $dec = length $dec;
- my $decimalplaces = ($dec > 2) ? $dec : 2;
+ my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
+ $dec = length $dec;
+ my $decimalplaces = ($dec > 2) ? $dec : 2;
- if ($sellprice) {
- $form->{"sellprice_$i"} = $sellprice;
- } else {
+ if ($sellprice) {
+ $form->{"sellprice_$i"} = $sellprice;
+ } else {
- # if there is an exchange rate adjust sellprice
- if (($form->{exchangerate} * 1) != 0) {
- $form->{"sellprice_$i"} /= $form->{exchangerate};
- $form->{"sellprice_$i"} =
- $form->round_amount($form->{"sellprice_$i"}, $decimalplaces);
+ # if there is an exchange rate adjust sellprice
+ if (($form->{exchangerate} * 1) != 0) {
+ $form->{"sellprice_$i"} /= $form->{exchangerate};
+ $form->{"sellprice_$i"} =
+ $form->round_amount($form->{"sellprice_$i"}, $decimalplaces);
+ }
}
- }
- map { $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) }
- qw(sellprice listprice weight);
+ # at this stage qty of newly added part needs to be have been parsed
+ $form->{weight} += ($form->{"weight_$i"} * $form->{"qty_$i"});
- # at this stage qty of newly added part needs to be have been parsed
- $form->{weight} += ($form->{"weight_$i"} * $form->{"qty_$i"});
+ if ($form->{"not_discountable_$i"}) {
+ $form->{"discount_$i"} = 0;
+ }
- if ($form->{"not_discountable_$i"}) {
- $form->{"discount_$i"} = 0;
- }
+ my $amount =
+ $form->{"sellprice_$i"} * (1 - $form->{"discount_$i"}) * $form->{"qty_$i"};
+ map { $form->{"${_}_base"} += $amount } (split / /, $form->{"taxaccounts_$i"});
+ map { $amount += ($form->{"${_}_base"} * $form->{"${_}_rate"}) } split / /, $form->{"taxaccounts_$i"} if !$form->{taxincluded};
- my $amount =
- $form->{"sellprice_$i"} * (1 - $form->{"discount_$i"} / 100) *
- $form->{"qty_$i"};
- map { $form->{"${_}_base"} += $amount }
- (split / /, $form->{"taxaccounts_$i"});
- map { $amount += ($form->{"${_}_base"} * $form->{"${_}_rate"}) } split / /,
- $form->{"taxaccounts_$i"}
- if !$form->{taxincluded};
+ $form->{creditremaining} -= $amount;
- $form->{creditremaining} -= $amount;
+ $form->{"runningnumber_$i"} = $i;
- $form->{"runningnumber_$i"} = $i;
+ # format amounts
+ map {
+ $form->{"${_}_$i"} =
+ $form->format_amount(\%myconfig, $form->{"${_}_$i"}, $decimalplaces)
+ } qw(sellprice listprice lastcost qty) if $form->{item} ne 'assembly';
+ $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0) if $form->{item} ne 'assembly';
- delete $form->{nextsub};
+ delete $form->{nextsub};
- # format amounts
- map {
- $form->{"${_}_$i"} =
- $form->format_amount(\%myconfig, $form->{"${_}_$i"}, $decimalplaces)
- } qw(sellprice listprice lastcost) if $form->{item} ne 'assembly';
+ }
&display_form;
$numberfld = $form->{type} =~ /^sales/ ? 'sdonumber' : 'pdonumber';
$form->{label} = $form->{formname} eq 'pick_list' ? $locale->text('Pick List') : $locale->text('Delivery Order');
}
+ if ($form->{type} =~ /letter/) {
+ undef $due;
+ undef $inv;
+ $form->{label} = $locale->text('Letter');
+ }
$form->{TEMPLATE_DRIVER_OPTIONS} = { };
- if (any { $form->{type} eq $_ } qw(sales_quotation sales_order sales_delivery_order invoice request_quotation purchase_order purchase_delivery_order)) {
+ if (any { $form->{type} eq $_ } qw(sales_quotation sales_order sales_delivery_order invoice request_quotation purchase_order purchase_delivery_order credit_note)) {
$form->{TEMPLATE_DRIVER_OPTIONS}->{variable_content_types} = {
longdescription => 'html',
partnotes => 'html',
DO->order_details(\%myconfig, \%$form);
} elsif ($order) {
OE->order_details(\%myconfig, \%$form);
+ } elsif ($form->{type} eq 'letter') {
+ # right now, no details are needed
+ # but i do not want to break the bad default (invoice)
} else {
IS->invoice_details(\%myconfig, \%$form, $locale);
}
my $form = $main::form;
- my %part_information = IC->get_basic_part_info('id' => [ grep { $_ } map { $form->{"id_${_}"} } (1..$form->{rowcount}) ],
- 'vendor_id' => $form->{vendor_id});
+ my %part_information = IC->get_basic_part_info('id' => [ grep { $_ } map { $form->{"id_${_}"} } (1..$form->{rowcount}) ]);
$form->{PART_INFORMATION} = \%part_information;
my $rows = scalar @{ $form->{item_list} };
+ $form->{"discount_$i"} = $form->parse_amount(\%myconfig, $form->{"discount_$i"}) / 100.0;
+ $form->{"discount_$i"} ||= $form->{vendor_discount};
+
if ($rows) {
- $form->{"qty_$i"} = 1 unless $form->parse_amount(\%myconfig, $form->{"qty_$i"});
+ $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
+ if( !$form->{"qty_$i"} ) {
+ $form->{"qty_$i"} = 1;
+ }
if ($rows > 1) {
- select_item(mode => 'IR');
+ select_item(mode => 'IR', pre_entered_qty => $form->{"qty_$i"});
::end_of_request();
} else {
# override sellprice if there is one entered
my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
- # ergaenzung fuer bug 736 Lieferanten-Rabatt auch in Einkaufsrechnungen vorbelegen jb
- $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{vendor_discount} * 100 );
map { $form->{item_list}[$i]{$_} =~ s/\"/"/g } qw(partnumber description unit);
map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
$form->{"sellprice_$i"} /= $exchangerate;
}
- my $amount = $form->{"sellprice_$i"} * $form->{"qty_$i"} * (1 - $form->{"discount_$i"} / 100);
+ my $amount = $form->{"sellprice_$i"} * $form->{"qty_$i"} * (1 - $form->{"discount_$i"});
$form->{creditremaining} -= $amount;
$form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"}, $decimalplaces);
$form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
+ $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
}
&display_form;
my $rows = scalar @{ $form->{item_list} };
- # Falls kein Kundenrabatt vorhanden ist, den aktuellen Rabatt nicht mit 0% überschreiben,
- # da hier der Anwender schon manual einen Wert eingetragen haben könnte (analog zu qty) Bugfix: 1412
- if ($form->{customer_discount}){
- $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{customer_discount} * 100);
- }
+ $form->{"discount_$i"} = $form->parse_amount(\%myconfig, $form->{"discount_$i"}) / 100.0;
+ $form->{"discount_$i"} ||= $form->{customer_discount};
if ($rows) {
$form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
if ($rows > 1) {
- select_item(mode => 'IS');
+ select_item(mode => 'IS', pre_entered_qty => $form->{"qty_$i"});
::end_of_request();
} else {
$form->{"listprice_$i"} /= $exchangerate;
- my $amount = $form->{"sellprice_$i"} * $form->{"qty_$i"} * (1 - $form->{"discount_$i"} / 100);
+ my $amount = $form->{"sellprice_$i"} * $form->{"qty_$i"} * (1 - $form->{"discount_$i"});
map { $form->{"${_}_base"} = 0 } split / /, $form->{taxaccounts};
map { $form->{"${_}_base"} += $amount } split / /, $form->{"taxaccounts_$i"};
map { $amount += ($form->{"${_}_base"} * $form->{"${_}_rate"}) } split / /, $form->{"taxaccounts_$i"} if !$form->{taxincluded};
map { $form->{"${_}_$i"} = $form->format_amount(\%myconfig, $form->{"${_}_$i"}, $decimalplaces) } qw(sellprice lastcost);
- $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"});
+ $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"});
+ $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
}
&display_form;
use SL::DB::Default;
use SL::Helper::CreatePDF;
use SL::Helper::Flash;
+use SL::Common;
+use Cwd;
require "bin/mozilla/reportgenerator.pl";
require "bin/mozilla/io.pl";
require "bin/mozilla/arap.pl";
);
my $pdf_file_name;
eval {
+ # catch LaTeX template not found error
+ my $tex_templates = $::instance_conf->get_templates . '/letter.tex';
+ die( t8('Please create/copy a template named letter.tex in your client template dir') ) unless (-e $tex_templates);
+
$pdf_file_name = SL::Helper::CreatePDF->create_pdf(%create_params);
+ # set some form defaults for printing webdav copy variables
+ $form->{tmpfile} = $pdf_file_name;
+ $form->{tmpdir} = 'users';
+ $form->{type} = 'letter';
+ $form->{cwd} = getcwd();
if ( $::form->{media} eq 'email') {
my $mail = Mailer->new;
my $signature = $::myconfig{signature};
$mail->{message} .= "\n-- \n$signature";
$mail->{message} =~ s/\r//g;
+ # copy_file_to_webdav was already done via io.pl -> edit_e_mail
my $err = $mail->send;
-# TODO
-# $self
-# ->js
-# ->flash($err?'error':'info',
-# $err?t8('A mail error occurred: #1', $err):
-# t8('The document have been sent to \'#1\'.', $mail->{to}))
-# ->render($self);
+ # TODO
+ # $self
+ # ->js
+ # ->flash($err?'error':'info',
+ # $err?t8('A mail error occurred: #1', $err):
+ # t8('The document have been sent to \'#1\'.', $mail->{to}))
+ # ->render($self);
return $err?0:1;
}
- if (!$::form->{printer_id}) {
+ if (!$::form->{printer_id} || $::form->{media} eq 'screen') {
+
my $file = IO::File->new($pdf_file_name, 'r') || croak("Cannot open file '$pdf_file_name'");
my $size = -s $pdf_file_name;
my $content_type = 'application/pdf';
$::locale->with_raw_io(\*STDOUT, sub { print while <$file> });
$file->close;
+ Common::copy_file_to_webdav_folder($form) if $::instance_conf->get_webdav_documents;
unlink $pdf_file_name;
return 1;
}
binmode $out;
print $out scalar(read_file($pdf_file_name));
close $out;
+ Common::copy_file_to_webdav_folder($form) if $::instance_conf->get_webdav_documents;
flash_later('info', t8('The documents have been sent to the printer \'#1\'.', $printer->printer_description));
my $callback = build_std_url('letter.pl', 'action=edit', 'id=' . $letter->{id}, 'printer_id');
$letter->check_number;
$letter->save;
+ $form->{formname} = "letter";
$letter->export_to($::form);
$::form->{id} = $letter->{id};
my $rows = scalar @{ $form->{item_list} };
- # hier ist das problem fuer bug 817 $form->{discount} wird nicht durchgeschliffen
- # ferner fallunterscheidung fuer verkauf oder einkauf s.a. bug 736 jb 04.05.2009
- # select discount as vendor_discount from vendor ||
- # select discount as customer_discount from customer
- $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"$form->{vc}_discount"} * 100);
+ $form->{"discount_$i"} = $form->parse_amount(\%myconfig, $form->{"discount_$i"}) / 100.0;
+ $form->{"discount_$i"} ||= $form->{"$form->{vc}_discount"};
$form->{"lastcost_$i"} = $form->parse_amount(\%myconfig, $form->{"lastcost_$i"});
if ($rows > 1) {
- select_item(mode => $mode);
+ select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
::end_of_request();
} else {
$form->{"sellprice_$i"} /= $exchangerate; # if there is an exchange rate adjust sellprice
}
- my $amount = $form->{"sellprice_$i"} * $form->{"qty_$i"} * (1 - $form->{"discount_$i"} / 100);
+ my $amount = $form->{"sellprice_$i"} * $form->{"qty_$i"} * (1 - $form->{"discount_$i"});
map { $form->{"${_}_base"} = 0 } split / /, $form->{taxaccounts};
map { $form->{"${_}_base"} += $amount } split / /, $form->{"taxaccounts_$i"};
map { $amount += ($form->{"${_}_base"} * $form->{"${_}_rate"}) } split / /, $form->{taxaccounts} if !$form->{taxincluded};
$form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"}, $decimalplaces);
$form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"}, $decimalplaces);
$form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
+ $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
}
display_form();
"business_types" => "ALL_BUSINESS_TYPES",);
$form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
+ $form->{CT_CUSTOM_VARIABLES} = CVar->get_configs('module' => 'CT');
+ ($form->{CT_CUSTOM_VARIABLES_FILTER_CODE},
+ $form->{CT_CUSTOM_VARIABLES_INCLUSION_CODE}) = CVar->render_search_options('variables' => $form->{CT_CUSTOM_VARIABLES},
+ 'include_prefix' => 'l_',
+ 'include_value' => 'Y');
+
# constants and subs for template
$form->{vc_keys} = sub { "$_[0]->{name}--$_[0]->{id}" };
"marge_total", "marge_percent",
"vcnumber", "ustid",
"country", "shippingpoint",
- "taxzone",
+ "taxzone", "insertdate",
"order_probability", "expected_billing_date", "expected_netamount",
);
my $report = SL::ReportGenerator->new(\%myconfig, $form);
+ my $ct_cvar_configs = CVar->get_configs('module' => 'CT');
+ my @ct_includeable_custom_variables = grep { $_->{includeable} } @{ $ct_cvar_configs };
+ my @ct_searchable_custom_variables = grep { $_->{searchable} } @{ $ct_cvar_configs };
+
+ my %column_defs_cvars = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @ct_includeable_custom_variables;
+ push @columns, map { "cvar_$_->{name}" } @ct_includeable_custom_variables;
+
my @hidden_variables = map { "l_${_}" } @columns;
push @hidden_variables, "l_subtotal", $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered ordnumber quonumber cusordnumber
transaction_description transdatefrom transdateto type vc employee_id salesman_id
reqdatefrom reqdateto projectnumber project_id periodic_invoices_active periodic_invoices_inactive
- business_id shippingpoint taxzone_id reqdate_unset_or_old
+ business_id shippingpoint taxzone_id reqdate_unset_or_old insertdatefrom insertdateto
order_probability_op order_probability_value expected_billing_date_from expected_billing_date_to);
+ push @hidden_variables, map { "cvar_$_->{name}" } @ct_searchable_custom_variables;
my @keys_for_url = grep { $form->{$_} } @hidden_variables;
push @keys_for_url, 'taxzone_id' if $form->{taxzone_id} ne ''; # taxzone_id could be 0
'periodic_invoices' => { 'text' => $locale->text('Per. Inv.'), },
'shippingpoint' => { 'text' => $locale->text('Shipping Point'), },
'taxzone' => { 'text' => $locale->text('Steuersatz'), },
+ 'insertdate' => { 'text' => $locale->text('Insert Date'), },
'order_probability' => { 'text' => $locale->text('Order probability'), },
'expected_billing_date' => { 'text' => $locale->text('Exp. bill. date'), },
'expected_netamount' => { 'text' => $locale->text('Exp. netamount'), },
+ %column_defs_cvars,
);
- foreach my $name (qw(id transdate reqdate quonumber ordnumber cusordnumber name employee salesman shipvia transaction_description shippingpoint taxzone)) {
+ foreach my $name (qw(id transdate reqdate quonumber ordnumber cusordnumber name employee salesman shipvia transaction_description shippingpoint taxzone insertdate)) {
my $sortdir = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
$column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
}
$report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
$report->set_sort_indicator($form->{sort}, $form->{sortdir});
+ CVar->add_custom_variables_to_report('module' => 'CT',
+ 'trans_id_field' => "$form->{vc}_id",
+ 'configs' => $ct_cvar_configs,
+ 'column_defs' => \%column_defs,
+ 'data' => $form->{OE});
+
my @options;
my ($department) = split m/--/, $form->{department};
push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1) if $form->{reqdatefrom};
push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{reqdateto}, 1) if $form->{reqdateto};
};
+ if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
+ push @options, $locale->text('Insert Date');
+ push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1) if $form->{insertdatefrom};
+ push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1) if $form->{insertdateto};
+ };
push @options, $locale->text('Open') if $form->{open};
push @options, $locale->text('Closed') if $form->{closed};
push @options, $locale->text('Delivery Order created') if $form->{delivered};
position: relative;
top: 2px;
vertical-align: center;
- background-image: url(../../image/icons/16x16/phone.png);
+ background-image: url(../image/icons/16x16/phone.png);
background-repeat: no-repeat;
}
2015-0x-xx - Release 3.2.2-unstable
-
Größere neue Features:
Bankerweiterung und Skontobehandlung
buchen möchte. Es wird je nach Zahlungsbetrag und Zahlungsdatum ein sinnvoller
Vorschlag gemacht.
-
Kleinere neue Features und Detailverbesserungen:
+- Briefe werden auch im WebDAV archiviert. Ferner bessere Fehlerbehandlung und
+ E-Mail-Funktion aktiviert.
+
+- Mehrfachauswahl und Mengeneingabe für Artikel:
+ Wenn in den Belegmasken die Artikeleingabe nicht eindeutig ist, erscheint
+ eine Maske zur Artikelauswahl. Hierzu kann jetzt in den Benutzereinstellungen
+ eingestellt werden, dass in dieser Maske mehrere Artikel mit Mengen ausgewählt
+ werden können.
+
+- Stammdaten->Berichte->Waren: Nach Shopartikel filtern und anzeigen können.
+
+- Auftrags-/Angebotsbericht: Erfassungszeit als letzte Sortierung verwenden,
+ damit die Einträge nach Eingabezeitpunkt sortiert sind, wenn es
+ gleichrangige Einträge in der aktuellen Sortierung gibt.
+
+- Bei Eingabe nicht eindeutiger Artikel in den Belegmasken bleibt jetzt auch die
+ Mengeneingabe über die Auswahlmaske hinweg bestehen. Damit kann man die Menge
+ auch schon vorher eingeben: Nicht eindeutiger Artikel, TAB, TAB, Menge, ENTER
+
+- In den Berichten zu Aufträgen, VK-Lieferscheinen, Warenstammdaten, Kunden-/
+ Lieferntenstammdaten kann das Erfassungsdatum angezeigt und danach gefiltert
+ werden.
+
+- Filtern/Anzeigen von benutzerdefinierten Variablen bei Kunden-/Lieferanten in
+ den Berichten Angebot/Aufträge und Verkaufsrechnungen
+
+- Filtern nach Kunden-/Lieferantentyp bei Lieferschein-Berichten.
+
+- Preisgruppe bei Stammdaten->Berichte->Kunden anzeigen lassen können.
+
+
+Bugfixes:
+
+- Bugfix #54 Fehlermeldung im Mahnlauf bei automatischer Rechnung über Mahngebühren
+- Bugfix #50 Kundentyp-Rabatt wird falsch übernommen
+
+
2015-04-10 - Release 3.2.1
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="refresh" content="0;URL=controller.pl?action=LoginScreen/user_login">
+ <link rel='shortcut icon' href='favicon.ico' type='image/x-icon'>
</head>
<body>
<a href="controller.pl?action=LoginScreen/user_login">kivitendo-Login</a>
else if (action[0] == 'reinit_widgets') kivi.reinit_widgets();
else if (action[0] == 'run') kivi.run(action[1], action.slice(2, action.length));
else if (action[0] == 'run_once_for') kivi.run_once_for(action[1], action[2], action[3]);
+ else if (action[0] == 'scroll_into_view') $(action[1])[0].scrollIntoView();
else console.log('Unknown action: ' + action[0]);
'Add User Group' => 'Neue Benutzergruppe',
'Add Vendor' => 'Lieferant erfassen',
'Add Vendor Invoice' => 'Einkaufsrechnung erfassen',
- 'Add Vendor Letter' => '',
'Add Warehouse' => 'Lager erfassen',
'Add and edit units' => 'Einheiten erfassen und bearbeiten',
'Add bank account' => 'Bankkonto erfassen',
'Edit requirement spec template' => 'Pflichtenheftvorlage bearbeiten',
'Edit requirement spec type' => 'Pflichtenhefttypen bearbeiten',
'Edit risk level' => 'Risikograd bearbeiten',
+ 'Edit sales letters' => 'Verkaufsbrief erstellen',
'Edit sales price rule' => 'Verkaufspreisregel bearbeiten',
'Edit section #1' => 'Abschnitt #1 bearbeiten',
'Edit taxzone' => 'Steuerzone bearbeiten',
'Information' => 'Information',
'Initial version.' => 'Initiale Version.',
'Insert' => 'Einfügen',
+ 'Insert Date' => 'Erfassungsdatum',
'Insert new' => 'Hinzufügen',
'Insert with new customer/vendor number' => 'Mit neuer Kunden-/Lieferantennummer anlegen',
'Insert with new database ID' => 'Neu anlegen mit neuer Datenbank-ID',
'It will simply set the taxkey to 0 (meaning "no taxes") which is the correct value for such inventory transactions.' => 'Es wird einfach die Steuerschlüssel auf 0 setzen, was "keine Steuer" bedeutet und für solche Warenbestandsbuchungen der richtige Wert ist.',
'Item deleted!' => 'Artikel gelöscht!',
'Item mode' => 'Artikelmodus',
+ 'Item multi selection with qty' => 'Artikel-Mehrfachauswahl mit Menge',
'Item not on file!' => 'Dieser Artikel ist nicht in der Datenbank!',
'Item values' => 'Artikelwerte',
'Item variables' => 'Artikelvariablen',
'Paid' => 'bezahlt',
'Paid amount' => 'Bezahlter Betrag',
'Part' => 'Ware',
+ 'Part "#1" has chargenumber or best before date set. So it cannot be transfered automaticaly.' => 'Bei Artikel "#1" ist eine Chargenummer oder ein Mindesthaltbarkeitsdatum vergeben. Deshalb kann dieser Artikel nicht automatisch ausgelagert werden.',
'Part (database ID)' => 'Artikel (Datenbank-ID)',
'Part Description' => 'Artikelbeschreibung',
'Part Description missing!' => 'Artikelbezeichnung fehlt!',
'Part Notes' => 'Bemerkungen',
'Part Number' => 'Artikelnummer',
'Part Number missing!' => 'Artikelnummer fehlt!',
- 'Part "#1" has chargenumber or best before date set. So it cannot be transfered automaticaly.' => 'Bei Artikel "#1" ist eine Chargenummer oder ein Mindesthaltbarkeitsdatum vergeben. Deshalb kann dieser Artikel nicht automatisch ausgelagert werden.',
'Part picker' => 'Artikelauswahl',
'Partial invoices' => 'Teilrechnungen',
'Partnumber' => 'Artikelnummer',
'Picture #1: #2' => 'Abbildung #1: #2',
'Pictures for parts' => 'Bilder für Waren',
'Pictures for search parts' => 'Bilder für Warensuche',
- 'Pirce rules must have at least one rule.' => 'Preisregeln brauchen mindestens eine Bedingung.',
'Please Check the bank information for each customer:' => 'Bitte überprüfen Sie die Bankinformationen der Kunden:',
'Please Check the bank information for each vendor:' => 'Bitte überprüfen Sie die Kontoinformationen der Lieferanten:',
'Please ask your administrator to create warehouses and bins.' => 'Bitten Sie Ihren Administrator, dass er Lager und Lagerplätze anlegt.',
'Please contact your administrator or a service provider.' => 'Bitte kontaktieren Sie Ihren Administrator oder einen Dienstleister.',
'Please contact your administrator.' => 'Bitte wenden Sie sich an Ihren Administrator.',
'Please correct the settings and try again or deactivate that client.' => 'Bitte korrigieren Sie die Einstellungen und versuchen Sie es erneut, oder deaktivieren Sie diesen Mandanten.',
+ 'Please create/copy a template named letter.tex in your client template dir' => 'Bitte erstellen / kopieren Sie eine Druckvorlage namens letter.tex in Ihren Mandantenvorlagen-Ordner',
'Please create a CSV import profile called "MT940" for the import type bank transactions:' => 'Bitte erstellen Sie ein CSV Import Profil mit dem Namen "MT940" für den Importtyp Bankbewegungen',
'Please define a taxkey for the following taxes and run the update again:' => 'Bitte definieren Sie einen Steuerschlüssel für die folgenden Steuern und starten Sie dann das Update erneut:',
'Please do so in the administration area.' => 'Bitte erledigen Sie dies im Administrationsbereich.',
'Price group (name)' => 'Preisgruppe (Name) ',
'Price information' => 'Preisinformation',
'Price or discount must not be zero.' => 'Preis/Rabatt darf nicht 0,00 sein',
+ 'Price rules must have at least one rule.' => 'Preisregeln brauchen mindestens eine Bedingung.',
'Price sources deactivated in this client' => 'Preisquellen die in diesem Mandanten deaktiviert sind',
'Price type explanation' => 'Preistyp Erklärung',
'Pricegroup' => 'Preisgruppe',
'Row was linked to another record' => 'Zeile wurde über einen anderen Beleg verlinkt',
'Row was source for current record' => 'Zeile war Quelle für aktuellen Beleg',
'Rule Details' => 'Regel Details',
+ 'Rule for customer must not be empty' => 'Eine Kundenbedingung darf nicht leer sein',
+ 'Rule for part must not be empty' => 'Eine Warenbedingung darf nicht leer sein',
+ 'Rule for vendor must not be empty' => 'Eine Lieferantenbedingung darf nicht leer sein',
'Run JavaScript unit tests' => 'JavaScript-Unit-Tests ausführen',
'Run at' => 'Ausgeführt um',
'Run tests' => 'Tests ausführen',
'Show parts' => 'Artikel anzeigen',
'Show requirement spec' => 'Pflichtenheft anzeigen',
'Show requirement spec template' => 'Pflichtenheftvorlage anzeigen',
+ 'Show sales letters report' => 'Verkaufsbrief anzeigen',
'Show settings' => 'Einstellungen anzeigen',
'Show the picture in the part form' => 'Bild in Warenmaske anzeigen',
'Show the pictures in the result for search parts' => 'Bilder in Suchergebnis für Stammdaten -> Berichte -> Waren anzeigen',
'The following currencies have been used, but they are not defined:' => 'Die folgenden Währungen wurden benutzt, sind aber nicht ordnungsgemäß in der Datenbank eingetragen:',
'The following drafts have been saved and can be loaded.' => 'Die folgenden Entwürfe wurden gespeichert und können geladen werden.',
'The following groups are valid for this client' => 'Die folgenden Gruppen sind für diesen Mandanten gültig',
- 'The following letter drafts have been saved and can be loaded.' => 'Die folgenden Briefentwürfe sind vorhanden und können geladen werden.',
'The following list has been generated automatically from existing users collapsing users with identical settings into a single entry.' => 'Die folgende Liste wurde automatisch aus den im System vorhandenen Benutzern zusammengestellt, wobei identische Einstellungen zu einem Eintrag zusammengefasst wurden.',
'The following old files whose settings have to be merged manually into the new configuration file "config/kivitendo.conf" still exist:' => 'Es existieren noch die folgenden alten Dateien, deren Einstellungen manuell in die neue Konfiguratsdatei "config/kivitendo.conf" migriert werden müssen:',
'The following transaction contains wrong taxes:' => 'Die folgende Buchung enthält falsche Steuern:',
'Vendor Discount' => 'Lieferantenrabatt',
'Vendor Invoice' => 'Einkaufsrechnung',
'Vendor Invoices & AP Transactions' => 'Einkaufsrechnungen & Kreditorenbuchungen',
- 'Vendor Letters' => '',
'Vendor Name' => 'Lieferantenname',
'Vendor Number' => 'Lieferantennummer',
'Vendor Order Number' => 'Bestellnummer beim Lieferanten',
'You have to grant users access to one or more clients.' => 'Benutzern muss dann Zugriff auf einzelne Mandanten gewährt werden.',
'You have to specify a department.' => 'Sie müssen eine Abteilung wählen.',
'You have to specify an execution date for each antry.' => 'Sie müssen für jeden zu buchenden Eintrag ein Ausführungsdatum angeben.',
- 'You must choose a user.' => '',
'You must chose a user.' => 'Sie müssen einen Benutzer auswählen.',
'You must enter a name for your new print templates.' => 'Sie müssen einen Namen für die neuen Druckvorlagen angeben.',
'You must select existing print templates or create a new set.' => 'Sie müssen vorhandene Druckvorlagen auswählen oder einen neuen Satz anlegen.',
'bis' => 'bis',
'building data' => 'Verarbeite Daten',
'building report' => 'Erstelle Bericht',
- 'button' => '',
'cash' => 'Ist-Versteuerung',
'chargenumber #1' => 'Chargennummer #1',
'chart_of_accounts' => 'kontenuebersicht',
\n=<br>
[Template/LaTeX]
-order=\\ <pagebreak> & \n \r " $ <bullet> % _ # ^ { } < > £ ± ² ³ ° § ® © \xad \xa0 ➔ → ← |
+order=\\ <pagebreak> & \n \r " $ <bullet> % _ # ^ { } < > £ ± ² ³ ° § ® © \xad \xa0 ➔ → ← | −
\\=\\textbackslash\s
<pagebreak>=
"=''
←=$\\leftarrow$
\xa0=~
|=\\textbar
+−={\\textemdash}
[Template/OpenDocument]
order=& < > " ' \x80 \n \r
'Increase' => '',
'Individual Items' => '',
'Information' => '',
+ 'Insert Date' => '',
'Insert with new customer/vendor number' => '',
'Insert with new database ID' => '',
'Insert with new part number' => '',
'It will simply set the taxkey to 0 (meaning "no taxes") which is the correct value for such inventory transactions.' => '',
'Item deleted!' => '',
'Item mode' => '',
+ 'Item multi selection with qty' => '',
'Item not on file!' => '',
'Item values' => '',
'Item variables' => '',
--- /dev/null
+package Regexp::IPv6;
+
+our $VERSION = '0.03';
+
+use strict;
+use warnings;
+
+require Exporter;
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw($IPv6_re);
+
+my $IPv4 = "((25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))";
+my $G = "[0-9a-fA-F]{1,4}";
+
+my @tail = ( ":",
+ "(:($G)?|$IPv4)",
+ ":($IPv4|$G(:$G)?|)",
+ "(:$IPv4|:$G(:$IPv4|(:$G){0,2})|:)",
+ "((:$G){0,2}(:$IPv4|(:$G){1,2})|:)",
+ "((:$G){0,3}(:$IPv4|(:$G){1,2})|:)",
+ "((:$G){0,4}(:$IPv4|(:$G){1,2})|:)" );
+
+our $IPv6_re = $G;
+$IPv6_re = "$G:($IPv6_re|$_)" for @tail;
+$IPv6_re = qq/:(:$G){0,5}((:$G){1,2}|:$IPv4)|$IPv6_re/;
+$IPv6_re =~ s/\(/(?:/g;
+$IPv6_re = qr/$IPv6_re/;
+
+1;
+__END__
+
+=head1 NAME
+
+Regexp::IPv6 - Regular expression for IPv6 addresses
+
+=head1 SYNOPSIS
+
+ use Regexp::IPv6 qw($IPv6_re);
+
+ $address =~ /^$IPv6_re$/ and print "IPv6 address\n";
+
+=head1 DESCRIPTION
+
+This module exports the $IPv6_re regular expression that matches any
+valid IPv6 address as described in "RFC 2373 - 2.2 Text Representation
+of Addresses" but C<::>. Any string not compliant with such RFC will
+be rejected.
+
+To match full strings use C</^$IPv6_re$/>.
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2009, 2010 by Salvador FandiE<ntilde>o
+(sfandino@yahoo.com)
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+Additionally, you are allowed to use the regexp generated by the
+module in any way you want, without any restriction. For instance, you
+are allowed to copy it verbating in your program.
+
+=cut
+
}
if ($check{a}) {
- foreach my $check (keys %check) {
- $check{$check} = 1 unless defined $check{$check};
- }
+ $check{$_} //= 1 for qw(o d l r);
}
--- /dev/null
+# @tag: ar_ap_fix_notes_as_html_for_non_invoices
+# @description: Kreditoren-/Debitorenbuchungen: Bemerkungsfeld darf kein HTML sein
+# @depends: oe_ar_ap_delivery_orders_edit_notes_as_html
+package SL::DBUpgrade2::ar_ap_fix_notes_as_html_for_non_invoices;
+
+use strict;
+use utf8;
+
+use SL::DBUtils;
+
+use parent qw(SL::DBUpgrade2::Base);
+
+sub fix_column {
+ my ($self, $table) = @_;
+
+ my $sth = $self->dbh->prepare(qq|UPDATE $table SET notes = ? WHERE id = ?|) || $self->dberror;
+
+ my $query = <<SQL;
+ SELECT id, notes
+ FROM $table
+ WHERE (notes IS NOT NULL)
+ AND (NOT COALESCE(invoice, FALSE))
+ AND (itime < (
+ SELECT itime
+ FROM schema_info
+ WHERE tag = 'oe_ar_ap_delivery_orders_edit_notes_as_html'))
+SQL
+
+ foreach my $row (selectall_hashref_query($::form, $self->dbh, $query)) {
+ next if !$row->{notes} || (($row->{notes} !~ m{^<[a-z]+>}) && ($row->{notes} !~ m{</[a-z]+>$}));
+
+ my $new_content = $row->{notes};
+ $new_content =~ s{^<p>|</p>$}{}gi;
+ $new_content =~ s{<br */>}{\n}gi;
+ $new_content =~ s{</p><p>}{\n\n}gi;
+ $new_content = $::locale->unquote_special_chars('html', $new_content);
+
+ $sth->execute($new_content, $row->{id}) if $new_content ne $row->{notes};
+ }
+
+ $sth->finish;
+}
+
+sub run {
+ my ($self) = @_;
+
+ $self->fix_column($_) for qw(ar ap);
+
+ return 1;
+}
+
+1;
}
$sth->finish;
+ my $taxzone_charts_update_query = "INSERT INTO taxzone_charts (taxzone_id, buchungsgruppen_id, income_accno_id, expense_accno_id) VALUES (?, ?, ?, ?)";
+ $sth = $self->dbh->prepare($taxzone_charts_update_query);
+
# convert Buchungsgruppen to taxzone_charts if any exist
# the default swiss COA doesn't have any, for example
- if ( scalar @{ $::form->{buchungsgruppen} } > 0 ) {
- my $taxzone_charts_update_query;
+ if ( scalar @{ $::form->{buchungsgruppen} } > 0 ) {
foreach my $taxzone ( @{$::form->{taxzones}} ) {
foreach my $buchungsgruppe ( @{$::form->{buchungsgruppen}} ) {
my $id = $taxzone->{id};
my $income_accno_id = $buchungsgruppe->{"income_accno_id_$id"};
my $expense_accno_id = $buchungsgruppe->{"expense_accno_id_$id"};
- # TODO: check if the variables have a value
- $taxzone_charts_update_query .= "INSERT INTO taxzone_charts (taxzone_id, buchungsgruppen_id, income_accno_id, expense_accno_id) VALUES ('$taxzone->{id}', '$buchungsgruppe->{id}', $income_accno_id, $expense_accno_id);\n";
+ my @values = ($taxzone->{id}, $buchungsgruppe->{id}, $income_accno_id, $expense_accno_id);
+ $sth->execute(@values) && next;
+ $taxzone_charts_update_query =~ s{\?}{shift(@values)}eg;
+ $::form->dberror($taxzone_charts_update_query);
};
};
- $self->db_query($taxzone_charts_update_query) if $taxzone_charts_update_query;
};
+ $sth->finish;
+
my $clean_buchungsgruppen_query = <<SQL;
alter table buchungsgruppen drop column income_accno_id_0;
alter table buchungsgruppen drop column income_accno_id_1;
--- /dev/null
+-- @tag: delete_invalidated_custom_variables_for_parts
+-- @description: Bei Artikeln ungültig gesetzte, benutzerdefinierte Variablen löschen
+-- @depends: release_3_2_0
+DELETE FROM custom_variables
+WHERE (config_id IN (
+ SELECT id
+ FROM custom_variable_configs
+ WHERE module = 'IC'))
+ AND EXISTS (
+ SELECT val.id
+ FROM custom_variables_validity val
+ WHERE (val.config_id = custom_variables.config_id)
+ AND (val.trans_id = custom_variables.trans_id));
-- @tag: remove_obsolete_trigger
-- @description: Entfernt veraltete Trigger check_inventory
--- @depends: release_3_2_0
--- @encoding: utf-8
-- drop triggers
DROP TRIGGER IF EXISTS check_inventory ON oe;
-- @tag: taxzone_id_in_oe_delivery_orders
-- @description: Werte für Inland in Spalte taxzone_id in Tabellen oe und delivery_orders in Foreign Key zu tax_zones konvertieren; NULL-Werte in ap/ar verhindern; Spalten NOT NULL setzen
--- @depends: change_taxzone_id_0
+-- @depends: change_taxzone_id_0 remove_obsolete_trigger
UPDATE oe SET taxzone_id = (SELECT id FROM tax_zones WHERE description = 'Inland') WHERE (taxzone_id = 0) OR (taxzone_id IS NULL);
UPDATE delivery_orders SET taxzone_id = (SELECT id FROM tax_zones WHERE description = 'Inland') WHERE (taxzone_id = 0) OR (taxzone_id IS NULL);
-- @tag: warehouse_add_delivery_order_items_stock_id
-- @description: Constraints für inventory auf delivery_order (dois und do). Ferner sinnvolle Umbenennung zumindestens von einer Spalte (orderitems -> dois). <br><b>Falls die Constraint nicht gesetzt werden kann, kontaktieren Sie einen Dienstleister und / oder löschen sie die Verknüpfung der Warenbewegung mit Lieferschein auf eigene Verantwortung mit: "UPDATE inventory SET oe_id = NULL WHERE oe_id NOT IN (select id from delivery_orders);"<br>Hintergrund: Eingelagerte Lieferscheine können / sollen nicht gelöscht werden, allerdings weist dieser Datenbestand genau diesen Fall auf.</b>
--- @depends: release_3_1_0
+-- @depends: release_3_1_0 remove_obsolete_trigger
ALTER TABLE inventory RENAME orderitems_id TO delivery_order_items_stock_id;
ALTER TABLE inventory ADD CONSTRAINT delivery_order_items_stock_id_fkey FOREIGN KEY (delivery_order_items_stock_id) REFERENCES delivery_order_items_stock (id);
ALTER TABLE inventory ADD CONSTRAINT oe_id_fkey FOREIGN KEY (oe_id) REFERENCES delivery_orders (id);
sekf
sucess
varsion
+pirce
);
$testcount = scalar(@Support::Files::testitems);
</td>
</tr>
+ <tr>
+ <th align="right">[% 'Item multi selection with qty' | $T8 %]</th>
+ <td>
+ [% L.yes_no_tag('item_multiselect', myconfig_item_multiselect) %]
+ </td>
+ </tr>
+
</table>
</div>
[% L.date_tag('transdateto') %]
</td>
</tr>
+
+[%- IF CT_CUSTOM_VARIABLES.size %]
+ <tr>
+ <td></td>
+ <td colspan=4 align=left><b>[% 'Custom variables for module' | $T8 %]: [%'Customers and vendors' | $T8 %]</td>
+ </tr>
+ [% CT_CUSTOM_VARIABLES_FILTER_CODE %]
+[%- END %]
+
<input type=hidden name=sort value=transdate>
</table>
</td>
<td align=right><input name="l_ustid" id="l_ustid" class=checkbox type=checkbox value=Y></td>
<td nowrap>[% 'USt-IdNr.' | $T8 %]</td>
</tr>
+
+ <table>
+ <tr><td align="right">
+ [% CT_CUSTOM_VARIABLES_INCLUSION_CODE %]
+ </td></tr>
+ </table>
+
</table>
</td>
</tr>
</tr>
[% END %]
+ <tr>
+ <th align="right">[% 'Insert Date' | $T8 %] [% 'From' | $T8 %]</th>
+ <td>
+ [% L.date_tag('insertdatefrom') %]
+ </td>
+ <th align="right">[% 'Bis' | $T8 %]</th>
+ <td>
+ [% L.date_tag('insertdateto') %]
+ </td>
+ </tr>
+
[% CUSTOM_VARIABLES_FILTER_CODE %]
<tr>
<tr>
<th align="right" nowrap>[% 'Include in Report' | $T8 %]</th>
- <td>
+ <td colspan="5">
<table border="0">
<tr>
<td>
<input name="l_payment" id="l_payment" type="checkbox" class="checkbox" value="Y">
<label for="l_payment">[% 'Payment Terms' | $T8 %]</label>
</td>
+ <td>
+ <input name="l_insertdate" id="l_insertdate" class="checkbox" type="checkbox" value="Y">
+ <label for="l_insertdate">[% 'Insert Date' | $T8 %]</label>
+ </td>
[% IF IS_CUSTOMER %]
<td>
<input name="l_salesman" id="l_salesman" type="checkbox" class="checkbox" value="Y">
<label for="l_salesman">[% 'Salesman' | $T8 %]</label>
</td>
+ </tr>
+ <tr>
+ <td>
+ <input name="l_pricegroup" id="l_pricegroup" type="checkbox" class="checkbox" value="Y">
+ <label for="l_pricegroup">[% 'Pricegroup' | $T8 %]</label>
+ </td>
[% END %]
</tr>
</td>
</tr>
+ [%- IF is_customer %]
+ <tr>
+ <th align="right" nowrap>[% 'Insert Date' | $T8 %]</th>
+ <td>[% insertdate %]</td>
+ </tr>
+ [%- END %]
+
<tr>
<th width="70%" align="right" nowrap>[% 'Project Number' | $T8 %]</th>
<td>
[%- SET is_customer = '0' %]
[%- END %]
+ [%- SET vctypelabel = vc == 'customer' ? LxERP.t8('Customer type') : LxERP.t8('Vendor type') %]
+
<script type="text/javascript">
$(function(){ document.Form.donumber.focus(); });
</script>
<td colspan="3"><input name="serialnumber" class="fixed_width"></td>
</tr>
+ [%- IF ALL_BUSINESS_TYPES.size %]
+ <tr>
+ <th align="right" nowrap>[% vctypelabel %]</th>
+ <td colspan="3">
+ [% L.select_tag('business_id', ALL_BUSINESS_TYPES, title_key = 'description', with_empty = 1, style='width:250px') %]
+ </td>
+ </tr>
+ [%- END %]
+
<tr>
<th align="right">[% 'Delivery Order Date' | $T8 %] [% 'From' | $T8 %]</th>
<td>
</td>
</tr>
+ [%- IF is_customer %]
+ <tr>
+ <th align="right">[% 'Insert Date' | $T8 %] [% 'From' | $T8 %]</th>
+ <td>
+ [% L.date_tag('insertdatefrom') %]
+ </td>
+ <th align="right">[% 'Bis' | $T8 %]</th>
+ <td>
+ [% L.date_tag('insertdateto') %]
+ </td>
+ </tr>
+ [%- END %]
+
<tr>
<th align="right">[% 'Include in Report' | $T8 %]</th>
<td colspan="5">
<input name="l_reqdate" id="l_reqdate" class="checkbox" type="checkbox" value="Y" checked>
<label for="l_reqdate">[% 'Reqdate' | $T8 %]</label>
</td>
+ [% IF is_customer %]
+ <td>
+ <input name="l_insertdate" id="l_insertdate" class="checkbox" type="checkbox" value="Y">
+ <label for="l_insertdate">[% 'Insert Date' | $T8 %]</label>
+ </td>
+ [%- END %]
</tr>
<tr>
<td><input name="microfiche" size="20"></td>
</tr>
+ <tr>
+ <th align="right" nowrap>[% 'Shopartikel' | $T8 %]</th>
+ <td>[% L.yes_no_tag('shop', shop, default='', with_empty=1, empty_title='---') %]</td>
+ </tr>
+
+ <tr>
+ <th align="right">[% 'Insert Date' | $T8 %]</th>
+ <td>
+ [% 'From' | $T8 %][% L.date_tag('insertdatefrom') %]
+ [% 'Bis' | $T8 %] [% L.date_tag('insertdateto') %]
+ </td>
+ </tr>
+
[% CUSTOM_VARIABLES_FILTER_CODE %]
[%- IF is_assembly %]
<tr>
<td>[%- L.checkbox_tag('l_notes', label=LxERP.t8('Notes'), value='Y') %]</td>
<td>[%- L.checkbox_tag('l_name', label=LxERP.t8('Name in Selected Records'), value='Y') %]</td>
+ <td>[%- L.checkbox_tag('l_shop', label=LxERP.t8('Shopartikel'), value='Y') %]</td>
+ <td>[%- L.checkbox_tag('l_insertdate', label=LxERP.t8('Insert Date'), value='Y') %]</td>
</tr>
[% CUSTOM_VARIABLES_INCLUSION_CODE %]
<table width="100%">
<tr class="listheading">
- <th> </th>
+ [%- IF myconfig_item_multiselect %]
+ <th>[% LxERP.t8('Qty') %]</th>
+ [%- ELSE %]
+ <th> </th>
+ [%- END %]
<th>[% LxERP.t8('Number') %]</th>
<th>[% LxERP.t8('Part Description') %]</th>
<th>[% LxERP.t8('Other Matches') %]</th>
[%- FOREACH item = ITEM_LIST %]
<tr class="listrow[% loop.count % 2 %]">
- <td><input name="select_item_id" class="radio" type="radio" value="[% HTML.escape(item.id) %]"[% IF loop.first %] checked[% END %]></td>
+ [%- IF myconfig_item_multiselect %]
+ <td>[% L.input_tag('select_qty_' _ HTML.escape(item.id), '', size => 5) %]</td>
+ [%- ELSE %]
+ <td><input name="select_item_id" class="radio" type="radio" value="[% HTML.escape(item.id) %]"[% IF loop.first %] checked[% END %]></td>
+ [%- END %]
<td>[% HTML.escape(item.partnumber) %]</td>
<td>[% HTML.escape(item.description) %]</td>
<td>[% HTML.escape(item.matches).join('<br>') %]</td>
[% L.submit_tag('action', LxERP.t8('Continue')) %]
</form>
+
+[%- IF myconfig_item_multiselect %]
+ <script type='text/javascript'>
+ var first_click = 1;;
+ [%- FOREACH item = ITEM_LIST %]
+ [% SET THIS_ID = 'select_qty_' _ HTML.escape(item.id) %]
+ $('#[% THIS_ID %]').click(function(){
+ var qty = '1';
+ if (first_click) {
+ qty = '[% LxERP.format_amount(PRE_ENTERED_QTY, 5) %]';
+ }
+ first_click = 0;
+ if ($('#[% THIS_ID %]').attr('value') == '') {
+ $('#[% THIS_ID %]').attr('value', qty); $('#[% THIS_ID %]').select();
+ }
+ });
+ [%- END %]
+ </script>
+[%- END %]
<span class="plus[% IF is_credit_remaining_negativ %]0[% ELSE %]1[% END %]">[% LxERP.format_amount(creditremaining,0 ,'0') %]</span>
</td>
</tr>
+[%- IF business %]
+ <tr>
+ <th align="right">[% 'Vendor type' | $T8 %]</th>
+ <td>[% business %]; [% 'Trade Discount' | $T8 %] [% LxERP.format_amount(tradediscount * 100) %] %</td>
+ </tr>
+[%- END %]
<tr>
<th align="right" nowrap>[% 'Record in' | $T8 %]</th>
<td colspan="3"><select name="AP" style="width:250px;">[% selectAP %]</select></td>
<input type="hidden" name="action" value="dispatcher">
<input class="submit" type="submit" name="action_update" id="update_button" value="[% 'Update' | $T8 %]">
-<input class="submit" type="submit" name="action_print" value="[% 'Print' | $T8 %]">
-<input class="submit" type="submit" name="action_e_mail" value="[% 'E-mail' | $T8 %]">
+
+[%- IF letter.letternumber %]
+ <input class="submit" type="submit" name="action_print" value="[% 'Print' | $T8 %]">
+ <input class="submit" type="submit" name="action_e_mail" value="[% 'E-mail' | $T8 %]">
+[% END %]
+
<input class="submit" type="submit" name="action_save" value="[% 'Save' | $T8 %]">
<input class="submit" type="submit" name="action_save_letter_draft" value="[% 'Save Draft' | $T8 %]">
[% L.date_tag('reqdate', reqdate, id='reqdate') %]
</td>
</tr>
+ [%- IF is_sales_ord %]
+ <tr>
+ <th align="right" nowrap>[% 'Insert Date' | $T8 %]</th>
+ <td>[% insertdate %]</td>
+ </tr>
+ [%- END %]
<tr>
<th width="70%" align="right" nowrap>[% 'Project Number' | $T8 %]</th>
<td>
[% L.date_tag('reqdateto') %]
</td>
</tr>
+
+ [%- IF type == 'sales_order' %]
+ <tr>
+ <th align="right">[% 'Insert Date' | $T8 %] [% 'From' | $T8 %]</th>
+ <td>
+ [% L.date_tag('insertdatefrom') %]
+ </td>
+ <th align="right">[% 'Bis' | $T8 %]</th>
+ <td>
+ [% L.date_tag('insertdateto') %]
+ </td>
+ </tr>
+ [%- END %]
+
[%- IF type == 'sales_quotation' %]
<tr>
<th align="right">[% 'Expected billing date' | $T8 %] [% 'From' | $T8 %]</th>
</td>
</tr>
[%- END %]
+
+[%- IF CT_CUSTOM_VARIABLES.size %]
+ <tr>
+ <td></td>
+ <td colspan=4 align=left><b>[% 'Custom variables for module' | $T8 %]: [%'Customers and vendors' | $T8 %]</td>
+ </tr>
+ [% CT_CUSTOM_VARIABLES_FILTER_CODE %]
+[%- END %]
+
<tr>
<th align="right">[% 'Include in Report' | $T8 %]</th>
<td colspan="5">
<input name="l_reqdate" id="l_reqdate" class="checkbox" type="checkbox" value="Y" checked>
<label for="l_reqdate">[% IF is_order %][% 'Required by' | $T8 %][% ELSE %][% 'Valid until' | $T8 %][% END %]</label>
</td>
+ [%- IF type == 'sales_order' %]
+ <td>
+ <input name="l_insertdate" id="l_insertdate" class="checkbox" type="checkbox" value="Y">
+ <label for="l_insertdate">[% 'Insert Date' | $T8 %]</label>
+ </td>
+ [%- END %]
</tr>
<tr>
<td>
<label for="l_ustid">[% 'USt-IdNr.' | $T8 %]</label>
</td>
</tr>
+
+ [% CT_CUSTOM_VARIABLES_INCLUSION_CODE %]
+
[%- IF type == 'sales_order' %]
<tr><td colspan="3"><hr></td></tr>
[%- END %]