From: Niclas Zimmermann Date: Mon, 3 Jun 2013 11:49:40 +0000 (+0200) Subject: Merge branch 'gewicht' X-Git-Tag: release-3.1.0beta1~388 X-Git-Url: http://wagnertech.de/gitweb/gitweb.cgi/mfinanz.git/commitdiff_plain/c6b2257945060625bcb86fa7e2efd27c737480ff?hp=68610badb49a0bcb03acea594a236e1812cdb869 Merge branch 'gewicht' Conflicts: SL/Controller/ClientConfig.pm SL/DB/MetaSetup/Default.pm --- diff --git a/SL/AM.pm b/SL/AM.pm index c85c47911..4934d866c 100644 --- a/SL/AM.pm +++ b/SL/AM.pm @@ -40,6 +40,7 @@ package AM; use Carp; use Data::Dumper; use Encode; +use List::MoreUtils qw(any); use SL::DBUtils; use strict; @@ -207,14 +208,15 @@ sub save_account { # connect to database, turn off AutoCommit my $dbh = $form->dbconnect_noauto($myconfig); - # sanity check, can't have AR with AR_... - if ($form->{AR} || $form->{AP} || $form->{IC}) { - map { delete $form->{$_} } - qw(AR_amount AR_tax AR_paid AP_amount AP_tax AP_paid IC_sale IC_cogs IC_taxpart IC_income IC_expense IC_taxservice); + for (qw(AR_include_in_dropdown AP_include_in_dropdown summary_account)) { + $form->{$form->{$_}} = $form->{$_} if $form->{$_}; } - for (qw(AR_include_in_dropdown AP_include_in_dropdown)) { - $form->{$form->{$_}} = $form->{$_} if $form->{$_}; + # sanity check, can't have AR with AR_... + if ($form->{AR} || $form->{AP} || $form->{IC}) { + if (any { $form->{$_} } qw(AR_amount AR_tax AR_paid AP_amount AP_tax AP_paid IC_sale IC_cogs IC_taxpart IC_income IC_expense IC_taxservice)) { + $form->error($::locale->text('It is not allowed that a summary account occurs in a drop-down menu!')); + } } $form->{link} = ""; @@ -1060,10 +1062,6 @@ sub save_defaults { my %accnos; map { ($accnos{$_}) = split(m/--/, $form->{$_}) } qw(inventory_accno income_accno expense_accno fxgain_accno fxloss_accno ar_paid_accno); - $form->{curr} =~ s/ //g; - my @currencies = grep { $_ ne '' } split m/:/, $form->{curr}; - my $currency = join ':', @currencies; - # these defaults are database wide my $query = @@ -1087,7 +1085,6 @@ sub save_defaults { assemblynumber = ?, sdonumber = ?, pdonumber = ?, - curr = ?, businessnumber = ?, weightunit = ?, language_id = ?|; @@ -1100,11 +1097,24 @@ sub save_defaults { $form->{articlenumber}, $form->{servicenumber}, $form->{assemblynumber}, $form->{sdonumber}, $form->{pdonumber}, - $currency, $form->{businessnumber}, $form->{weightunit}, conv_i($form->{language_id})); do_query($form, $dbh, $query, @values); + $main::lxdebug->message(0, "es gibt rowcount: " . $form->{rowcount}); + + for my $i (1..$form->{rowcount}) { + if ($form->{"curr_$i"} ne $form->{"old_curr_$i"}) { + $query = qq|UPDATE currencies SET name = ? WHERE name = ?|; + do_query($form, $dbh, $query, $form->{"curr_$i"}, $form->{"old_curr_$i"}); + } + } + + if (length($form->{new_curr}) > 0) { + $query = qq|INSERT INTO currencies (name) VALUES (?)|; + do_query($form, $dbh, $query, $form->{new_curr}); + } + $dbh->commit(); $main::lxdebug->leave_sub(); @@ -1118,7 +1128,7 @@ sub save_preferences { my $dbh = $form->get_standard_dbh($myconfig); - my ($currency, $businessnumber) = selectrow_query($form, $dbh, qq|SELECT curr, businessnumber FROM defaults|); + my ($businessnumber) = selectrow_query($form, $dbh, qq|SELECT businessnumber FROM defaults|); # update name my $query = qq|UPDATE employee SET name = ? WHERE login = ?|; @@ -1126,10 +1136,6 @@ sub save_preferences { my $rc = $dbh->commit(); - # save first currency in myconfig - $currency =~ s/:.*//; - $form->{currency} = $currency; - $form->{businessnumber} = $businessnumber; $myconfig = User->new(login => $form->{login}); @@ -1286,6 +1292,15 @@ sub defaultaccounts { } $sth->finish; + + #Get currencies: + $query = qq|SELECT name AS curr FROM currencies ORDER BY id|; + $form->{CURRENCIES} = selectall_hashref_query($form, $dbh, $query); + + #Which of them is the default currency? + $query = qq|SELECT name AS defaultcurrency FROM currencies WHERE id = (SELECT currency_id FROM defaults LIMIT 1);|; + ($form->{defaultcurrency}) = selectrow_query($form, $dbh, $query); + $dbh->disconnect; $main::lxdebug->leave_sub(); @@ -2101,9 +2116,10 @@ sub get_warehouse { map { $form->{$_} = $ref->{$_} } keys %{ $ref }; $query = qq|SELECT b.*, EXISTS - (SELECT i.warehouse_id - FROM inventory i + (SELECT i.warehouse_id, p.warehouse_id + FROM inventory i, parts p WHERE i.bin_id = b.id + OR p.bin_id = b.id LIMIT 1) AS in_use FROM bin b diff --git a/SL/AP.pm b/SL/AP.pm index 53c1d2f5d..bc5a36202 100644 --- a/SL/AP.pm +++ b/SL/AP.pm @@ -55,7 +55,6 @@ sub post_transaction { my $exchangerate = 0; $form->{defaultcurrency} = $form->get_default_currency($myconfig); - delete $form->{currency} unless $form->{defaultcurrency}; ($null, $form->{department_id}) = split(/--/, $form->{department}); @@ -185,7 +184,7 @@ sub post_transaction { $query = qq|UPDATE ap SET invnumber = ?, transdate = ?, ordnumber = ?, vendor_id = ?, taxincluded = ?, amount = ?, duedate = ?, paid = ?, netamount = ?, - curr = ?, notes = ?, department_id = ?, storno = ?, storno_id = ?, + currency_id = (SELECT id FROM currencies WHERE name = ?), notes = ?, department_id = ?, storno = ?, storno_id = ?, globalproject_id = ?, direct_debit = ? WHERE id = ?|; @values = ($form->{invnumber}, conv_date($form->{transdate}), @@ -666,7 +665,6 @@ sub post_payment { $form->{exchangerate} = $form->format_amount($myconfig, $form->{exchangerate}); $form->{defaultcurrency} = $form->get_default_currency($myconfig); - delete $form->{currency} unless $form->{defaultcurrency}; # Get the AP accno. $query = diff --git a/SL/AR.pm b/SL/AR.pm index 5ec2b1850..b6a7376e1 100644 --- a/SL/AR.pm +++ b/SL/AR.pm @@ -56,7 +56,6 @@ sub post_transaction { my $dbh = $provided_dbh ? $provided_dbh : $form->dbconnect_noauto($myconfig); $form->{defaultcurrency} = $form->get_default_currency($myconfig); - delete $form->{currency} unless $form->{defaultcurrency}; # set exchangerate $form->{exchangerate} = ($form->{currency} eq $form->{defaultcurrency}) ? 1 : @@ -135,8 +134,8 @@ sub post_transaction { } else { $query = qq|SELECT nextval('glid')|; ($form->{id}) = selectrow_query($form, $dbh, $query); - $query = qq|INSERT INTO ar (id, invnumber, employee_id) VALUES (?, 'dummy', ?)|; - do_query($form, $dbh, $query, $form->{id}, $form->{employee_id}); + $query = qq|INSERT INTO ar (id, invnumber, employee_id, currency_id) VALUES (?, 'dummy', ?, (SELECT id FROM currencies WHERE name=?))|; + do_query($form, $dbh, $query, $form->{id}, $form->{employee_id}, $form->{currency}); $form->{invnumber} = $form->update_defaults($myconfig, "invnumber", $dbh) unless $form->{invnumber}; } } @@ -156,12 +155,12 @@ sub post_transaction { qq|UPDATE ar set invnumber = ?, ordnumber = ?, transdate = ?, customer_id = ?, taxincluded = ?, amount = ?, duedate = ?, paid = ?, - netamount = ?, curr = ?, notes = ?, department_id = ?, + netamount = ?, notes = ?, department_id = ?, employee_id = ?, storno = ?, storno_id = ?, globalproject_id = ?, direct_debit = ? WHERE id = ?|; my @values = ($form->{invnumber}, $form->{ordnumber}, conv_date($form->{transdate}), conv_i($form->{customer_id}), $form->{taxincluded} ? 't' : 'f', $form->{amount}, - conv_date($form->{duedate}), $form->{paid}, $form->{netamount}, $form->{currency}, $form->{notes}, conv_i($form->{department_id}), + conv_date($form->{duedate}), $form->{paid}, $form->{netamount}, $form->{notes}, conv_i($form->{department_id}), conv_i($form->{employee_id}), $form->{storno} ? 't' : 'f', $form->{storno_id}, conv_i($form->{globalproject_id}), $form->{direct_debit} ? 't' : 'f', conv_i($form->{id})); do_query($form, $dbh, $query, @values); @@ -427,7 +426,6 @@ sub post_payment { $form->{exchangerate} = $form->format_amount($myconfig, $form->{exchangerate}); $form->{defaultcurrency} = $form->get_default_currency($myconfig); - delete $form->{currency} unless $form->{defaultcurrency}; # Get the AR accno (which is normally done by Form::create_links()). $query = diff --git a/SL/CP.pm b/SL/CP.pm index 18673c215..29ac35ab0 100644 --- a/SL/CP.pm +++ b/SL/CP.pm @@ -93,10 +93,9 @@ sub paymentaccounts { } $sth->finish; - # get currencies and closedto - $query = qq|SELECT curr, closedto FROM defaults|; - ($form->{currencies}, $form->{closedto}) = - selectrow_query($form, $dbh, $query); + # get closedto + $query = qq|SELECT closedto FROM defaults|; + ($form->{closedto}) = selectrow_query($form, $dbh, $query); $dbh->disconnect; @@ -150,9 +149,10 @@ sub get_openinvoices { my $arap = $form->{arap} eq "ar" ? "ar" : "ap"; my $query = - qq|SELECT a.id, a.invnumber, a.transdate, a.amount, a.paid, a.curr | . + qq|SELECT a.id, a.invnumber, a.transdate, a.amount, a.paid, cu.name AS curr | . qq|FROM $arap a | . - qq|WHERE (a.${vc}_id = ?) AND (COALESCE(a.curr, '') = ?) AND NOT (a.amount = a.paid)| . + qq|LEFT JOIN currencies cu ON (cu.id=a.currency_id)| . + qq|WHERE (a.${vc}_id = ?) AND cu.name = ? AND NOT (a.amount = a.paid)| . qq|ORDER BY a.id|; my $sth = prepare_execute_query($form, $dbh, $query, conv_i($form->{"${vc}_id"}), @@ -174,7 +174,7 @@ sub get_openinvoices { SELECT COUNT(*) FROM $arap WHERE (${vc}_id = ?) - AND (COALESCE(curr, '') <> ?) + AND ((SELECT cu.name FROM currencies cu WHERE cu.id=${arap}.currency_id) <> ?) AND (amount <> paid) SQL ($form->{openinvoices_other_currencies}) = selectfirst_array_query($form, $dbh, $query, conv_i($form->{"${vc}_id"}), "$form->{currency}"); @@ -250,7 +250,7 @@ sub process_payment { qq|SELECT $buysell | . qq|FROM exchangerate e | . qq|JOIN ${arap} a ON (a.transdate = e.transdate) | . - qq|WHERE (e.curr = ?) AND (a.id = ?)|; + qq|WHERE (e.currency_id = (SELECT id FROM currencies WHERE name = ?)) AND (a.id = ?)|; my ($exchangerate) = selectrow_query($form, $dbh, $query, $form->{currency}, $form->{"id_$i"}); diff --git a/SL/CT.pm b/SL/CT.pm index bf4aad448..66b3e25b7 100644 --- a/SL/CT.pm +++ b/SL/CT.pm @@ -68,11 +68,12 @@ sub get_tuple { my $ref = $sth->fetchrow_hashref("NAME_lc"); map { $form->{$_} = $ref->{$_} } keys %$ref; + $sth->finish; - # remove any trailing whitespace - $form->{curr} =~ s/\s*$//; + #get name of currency instead of id: + $query = qq|SELECT name AS curr FROM currencies WHERE id=?|; + ($form->{curr}) = selectrow_query($form, $dbh, $query, conv_i($form->{currency_id})); - $sth->finish; if ( $form->{salesman_id} ) { my $query = qq|SELECT ct.name AS salesman | . @@ -275,7 +276,7 @@ sub save_customer { $query = qq|SELECT nextval('id')|; ($form->{id}) = selectrow_query($form, $dbh, $query); - $query = qq|INSERT INTO customer (id, name) VALUES (?, '')|; + $query = qq|INSERT INTO customer (id, name, currency_id) VALUES (?, '', (SELECT currency_id FROM defaults))|; do_query($form, $dbh, $query, $form->{id}); } @@ -319,7 +320,7 @@ sub save_customer { qq|user_password = ?, | . qq|c_vendor_id = ?, | . qq|klass = ?, | . - qq|curr = ?, | . + qq|currency_id = (SELECT id FROM currencies WHERE name = ?), | . qq|taxincluded_checked = ? | . qq|WHERE id = ?|; my @values = ( @@ -362,7 +363,7 @@ sub save_customer { $form->{user_password}, $form->{c_vendor_id}, conv_i($form->{klass}), - substr($form->{currency}, 0, 3), + $form->{currency}, $form->{taxincluded_checked} ne '' ? $form->{taxincluded_checked} : undef, $form->{id} ); @@ -422,7 +423,7 @@ sub save_vendor { $query = qq|SELECT nextval('id')|; ($form->{id}) = selectrow_query($form, $dbh, $query); - $query = qq|INSERT INTO vendor (id, name) VALUES (?, '')|; + $query = qq|INSERT INTO vendor (id, name, currency_id) VALUES (?, '', (SELECT currency_id FROM defaults))|; do_query($form, $dbh, $query, $form->{id}); my $vendornumber = SL::TransNumber->new(type => 'vendor', @@ -471,7 +472,7 @@ sub save_vendor { qq| username = ?, | . qq| user_password = ?, | . qq| v_customer_id = ?, | . - qq| curr = ? | . + qq| currency_id = (SELECT id FROM currencies WHERE name = ?) | . qq|WHERE id = ?|; my @values = ( $form->{vendornumber}, @@ -511,7 +512,7 @@ sub save_vendor { $form->{username}, $form->{user_password}, $form->{v_customer_id}, - substr($form->{currency}, 0, 3), + $form->{currency}, $form->{id} ); do_query($form, $dbh, $query, @values); @@ -615,24 +616,24 @@ sub search { my @values; my %allowed_sort_columns = ( - "id" => "id", - "customernumber" => "customernumber", - "vendornumber" => "vendornumber", - "name" => "ct.name", - "contact" => "contact", - "phone" => "phone", - "fax" => "fax", - "email" => "email", - "street" => "street", - "taxnumber" => "taxnumber", - "business" => "business", - "invnumber" => "invnumber", - "ordnumber" => "ordnumber", - "quonumber" => "quonumber", - "zipcode" => "zipcode", - "city" => "city", - "country" => "country", - "salesman" => "e.name" + "id" => "ct.id", + "customernumber" => "ct.customernumber", + "vendornumber" => "ct.vendornumber", + "name" => "ct.name", + "contact" => "ct.contact", + "phone" => "ct.phone", + "fax" => "ct.fax", + "email" => "ct.email", + "street" => "ct.street", + "taxnumber" => "ct.taxnumber", + "business" => "ct.business", + "invnumber" => "ct.invnumber", + "ordnumber" => "ct.ordnumber", + "quonumber" => "ct.quonumber", + "zipcode" => "ct.zipcode", + "city" => "ct.city", + "country" => "ct.country", + "salesman" => "e.name" ); $form->{sort} ||= "name"; @@ -674,10 +675,10 @@ sub search { $where .= " AND ((lower(ct.city) LIKE lower(?)) OR (ct.id IN ( - SELECT trans_id - FROM shipto - WHERE (module = 'CT') - AND (lower(shiptocity) LIKE lower(?)) + SELECT sc.trans_id + FROM shipto sc + WHERE (sc.module = 'CT') + AND (lower(sc.shiptocity) LIKE lower(?)) )) )"; push @values, ('%' . $form->{addr_city} . '%') x 2; @@ -687,10 +688,10 @@ sub search { $where .= " AND ((lower(ct.country) LIKE lower(?)) OR (ct.id IN ( - SELECT trans_id - FROM shipto - WHERE (module = 'CT') - AND (lower(shiptocountry) LIKE lower(?)) + SELECT so.trans_id + FROM shipto so + WHERE (so.module = 'CT') + AND (lower(so.shiptocountry) LIKE lower(?)) )) )"; push @values, ('%' . $form->{addr_country} . '%') x 2; @@ -716,20 +717,20 @@ sub search { } if ($form->{obsolete} eq "Y") { - $where .= qq| AND obsolete|; + $where .= qq| AND ct.obsolete|; } elsif ($form->{obsolete} eq "N") { - $where .= qq| AND NOT obsolete|; + $where .= qq| AND NOT ct.obsolete|; } if ($form->{business_id}) { - $where .= qq| AND (business_id = ?)|; + $where .= qq| AND (ct.business_id = ?)|; push(@values, conv_i($form->{business_id})); } # 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)) { - $where .= qq| AND ct.salesman_id = (select id from employee where login= ?)|; + $where .= qq| AND ct.salesman_id = (select em.id from employee em where em.login = ?)|; push(@values, $form->{login}); } @@ -743,12 +744,12 @@ sub search { } if ($form->{addr_street}) { - $where .= qq| AND (street ILIKE ?)|; + $where .= qq| AND (ct.street ILIKE ?)|; push @values, '%' . $form->{addr_street} . '%'; } if ($form->{addr_zipcode}) { - $where .= qq| AND (zipcode ILIKE ?)|; + $where .= qq| AND (ct.zipcode ILIKE ?)|; push @values, $form->{addr_zipcode} . '%'; } diff --git a/SL/CVar.pm b/SL/CVar.pm index 0a91c59f6..02456cb0b 100644 --- a/SL/CVar.pm +++ b/SL/CVar.pm @@ -2,12 +2,13 @@ package CVar; use strict; +use List::MoreUtils qw(any); use List::Util qw(first); use Scalar::Util qw(blessed); use Data::Dumper; use SL::DBUtils; -use SL::MoreCommon qw(any listify); +use SL::MoreCommon qw(listify); sub get_configs { $main::lxdebug->enter_sub(); diff --git a/SL/Common.pm b/SL/Common.pm index ff6c7218a..dc1cc04ec 100644 --- a/SL/Common.pm +++ b/SL/Common.pm @@ -85,7 +85,7 @@ sub retrieve_parts { } if ($form->{no_services}) { - $filter .= qq| AND (inventory_accno_id is not NULL or assembly=TRUE)|; # @mb hier nochmal optimieren ... nach kurzer ruecksprache alles i.o. + $filter .= qq| AND (inventory_accno_id is not NULL or assembly=TRUE)|; } substr($filter, 1, 3) = "WHERE" if ($filter); @@ -94,7 +94,8 @@ sub retrieve_parts { $order_dir = $order_dir ? "ASC" : "DESC"; my $query = - qq|SELECT id, partnumber, description, ean | . + qq|SELECT id, partnumber, description, ean, | . + qq| warehouse_id, bin_id | . qq|FROM parts $filter | . qq|ORDER BY $order_by $order_dir|; my $sth = $dbh->prepare($query); @@ -501,6 +502,8 @@ sub save_email_status { } elsif ($form->{script} eq 'ir.pl') { $table = 'ap'; + } elsif ($form->{script} eq 'do.pl') { + $table = 'delivery_orders'; } return $main::lxdebug->leave_sub() if (!$form->{id} || !$table || !$form->{formname}); diff --git a/SL/Controller/ClientConfig.pm b/SL/Controller/ClientConfig.pm index 74bc8b597..148054e4e 100644 --- a/SL/Controller/ClientConfig.pm +++ b/SL/Controller/ClientConfig.pm @@ -43,6 +43,19 @@ sub action_edit { map { $self->{$_} = SL::DB::Default->get->$_ } qw(sales_order_show_delete purchase_order_show_delete sales_delivery_order_show_delete purchase_delivery_order_show_delete); + map { $self->{$_} = SL::DB::Default->get->$_ } qw(warehouse_id bin_id); + $::form->get_lists('warehouses' => { 'key' => 'WAREHOUSES', + 'bins' => 'BINS', }); + $self->{WAREHOUSES} = $::form->{WAREHOUSES}; + # leerer lagerplatz mit id 0 + my $no_default_bin_entry = { 'id' => '0', description => '--', 'BINS' => [ { id => '0', description => ''} ] }; + push @ { $self->{WAREHOUSES} }, $no_default_bin_entry; + + if (my $max = scalar @{ $self->{WAREHOUSES} }) { + $self->{warehouse_id} ||= $self->{WAREHOUSES}->[$max -1]->{id}; + $self->{bin_id} ||= $self->{WAREHOUSES}->[$max -1]->{BINS}->[0]->{id}; + } + $self->{show_weight} = SL::DB::Default->get->show_weight; $self->render('client_config/form', title => $::locale->text('Client Configuration')); @@ -66,7 +79,16 @@ sub action_save { map { SL::DB::Default->get->update_attributes($_ => $::form->{$_}); } qw(sales_order_show_delete purchase_order_show_delete sales_delivery_order_show_delete purchase_delivery_order_show_delete); +<<<<<<< HEAD + # undef warehouse_id if the empty value is selected + if ( ($::form->{warehouse_id} == 0) && ($::form->{bin_id} == 0) ) { + undef $::form->{warehouse_id}; + undef $::form->{bin_id}; + } + map { SL::DB::Default->get->update_attributes($_ => $::form->{$_}); } qw(warehouse_id bin_id); +======= SL::DB::Default->get->update_attributes('show_weight' => $::form->{show_weight}); +>>>>>>> gewicht flash_later('info', $::locale->text('Client Configuration saved!')); diff --git a/SL/Controller/CsvImport/Base.pm b/SL/Controller/CsvImport/Base.pm index 7f4326a07..db6dc4d87 100644 --- a/SL/Controller/CsvImport/Base.pm +++ b/SL/Controller/CsvImport/Base.pm @@ -5,6 +5,7 @@ use strict; use List::MoreUtils qw(pairwise); use SL::Helper::Csv; +use SL::DB::Currency; use SL::DB::Customer; use SL::DB::Language; use SL::DB::PaymentTerm; @@ -16,7 +17,7 @@ use parent qw(Rose::Object); use Rose::Object::MakeMethods::Generic ( scalar => [ qw(controller file csv test_run save_with_cascade) ], - 'scalar --get_set_init' => [ qw(profile displayable_columns existing_objects class manager_class cvar_columns all_cvar_configs all_languages payment_terms_by all_vc vc_by) ], + 'scalar --get_set_init' => [ qw(profile displayable_columns existing_objects class manager_class cvar_columns all_cvar_configs all_languages payment_terms_by all_currencies default_currency_id all_vc vc_by) ], ); sub run { @@ -139,6 +140,18 @@ sub init_all_languages { return SL::DB::Manager::Language->get_all; } +sub init_all_currencies { + my ($self) = @_; + + return SL::DB::Manager::Currency->get_all; +} + +sub init_default_currency_id { + my ($self) = @_; + + return SL::DB::Default->get->currency_id; +} + sub init_payment_terms_by { my ($self) = @_; diff --git a/SL/Controller/CsvImport/CustomerVendor.pm b/SL/Controller/CsvImport/CustomerVendor.pm index 96eb69691..47964936e 100644 --- a/SL/Controller/CsvImport/CustomerVendor.pm +++ b/SL/Controller/CsvImport/CustomerVendor.pm @@ -13,7 +13,7 @@ use parent qw(SL::Controller::CsvImport::Base); use Rose::Object::MakeMethods::Generic ( - 'scalar --get_set_init' => [ qw(table languages_by businesses_by) ], + 'scalar --get_set_init' => [ qw(table languages_by businesses_by currencies_by) ], ); sub init_table { @@ -44,6 +44,12 @@ sub init_languages_by { return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $self->all_languages } } ) } qw(id description article_code) }; } +sub init_currencies_by { + my ($self) = @_; + + return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $self->all_currencies } } ) } qw(id name) }; +} + sub check_objects { my ($self) = @_; @@ -65,6 +71,7 @@ sub check_objects { $self->check_language($entry); $self->check_business($entry); $self->check_payment($entry); + $self->check_currency($entry); $self->handle_cvars($entry); next if @{ $entry->{errors} }; @@ -155,6 +162,36 @@ sub check_language { return 1; } +sub check_currency { + my ($self, $entry) = @_; + + my $object = $entry->{object}; + + # Check whether or not currency ID is valid. + if ($object->currency_id && !$self->currencies_by->{id}->{ $object->currency_id }) { + push @{ $entry->{errors} }, $::locale->text('Error: Invalid currency'); + return 0; + } + + # Map name to ID if given. + if (!$object->currency_id && $entry->{raw_data}->{currency}) { + my $currency = $self->currencies_by->{name}->{ $entry->{raw_data}->{currency} }; + if (!$currency) { + push @{ $entry->{errors} }, $::locale->text('Error: Invalid currency'); + return 0; + } + + $object->currency_id($currency->id); + } + + # Set default currency if none was given. + $object->currency_id($self->default_currency_id) if !$object->currency_id; + + $entry->{raw_data}->{currency_id} = $object->currency_id; + + return 1; +} + sub check_business { my ($self, $entry) = @_; @@ -253,6 +290,8 @@ sub setup_displayable_columns { { name => 'contact', description => $::locale->text('Contact') }, { name => 'country', description => $::locale->text('Country') }, { name => 'creditlimit', description => $::locale->text('Credit Limit') }, + { name => 'currency', description => $::locale->text('Currency') }, + { name => 'currency_id', description => $::locale->text('Currency (database ID)') }, { name => 'customernumber', description => $::locale->text('Customer Number') }, { name => 'department_1', description => $::locale->text('Department 1') }, { name => 'department_2', description => $::locale->text('Department 2') }, diff --git a/SL/Controller/DeliveryPlan.pm b/SL/Controller/DeliveryPlan.pm index 33bede6a0..00adfc9f5 100644 --- a/SL/Controller/DeliveryPlan.pm +++ b/SL/Controller/DeliveryPlan.pm @@ -8,7 +8,7 @@ use SL::DB::OrderItem; use SL::Controller::Helper::GetModels; use SL::Controller::Helper::Paginated; use SL::Controller::Helper::Sorted; -use SL::Controller::Helper::ParseFilter; +use SL::Controller::Helper::Filtered; use SL::Controller::Helper::ReportGenerator; use SL::Locale::String; @@ -18,10 +18,12 @@ use Rose::Object::MakeMethods::Generic ( __PACKAGE__->run_before(sub { $::auth->assert('sales_order_edit'); }); -__PACKAGE__->get_models_url_params('flat_filter'); +__PACKAGE__->make_filtered( + MODEL => 'OrderItem', + LAUNDER_TO => 'filter' +); __PACKAGE__->make_paginated( MODEL => 'OrderItem', - PAGINATE_ARGS => 'db_args', ONLY => [ qw(list) ], ); @@ -42,110 +44,91 @@ __PACKAGE__->make_sorted( customer => t8('Customer'), ); -sub action_list { - my ($self) = @_; - - $self->db_args($self->setup_for_list(filter => $::form->{filter})); - $self->flat_filter({ map { $_->{key} => $_->{value} } $::form->flatten_variables('filter') }); - $self->make_filter_summary; - - $self->prepare_report; - - my $orderitems = $self->get_models(%{ $self->db_args }); - - $self->report_generator_list_objects(report => $self->{report}, objects => $orderitems); -} - -# private functions - -sub setup_for_list { - my ($self, %params) = @_; - $self->{filter} = {}; - my %args = ( - parse_filter( - $self->_pre_parse_filter($::form->{filter}, $self->{filter}), - with_objects => [ 'order', 'order.customer', 'part' ], - launder_to => $self->{filter}, - ), - ); - - $args{query} = [ @{ $args{query} || [] }, - ( - 'order.customer_id' => { gt => 0 }, - 'order.closed' => 0, - or => [ 'order.quotation' => 0, 'order.quotation' => undef ], - - # filter by shipped_qty < qty, read from innermost to outermost - 'id' => [ \" - -- 3. resolve the desired information about those - SELECT oi.id FROM ( - -- 2. slice only part, orderitem and both quantities from it - SELECT parts_id, trans_id, qty, SUM(doi_qty) AS doi_qty FROM ( - -- 1. join orderitems and deliverorder items via record_links. - -- also add customer data to filter for sales_orders - SELECT oi.parts_id, oi.trans_id, oi.id, oi.qty, doi.qty AS doi_qty - FROM orderitems oi, oe, record_links rl, delivery_order_items doi - WHERE - oe.id = oi.trans_id AND - oe.customer_id IS NOT NULL AND - (oe.quotation = 'f' OR oe.quotation IS NULL) AND - NOT oe.closed AND - rl.from_id = oe.id AND - rl.from_id = oi.trans_id AND - oe.id = oi.trans_id AND - rl.from_table = 'oe' AND - rl.to_table = 'delivery_orders' AND - rl.to_id = doi.delivery_order_id AND - oi.parts_id = doi.parts_id - ) tuples GROUP BY parts_id, trans_id, qty - ) partials - LEFT JOIN orderitems oi ON partials.parts_id = oi.parts_id AND partials.trans_id = oi.trans_id - WHERE oi.qty > doi_qty - - UNION ALL - - -- 4. since the join over record_links fails for sales_orders wihtout any delivery order - -- retrieve those without record_links at all - SELECT oi.id FROM orderitems oi, oe +my $delivery_plan_query = [ + 'order.customer_id' => { gt => 0 }, + 'order.closed' => 0, + or => [ 'order.quotation' => 0, 'order.quotation' => undef ], + + # filter by shipped_qty < qty, read from innermost to outermost + 'id' => [ \" + -- 3. resolve the desired information about those + SELECT oi.id FROM ( + -- 2. slice only part, orderitem and both quantities from it + SELECT parts_id, trans_id, qty, SUM(doi_qty) AS doi_qty FROM ( + -- 1. join orderitems and deliverorder items via record_links. + -- also add customer data to filter for sales_orders + SELECT oi.parts_id, oi.trans_id, oi.id, oi.qty, doi.qty AS doi_qty + FROM orderitems oi, oe, record_links rl, delivery_order_items doi WHERE oe.id = oi.trans_id AND oe.customer_id IS NOT NULL AND (oe.quotation = 'f' OR oe.quotation IS NULL) AND NOT oe.closed AND - oi.trans_id NOT IN ( - SELECT from_id - FROM record_links rl - WHERE - rl.from_table ='oe' AND - rl.to_table = 'delivery_orders' - ) - - UNION ALL - - -- 5. In case someone deleted a line of the delivery_order there will be a record_link (4 fails) - -- but there won't be a delivery_order_items to find (3 fails too). Search for orphaned orderitems this way - SELECT oi.id FROM orderitems AS oi, oe, record_links AS rl - WHERE + rl.from_id = oe.id AND + rl.from_id = oi.trans_id AND + oe.id = oi.trans_id AND rl.from_table = 'oe' AND rl.to_table = 'delivery_orders' AND + rl.to_id = doi.delivery_order_id AND + oi.parts_id = doi.parts_id + ) tuples GROUP BY parts_id, trans_id, qty + ) partials + LEFT JOIN orderitems oi ON partials.parts_id = oi.parts_id AND partials.trans_id = oi.trans_id + WHERE oi.qty > doi_qty + + UNION ALL + + -- 4. since the join over record_links fails for sales_orders wihtout any delivery order + -- retrieve those without record_links at all + SELECT oi.id FROM orderitems oi, oe + WHERE + oe.id = oi.trans_id AND + oe.customer_id IS NOT NULL AND + (oe.quotation = 'f' OR oe.quotation IS NULL) AND + NOT oe.closed AND + oi.trans_id NOT IN ( + SELECT from_id + FROM record_links rl + WHERE + rl.from_table ='oe' AND + rl.to_table = 'delivery_orders' + ) - oi.trans_id = rl.from_id AND - oi.parts_id NOT IN ( - SELECT doi.parts_id FROM delivery_order_items AS doi WHERE doi.delivery_order_id = rl.to_id - ) AND + UNION ALL - oe.id = oi.trans_id AND + -- 5. In case someone deleted a line of the delivery_order there will be a record_link (4 fails) + -- but there won't be a delivery_order_items to find (3 fails too). Search for orphaned orderitems this way + SELECT oi.id FROM orderitems AS oi, oe, record_links AS rl + WHERE + rl.from_table = 'oe' AND + rl.to_table = 'delivery_orders' AND - oe.customer_id IS NOT NULL AND - (oe.quotation = 'f' OR oe.quotation IS NULL) AND - NOT oe.closed - " ], - ) - ]; + oi.trans_id = rl.from_id AND + oi.parts_id NOT IN ( + SELECT doi.parts_id FROM delivery_order_items AS doi WHERE doi.delivery_order_id = rl.to_id + ) AND - return \%args; + oe.id = oi.trans_id AND + + oe.customer_id IS NOT NULL AND + (oe.quotation = 'f' OR oe.quotation IS NULL) AND + NOT oe.closed + " ], +]; + +sub action_list { + my ($self) = @_; + + $self->make_filter_summary; + + my $orderitems = $self->get_models(query => $delivery_plan_query, with_objects => [ 'order', 'order.customer', 'part' ]); + + $self->prepare_report; + $self->report_generator_list_objects(report => $self->{report}, objects => $orderitems); } +# private functions +# sub prepare_report { my ($self) = @_; @@ -209,14 +192,15 @@ sub make_filter_summary { [ $filter->{order}{customer}{"customernumber:substr::ilike"}, $::locale->text('Customer Number') ], ); - my @flags = ( - [ $filter->{part}{type}{part}, $::locale->text('Parts') ], - [ $filter->{part}{type}{service}, $::locale->text('Services') ], - [ $filter->{part}{type}{assembly}, $::locale->text('Assemblies') ], + my %flags = ( + part => $::locale->text('Parts'), + service => $::locale->text('Services'), + assembly => $::locale->text('Assemblies'), ); + my @flags = map { $flags{$_} } @{ $filter->{part}{type} || [] }; for (@flags) { - push @filter_strings, "$_->[1]" if $_->[0]; + push @filter_strings, $_ if $_; } for (@filters) { push @filter_strings, "$_->[1]: $_->[0]" if $_->[0]; @@ -248,45 +232,4 @@ sub link_to { } } -# unfortunately ParseFilter can't handle compount filters. -# so we clone the original filter (still need that for serializing) -# rip out the options we know an replace them with the compound options. -# ParseFilter will take care of the prefixing then. -sub _pre_parse_filter { - my ($self, $orig_filter, $launder_to) = @_; - - return undef unless $orig_filter; - - my $filter = clone($orig_filter); - if ($filter->{part} && $filter->{part}{type}) { - $launder_to->{part}{type} = delete $filter->{part}{type}; - my @part_filters = grep $_, map { - $launder_to->{part}{type}{$_} ? SL::DB::Manager::Part->type_filter($_) : () - } qw(part service assembly); - - push @{ $filter->{and} }, or => [ @part_filters ] if @part_filters; - } - - for my $op (qw(le ge)) { - if ($filter->{"reqdate:date::$op"}) { - $launder_to->{"reqdate_date__$op"} = delete $filter->{"reqdate:date::$op"}; - my $parsed_date = DateTime->from_lxoffice($launder_to->{"reqdate_date__$op"}); - push @{ $filter->{and} }, or => [ - 'reqdate' => { $op => $parsed_date }, - and => [ - 'reqdate' => undef, - 'order.reqdate' => { $op => $parsed_date }, - ] - ] if $parsed_date; - } - } - - if (my $style = delete $filter->{searchstyle}) { - $self->{searchstyle} = $style; - $launder_to->{searchstyle} = $style; - } - - return $filter; -} - 1; diff --git a/SL/Controller/Helper/Filtered.pm b/SL/Controller/Helper/Filtered.pm new file mode 100644 index 000000000..d248bf6f3 --- /dev/null +++ b/SL/Controller/Helper/Filtered.pm @@ -0,0 +1,310 @@ +package SL::Controller::Helper::Filtered; + +use strict; + +use Exporter qw(import); +use SL::Controller::Helper::ParseFilter (); +use List::MoreUtils qw(uniq); +our @EXPORT = qw(make_filtered get_filter_spec get_current_filter_params disable_filtering _save_current_filter_params _callback_handler_for_filtered _get_models_handler_for_filtered); + +use constant PRIV => '__filteredhelper_priv'; + +my %controller_filter_spec; + +sub make_filtered { + my ($class, %specs) = @_; + + $specs{MODEL} //= $class->controller_name; + $specs{MODEL} =~ s{ ^ SL::DB:: (?: .* :: )? }{}x; + $specs{FORM_PARAMS} //= 'filter'; + $specs{LAUNDER_TO} = '__INPLACE__' unless exists $specs{LAUNDER_TO}; + $specs{ONLY} //= []; + $specs{ONLY} = [ $specs{ONLY} ] if !ref $specs{ONLY}; + $specs{ONLY_MAP} = @{ $specs{ONLY} } ? { map { ($_ => 1) } @{ $specs{ONLY} } } : { '__ALL__' => 1 }; + + $controller_filter_spec{$class} = \%specs; + + my %hook_params = @{ $specs{ONLY} } ? ( only => $specs{ONLY} ) : (); + $class->run_before('_save_current_filter_params', %hook_params); + + SL::Controller::Helper::GetModels::register_get_models_handlers( + $class, + callback => '_callback_handler_for_filtered', + get_models => '_get_models_handler_for_filtered', + ONLY => $specs{ONLY}, + ); + + # $::lxdebug->dump(0, "CONSPEC", \%specs); +} + +sub get_filter_spec { + my ($class_or_self) = @_; + + return $controller_filter_spec{ref($class_or_self) || $class_or_self}; +} + +sub get_current_filter_params { + my ($self) = @_; + + return %{ _priv($self)->{filter_params} } if _priv($self)->{filter_params}; + + require Carp; + Carp::confess('It seems a GetModels plugin tries to access filter params before they got calculated. Make sure your make_filtered call comes first.'); +} + +sub _make_current_filter_params { + my ($self, %params) = @_; + + my $spec = $self->get_filter_spec; + my $filter = $params{filter} // _priv($self)->{filter} // {}, + my %filter_args = _get_filter_args($self, $spec); + my %parse_filter_args = ( + class => "SL::DB::Manager::$spec->{MODEL}", + with_objects => $params{with_objects}, + ); + my $laundered; + if ($spec->{LAUNDER_TO} eq '__INPLACE__') { + + } elsif ($spec->{LAUNDER_TO}) { + $laundered = {}; + $parse_filter_args{launder_to} = $laundered; + } else { + $parse_filter_args{no_launder} = 1; + } + + my %calculated_params = SL::Controller::Helper::ParseFilter::parse_filter($filter, %parse_filter_args); + + $calculated_params{query} = [ + @{ $calculated_params{query} || [] }, + @{ $filter_args{query} || [] }, + @{ $params{query} || [] }, + ]; + + $calculated_params{with_objects} = [ + uniq + @{ $calculated_params{with_objects} || [] }, + @{ $filter_args{with_objects} || [] }, + @{ $params{with_objects} || [] }, + ]; + + if ($laundered) { + if ($self->can($spec->{LAUNDER_TO})) { + $self->${\ $spec->{LAUNDER_TO} }($laundered); + } else { + $self->{$spec->{LAUNDER_TO}} = $laundered; + } + } + + # $::lxdebug->dump(0, "get_current_filter_params: ", \%calculated_params); + + _priv($self)->{filter_params} = \%calculated_params; + + return %calculated_params; +} + +sub disable_filtering { + my ($self) = @_; + _priv($self)->{disabled} = 1; +} + +# +# private functions +# + +sub _get_filter_args { + my ($self, $spec) = @_; + + $spec ||= $self->get_filter_spec; + + my %filter_args = ref($spec->{FILTER_ARGS}) eq 'CODE' ? %{ $spec->{FILTER_ARGS}->($self) } + : $spec->{FILTER_ARGS} ? do { my $sub = $spec->{FILTER_ARGS}; %{ $self->$sub() } } + : (); +} + +sub _save_current_filter_params { + my ($self) = @_; + + return if !_is_enabled($self); + + my $filter_spec = $self->get_filter_spec; + $self->{PRIV()}{filter} = $::form->{ $filter_spec->{FORM_PARAMS} }; + + # $::lxdebug->message(0, "saving current filter params to " . $self->{PRIV()}->{page} . ' / ' . $self->{PRIV()}->{per_page}); +} + +sub _callback_handler_for_filtered { + my ($self, %params) = @_; + my $priv = _priv($self); + + if (_is_enabled($self) && $priv->{filter}) { + my $filter_spec = $self->get_filter_spec; + my ($flattened) = SL::Controller::Helper::ParseFilter::flatten($priv->{filter}, undef, $filter_spec->{FORM_PARAMS}); + %params = (%params, @$flattened); + } + + # $::lxdebug->dump(0, "CB handler for filtered; params after flatten:", \%params); + + return %params; +} + +sub _get_models_handler_for_filtered { + my ($self, %params) = @_; + my $spec = $self->get_filter_spec; + + # $::lxdebug->dump(0, "params in get_models_for_filtered", \%params); + + my %filter_params; + %filter_params = _make_current_filter_params($self, %params) if _is_enabled($self); + + # $::lxdebug->dump(0, "GM handler for filtered; params nach modif (is_enabled? " . _is_enabled($self) . ")", \%params); + + return (%params, %filter_params); +} + +sub _priv { + my ($self) = @_; + $self->{PRIV()} ||= {}; + return $self->{PRIV()}; +} + +sub _is_enabled { + my ($self) = @_; + return !_priv($self)->{disabled} && ($self->get_filter_spec->{ONLY_MAP}->{$self->action_name} || $self->get_filter_spec->{ONLY_MAP}->{'__ALL__'}); +} + + +1; + +__END__ + +=pod + +=encoding utf8 + +=head1 NAME + +SL::Controller::Helper::Filtered - A helper for semi-automatic handling +of filtered lists of database models in a controller + +=head1 SYNOPSIS + +In a controller: + + use SL::Controller::Helper::GetModels; + use SL::Controller::Helper::Filtered; + + __PACKAGE__->make_filter( + MODEL => 'Part', + ONLY => [ qw(list) ], + FORM_PARAMS => [ qw(filter) ], + ); + + sub action_list { + my ($self) = @_; + + my $filtered_models = $self->get_models(%addition_filters); + $self->render('controller/list', ENTRIES => $filtered_models); + } + + +=head1 OVERVIEW + +This helper module enables use of the L +methods in conjunction with the L style of +plugins. Additional filters can be defined in the database models and filtering +can be reduced to a minimum of work. + +This plugin can be combined with L and +L for filtered, sorted and paginated lists. + +The controller has to provive information where to look for filter information +at compile time. This call is L. + +The underlying functionality that enables the use of more than just +the paginate helper is provided by the controller helper +C. See the documentation for L for +more information on it. + +=head1 PACKAGE FUNCTIONS + +=over 4 + +=item C + +This function must be called by a controller at compile time. It is +uesd to set the various parameters required for this helper to do its +magic. + +Careful: If you want to use this in conjunction with +L, you need to call C first, +or the paginating will not get all the relevant information to estimate the +number of pages correctly. To ensure this does not happen, this module will +croak when it detects such a scenario. + +The hash C<%filter_spec> can include the following parameters: + +=over 4 + +=item * C + +Optional. A string: the name of the Rose database model that is used +as a default in certain cases. If this parameter is missing then it is +derived from the controller's package (e.g. for the controller +C the C would default to +C). + +=item * C + +Optional. Indicates a key in C<$::form> to be used as filter. + +Defaults to the values C if missing. + +=item * C + +Option. Indicates a target for laundered filter arguments in the controller. +Can be set to C to disable laundering, and can be set to method named or +hash keys of the controller. In the latter case the laundered structure will be +put there. + +Defaults to inplace laundering which is not normally settable. + +=item * C + +Optional. An array reference containing a list of action names for +which the paginate parameters should be saved. If missing or empty then +all actions invoked on the controller are monitored. + +=back + +=back + +=head1 INSTANCE FUNCTIONS + +These functions are called on a controller instance. + +=over 4 + +=item C + +Returns a hash to be used in manager C calls or to be passed on to +GetModels. Will only work if the get_models chain has been called at least +once, because only then the full parameters can get parsed and stored. Will +croak otherwise. + +=item C + +Disable filtering for the duration of the current action. Can be used +when using the attribute C to L does not +cover all cases. + +=back + +=head1 BUGS + +Nothing here yet. + +=head1 AUTHOR + +Sven Schöling Es.schoeling@linet-services.deE + +=cut diff --git a/SL/Controller/Helper/GetModels.pm b/SL/Controller/Helper/GetModels.pm index e35365717..8e2ede133 100644 --- a/SL/Controller/Helper/GetModels.pm +++ b/SL/Controller/Helper/GetModels.pm @@ -7,7 +7,7 @@ our @EXPORT = qw(get_models_url_params get_callback get_models); use constant PRIV => '__getmodelshelperpriv'; -my %registered_handlers = ( callback => [], get_models => [] ); +my $registered_handlers = {}; sub register_get_models_handlers { my ($class, %additional_handlers) = @_; @@ -18,7 +18,8 @@ sub register_get_models_handlers { $class->run_before(sub { $_[0]->{PRIV()} = { current_action => $_[1] }; }, %hook_params); - map { push @{ $registered_handlers{$_} }, $additional_handlers{$_} if $additional_handlers{$_} } keys %registered_handlers; + my $handlers = _registered_handlers($class); + map { push @{ $handlers->{$_} }, $additional_handlers{$_} if $additional_handlers{$_} } keys %$handlers; } sub get_models_url_params { @@ -34,7 +35,7 @@ sub get_models_url_params { ); }; - push @{ $registered_handlers{callback} }, $callback; + push @{ _registered_handlers($class)->{callback} }, $callback; } sub get_callback { @@ -48,9 +49,8 @@ sub get_callback { sub get_models { my ($self, %override_params) = @_; - my %default_params = _run_handlers($self, 'get_models'); + my %params = _run_handlers($self, 'get_models', %override_params); - my %params = (%default_params, %override_params); my $model = delete($params{model}) || die "No 'model' to work on"; return "SL::DB::Manager::${model}"->get_all(%params); @@ -63,7 +63,7 @@ sub get_models { sub _run_handlers { my ($self, $handler_type, %params) = @_; - foreach my $sub (@{ $registered_handlers{$handler_type} }) { + foreach my $sub (@{ _registered_handlers(ref $self)->{$handler_type} }) { if (ref $sub eq 'CODE') { %params = $sub->($self, %params); } elsif ($self->can($sub)) { @@ -76,6 +76,10 @@ sub _run_handlers { return %params; } +sub _registered_handlers { + $registered_handlers->{$_[0]} //= { callback => [], get_models => [] } +} + 1; __END__ diff --git a/SL/Controller/Helper/Paginated.pm b/SL/Controller/Helper/Paginated.pm index 15997b8e9..25c4efa09 100644 --- a/SL/Controller/Helper/Paginated.pm +++ b/SL/Controller/Helper/Paginated.pm @@ -18,6 +18,7 @@ sub make_paginated { $specs{MODEL} =~ s{ ^ SL::DB:: (?: .* :: )? }{}x; $specs{PER_PAGE} ||= "SL::DB::Manager::$specs{MODEL}"->default_objects_per_page; $specs{FORM_PARAMS} ||= [ qw(page per_page) ]; + $specs{PAGINATE_ARGS} ||= '__FILTER__'; $specs{ONLY} ||= []; $specs{ONLY} = [ $specs{ONLY} ] if !ref $specs{ONLY}; $specs{ONLY_MAP} = @{ $specs{ONLY} } ? { map { ($_ => 1) } @{ $specs{ONLY} } } : { '__ALL__' => 1 }; @@ -57,9 +58,13 @@ sub get_current_paginate_params { per_page => ($params{per_page} * 1) || $spec->{PER_PAGE}, ); - my %paginate_args = ref($spec->{PAGINATE_ARGS}) eq 'CODE' ? %{ $spec->{PAGINATE_ARGS}->($self) } - : $spec->{PAGINATE_ARGS} ? do { my $sub = $spec->{PAGINATE_ARGS}; %{ $self->$sub() } } - : (); + # try to use Filtered if available and nothing else is configured, but don't + # blow up if the controller does not use Filtered + my %paginate_args = ref($spec->{PAGINATE_ARGS}) eq 'CODE' ? %{ $spec->{PAGINATE_ARGS}->($self) } + : $spec->{PAGINATE_ARGS} eq '__FILTER__' + && $self->can('get_current_filter_params') ? $self->get_current_filter_params + : $spec->{PAGINATE_ARGS} ne '__FILTER__' ? do { my $sub = $spec->{PAGINATE_ARGS}; %{ $self->$sub() } } + : (); my $calculated_params = "SL::DB::Manager::$spec->{MODEL}"->paginate(%paginate_params, args => \%paginate_args); # $::lxdebug->dump(0, "get_current_paginate_params: ", $calculated_params); diff --git a/SL/Controller/Helper/ParseFilter.pm b/SL/Controller/Helper/ParseFilter.pm index 3bf1b6595..ee5a740d1 100644 --- a/SL/Controller/Helper/ParseFilter.pm +++ b/SL/Controller/Helper/ParseFilter.pm @@ -8,6 +8,7 @@ our @EXPORT = qw(parse_filter); use DateTime; use SL::Helper::DateTime; use List::MoreUtils qw(uniq); +use SL::MoreCommon qw(listify); use Data::Dumper; my %filters = ( @@ -33,10 +34,15 @@ sub parse_filter { my ($filter, %params) = @_; my $hint_objects = $params{with_objects} || []; + my $auto_objects = []; - my ($flattened, $objects) = _pre_parse($filter, $hint_objects, '', %params); + my ($flattened, $objects) = flatten($filter, $auto_objects, '', %params); - my $query = _parse_filter($flattened, %params); + if ($params{class}) { + $objects = $hint_objects; + } + + my $query = _parse_filter($flattened, $objects, %params); _launder_keys($filter, $params{launder_to}) unless $params{no_launder}; @@ -55,7 +61,7 @@ sub _launder_keys { if ('' eq ref $filter->{$orig}) { $launder_to->{$key} = $filter->{$orig}; } elsif ('ARRAY' eq ref $filter->{$orig}) { - $launder_to->{$key} = [ @{ $filter->{$orig} } ]; + $launder_to->{"${key}_"} = { map { $_ => 1 } @{ $filter->{$orig} } }; } else { $launder_to->{$key} ||= { }; _launder_keys($filter->{$key}, $launder_to->{$key}); @@ -63,7 +69,7 @@ sub _launder_keys { }; } -sub _pre_parse { +sub flatten { my ($filter, $with_objects, $prefix, %params) = @_; return (undef, $with_objects) unless 'HASH' eq ref $filter; @@ -74,7 +80,7 @@ sub _pre_parse { while (my ($key, $value) = each %$filter) { next if !defined $value || $value eq ''; # 0 is fine if ('HASH' eq ref $value) { - my ($query, $more_objects) = _pre_parse($value, $with_objects, _prefix($prefix, $key)); + my ($query, $more_objects) = flatten($value, $with_objects, _prefix($prefix, $key)); push @result, @$query if $query; push @$with_objects, _prefix($prefix, $key), ($more_objects ? @$more_objects : ()); } else { @@ -86,25 +92,97 @@ sub _pre_parse { } sub _parse_filter { - my ($flattened, %params) = @_; + my ($flattened, $with_objects, %params) = @_; return () unless 'ARRAY' eq ref $flattened; - my %sorted = ( @$flattened ); + $flattened = _collapse_indirect_filters($flattened); - my @keys = sort { length($b) <=> length($a) } keys %sorted; - for my $key (@keys) { - next unless $key =~ /^(.*\b)::$/; - $sorted{$1 . '::' . delete $sorted{$key} } = delete $sorted{$1} if $sorted{$1} && $sorted{$key}; - } + my @result; + for (my $i = 0; $i < scalar @$flattened; $i += 2) { + my ($key, $value) = ($flattened->[$i], $flattened->[$i+1]); - my %result; - while (my ($key, $value) = each %sorted) { ($key, $value) = _apply_all($key, $value, qr/\b:(\w+)/, { %filters, %{ $params{filters} || {} } }); ($key, $value) = _apply_all($key, $value, qr/\b::(\w+)/, { %methods, %{ $params{methods} || {} } }); - $result{$key} = $value; + ($key, $value) = _dispatch_custom_filters($params{class}, $with_objects, $key, $value) if $params{class}; + + push @result, $key, $value; + } + return \@result; +} + +sub _dispatch_custom_filters { + my ($class, $with_objects, $key, $value) = @_; + + # the key should by now have no filters left + # if it has, catch it here: + die 'unrecognized filters' if $key =~ /:/; + + my @tokens = split /\./, $key; + my $last_token = pop @tokens; + my $curr_class = $class->object_class; + + for my $token (@tokens) { + eval { + $curr_class = $curr_class->meta->relationship($token)->class; + 1; + } or do { + require Carp; + Carp::croak("Could not resolve the relationship '$token' in '$key' while building the filter request"); + } + } + + my $manager = $curr_class->meta->convention_manager->auto_manager_class_name; + my $obj_path = join '.', @tokens; + my $obj_prefix = join '.', @tokens, ''; + + if ($manager->can('filter')) { + ($key, $value, my $obj) = $manager->filter($last_token, $value, $obj_prefix); + _add_uniq($with_objects, $obj); + } else { + _add_uniq($with_objects, $obj_path); + } + + return ($key, $value); +} + +sub _add_uniq { + my ($array, $what) = @_; + + $array //= []; + $array = [ uniq @$array, listify($what) ]; +} + +sub _collapse_indirect_filters { + my ($flattened) = @_; + + die 'flattened filter array length is uneven, should be possible to use as hash' if @$flattened % 2; + + my (%keys_to_delete, %keys_to_move, @collapsed); + + # search keys matching /::$/; + for (my $i = 0; $i < scalar @$flattened; $i += 2) { + my ($key, $value) = ($flattened->[$i], $flattened->[$i+1]); + + next unless $key =~ /^(.*\b)::$/; + + $keys_to_delete{$key}++; + $keys_to_move{$1} = $1 . '::' . $value; } - return [ %result ]; + + for (my $i = 0; $i < scalar @$flattened; $i += 2) { + my ($key, $value) = ($flattened->[$i], $flattened->[$i+1]); + + if ($keys_to_move{$key}) { + push @collapsed, $keys_to_move{$key}, $value; + next; + } + if (!$keys_to_delete{$key}) { + push @collapsed, $key, $value; + } + } + + return \@collapsed; } sub _prefix { @@ -200,19 +278,6 @@ As a rule all value filters require a single colon and must be placed before match method suffixes, which are appended with 2 colons. See below for a full list of modifiers. -The reason for the method being last is that it is possible to specify the -method in another input. Suppose you want a date input and a separate -before/after/equal select, you can use the following: - - [% L.date_tag('filter.appointed_date:date', ... ) %] - -and later - - [% L.select_tag('filter.appointed_date::', ... ) %] - -The special empty method will be used to set the method for the previous -method-less input. - =back =head1 LAUNDERING @@ -223,15 +288,21 @@ default laundered into underscores, so you can use them like this: [% L.input_tag('filter.price:number::lt', filter.price_number__lt) %] -All of your original entries will stay intactg. If you don't want this to +Also Template has trouble when looking up the contents of arrays, so +these will get copied into a _ suffixed version as hashes: + + [% L.checkbox_tag('filter.ids[]', value=15, checked=filter.ids_.15) %] + +All of your original entries will stay intact. If you don't want this to happen pass C<< no_launder => 1 >> as a parameter. Additionally you can pass a different target for the laundered values with the C parameter. It takes an hashref and will deep copy all values in your filter to the target. So -if you have a filter that looks liek this: +if you have a filter that looks like this: $filter = { 'price:number::lt' => '2,30', - 'closed => '1', + closed => '1', + type => [ 'part', 'assembly' ], } and parse it with @@ -243,9 +314,46 @@ like this: $filter = { 'price_number__lt' => '2,30', - 'closed => '1', + closed => '1', + 'type_' => { part => 1, assembly => 1 }, + } + +=head1 INDIRECT FILTER METHODS + +The reason for the method being last is that it is possible to specify the +method in another input. Suppose you want a date input and a separate +before/after/equal select, you can use the following: + + [% L.date_tag('filter.appointed_date:date', ... ) %] + +and later + + [% L.select_tag('filter.appointed_date:date::', ... ) %] + +The special empty method will be used to set the method for the previous +method-less input. + +=head1 CUSTOM FILTERS FROM OBJECTS + +If the L call contains a parameter C, custom filters will +be honored. Suppose you have added a custom filter 'all' for parts which +expands to search both description and partnumber, the following + + $filter = { + 'part.all:substr::ilike' => 'A1', } +will expand to: + + query => [ + or => [ + part.description => { ilike => '%A1%' }, + part.partnumber => { ilike => '%A1%' }, + ] + ] + +For more abuot custom filters, see L. + =head1 FILTERS (leading with :) The following filters are built in, and can be used. @@ -307,7 +415,7 @@ following will not work as you expect: L.input_tag('customer.name:substr::ilike', ...) L.input_tag('invoice.customer.name:substr::ilike', ...) -This will sarch for orders whoe invoice has the _same_ customer, which matches +This will sarch for orders whose invoice has the _same_ customer, which matches both inputs. This is because tables are aliased by their name and not by their position in with_objects. diff --git a/SL/DB/Currency.pm b/SL/DB/Currency.pm new file mode 100644 index 000000000..14ba066e9 --- /dev/null +++ b/SL/DB/Currency.pm @@ -0,0 +1,13 @@ +# 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::Currency; + +use strict; + +use SL::DB::MetaSetup::Currency; + +# Creates get_all, get_all_count, get_all_iterator, delete_all and update_all. +__PACKAGE__->meta->make_manager_class; + +1; diff --git a/SL/DB/Default.pm b/SL/DB/Default.pm index b71b0f474..4ea7be63f 100644 --- a/SL/DB/Default.pm +++ b/SL/DB/Default.pm @@ -9,8 +9,8 @@ __PACKAGE__->meta->make_manager_class; sub get_default_currency { my $self = shift->get; - my @currencies = grep { $_ } split(/:/, $self->curr || ''); - return $currencies[0] || ''; + return $self->currency->name || '' if $self->currency_id; + return ''; } sub get { diff --git a/SL/DB/Helper/ALL.pm b/SL/DB/Helper/ALL.pm index bff5089b9..f951bf9d0 100644 --- a/SL/DB/Helper/ALL.pm +++ b/SL/DB/Helper/ALL.pm @@ -20,6 +20,7 @@ use SL::DB::Chart; use SL::DB::Contact; use SL::DB::CsvImportProfile; use SL::DB::CsvImportProfileSetting; +use SL::DB::Currency; use SL::DB::CustomVariable; use SL::DB::CustomVariableConfig; use SL::DB::CustomVariableValidity; diff --git a/SL/DB/Helper/Filtered.pm b/SL/DB/Helper/Filtered.pm new file mode 100644 index 000000000..15e098527 --- /dev/null +++ b/SL/DB/Helper/Filtered.pm @@ -0,0 +1,186 @@ +package SL::DB::Helper::Filtered; + +use strict; +use SL::Controller::Helper::ParseFilter (); + +require Exporter; +our @ISA = qw(Exporter); +our @EXPORT = qw (filter add_filter_specs); + +my %filter_spec; + +sub filter { + my ($class, $key, $value, $prefix) = @_; + + my $filters = _get_filters($class); + + return ($key, $value) unless $filters->{$key}; + + return $filters->{$key}->($key, $value, $prefix); +} + +sub _get_filters { + my ($class) = @_; + return $filter_spec{$class} ||= {}; +} + +sub add_filter_specs { + my $class = shift; + + my $filters = _get_filters($class); + + while (@_ > 1) { + my $key = shift; + $filters->{$key} = shift; + } +} + +1; + +__END__ + +=encoding utf-8 + +=head1 NAME + +SL::Helper::Sorted - Manager mixin for filtered results. + +=head1 SYNOPSIS + +In the manager: + + use SL::Helper::Filtered; + + __PACKAGE__->add_filter_specs( + custom_filter_name => sub { + my ($key, $value, $prefix) = @_; + # code to handle this + return ($key, $value, $with_objects); + }, + another_filter_name => \&_sub_to_handle_this, + ); + +In consuming code: + + ($key, $value, $with_objects) = $manager_class->filter($key, $value, $prefix); + +=head1 FUNCTIONS + +=over 4 + +=item C + +Adds new filters to this package as key value pairs. The key will be the new +filters name, the value is expected to be a coderef to an implementation of +this filter. See L for details on this. + +You can add multiple filters in one call, but only one filter per key. + +=item C + +Tells the manager to pply custom filters. If none is registered for C<$key>, +returns C<$key, $value>. + +Otherwise the filter code is called. + +=back + +=head1 INTERFACE OF A CUSTOM FILTER + +Lets look at an example of a working filter. Suppose your model has a lot of +notes fields, and you need to search in all of them. A working filter would be: + + __PACKAGE__->add_filter_specs( + all_notes => sub { + my ($key, $value, $prefix) = @_; + + return or => [ + $prefix . notes1 => $value, + $prefix . notes2 => $value, + ]; + } + ); + +If someone filters for C, your +filter will get called with: + + ->filter('all_notes', { ilike => '%telephone%' }, '') + +and the result will be: + + or => [ + notes1 => { notes1 => '%telephone%' }, + notes2 => { notes1 => '%telephone%' }, + ] + +The prefix is to make sure this also works when called on submodels: + + C + +will pass C as prefix so that the resulting query will be: + + or => [ + customer.notes1 => { notes1 => '%telephone%' }, + customer.notes2 => { notes1 => '%telephone%' }, + ] + +which is pretty much what you would expect. + +As a final touch consider a filter that needs to search somewhere else to work, +like this one: + + __PACKAGE__->add_filter_specs( + name => sub { + my ($key, $value, $prefix) = @_; + + return $prefix . person.name => $value, + $prefix . 'person'; + }, + }; + +Now you can search for C in your model without ever knowing that the real +name lies in the table C. Unfortunately Rose has to know about it to +get the joins right, and so you need to tell it to include C into its +C. That's the reason for the third return value. + + +To summarize: + +=over 4 + +=item * + +You will get passed the name of your filter as C<$key> stripped of all filters +and escapes. + +=item * + +You will get passed the C<$value> processed with all filters and escapes. + +=item * + +You will get passed a C<$prefix> that can be prepended to all database columns +to make sense to Rose. + +=item * + +You are expeceted to return exactly one key and one value. That can mean you +have to encapsulate your arguments into C<< or => [] >> or C<< and => [] >> blocks. + +=item * + +If your filter needs relationships that are not always loaded, you need to +return them in C style. If you need to return more than one, use +an arrayref. + +=back + +=head1 BUGS + +None yet. + +=head1 AUTHOR + +Sven Schöling Es.schoeling@linet-services.deE + +=cut diff --git a/SL/DB/Helper/FlattenToForm.pm b/SL/DB/Helper/FlattenToForm.pm index c21df9746..9754d2bb3 100644 --- a/SL/DB/Helper/FlattenToForm.pm +++ b/SL/DB/Helper/FlattenToForm.pm @@ -14,11 +14,11 @@ sub flatten_to_form { my $vc = $self->can('customer_id') && $self->customer_id ? 'customer' : 'vendor'; - _copy($self, $form, '', '', 0, qw(id type taxzone_id ordnumber quonumber invnumber donumber cusordnumber taxincluded shippingpoint shipvia notes intnotes curr cp_id + _copy($self, $form, '', '', 0, qw(id type taxzone_id ordnumber quonumber invnumber donumber cusordnumber taxincluded shippingpoint shipvia notes intnotes cp_id employee_id salesman_id closed department_id language_id payment_id delivery_customer_id delivery_vendor_id shipto_id proforma globalproject_id delivered transaction_description container_type accepted_by_customer invoice terms storno storno_id dunning_config_id orddate quodate reqdate gldate duedate deliverydate datepaid transdate)); - $form->{currency} = $form->{curr}; # curr is called currency in almost all forms + $form->{currency} = $form->{curr} = $self->currency_id ? $self->currency->name || '' : ''; if (_has($self, 'transdate')) { my $transdate_idx = ref($self) eq 'SL::DB::Order' ? ($self->quotation ? 'quodate' : 'orddate') diff --git a/SL/DB/Helper/Mappings.pm b/SL/DB/Helper/Mappings.pm index cc4d2abe1..f0b8be9af 100644 --- a/SL/DB/Helper/Mappings.pm +++ b/SL/DB/Helper/Mappings.pm @@ -55,6 +55,7 @@ my %lxoffice_package_names = ( csv_import_reports => 'csv_import_report', csv_import_report_rows => 'csv_import_report_row', csv_import_report_status => 'csv_import_report_status', + currencies => 'currency', custom_variable_configs => 'custom_variable_config', custom_variables => 'custom_variable', custom_variables_validity => 'custom_variable_validity', diff --git a/SL/DB/Helper/PriceTaxCalculator.pm b/SL/DB/Helper/PriceTaxCalculator.pm index 0801299fb..73613f89d 100644 --- a/SL/DB/Helper/PriceTaxCalculator.pm +++ b/SL/DB/Helper/PriceTaxCalculator.pm @@ -53,8 +53,9 @@ sub calculate_prices_and_taxes { sub _get_exchangerate { my ($self, $data, %params) = @_; - if (($self->curr || '') ne SL::DB::Default->get_default_currency) { - $data->{exchangerate} = $::form->check_exchangerate(\%::myconfig, $self->curr, $self->transdate, $data->{is_sales} ? 'buy' : 'sell'); + my $currency = $self->currency_id ? $self->currency->name || '' : ''; + if ($currency ne SL::DB::Default->get_default_currency) { + $data->{exchangerate} = $::form->check_exchangerate(\%::myconfig, $currency, $self->transdate, $data->{is_sales} ? 'buy' : 'sell'); $data->{exchangerate} ||= $params{exchangerate}; } $data->{exchangerate} ||= 1; diff --git a/SL/DB/Invoice.pm b/SL/DB/Invoice.pm index 09c7f86e7..d93de711a 100644 --- a/SL/DB/Invoice.pm +++ b/SL/DB/Invoice.pm @@ -98,9 +98,9 @@ sub new_from { my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_terms->terms_netto : 0; - my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes curr salesman_id cusordnumber ordnumber quonumber + my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes salesman_id cusordnumber ordnumber quonumber department_id cp_id language_id payment_id delivery_customer_id delivery_vendor_id taxzone_id shipto_id - globalproject_id transaction_description)), + globalproject_id transaction_description currency_id)), transdate => DateTime->today_local, gldate => DateTime->today_local, duedate => DateTime->today_local->add(days => $terms * 1), diff --git a/SL/DB/Manager/OrderItem.pm b/SL/DB/Manager/OrderItem.pm index e908b6b1f..e964f4ff3 100644 --- a/SL/DB/Manager/OrderItem.pm +++ b/SL/DB/Manager/OrderItem.pm @@ -5,12 +5,27 @@ use strict; use SL::DB::Helper::Manager; use base qw(SL::DB::Helper::Manager); +use SL::DB::Helper::Filtered; use SL::DB::Helper::Paginated; use SL::DB::Helper::Sorted; sub object_class { 'SL::DB::OrderItem' } __PACKAGE__->make_manager_methods; +__PACKAGE__->add_filter_specs( + reqdate => sub { + my ($key, $value, $prefix) = @_; + + return or => [ + $prefix . reqdate => $value, + and => [ + $prefix . reqdate => undef, + $prefix . 'order.reqdate' => $value, + ] + ], $prefix . 'order'; + }, +); + sub _sort_spec { return ( columns => { delivery_date => [ 'deliverydate', ], diff --git a/SL/DB/Manager/Part.pm b/SL/DB/Manager/Part.pm index 6c781a9fe..e23834625 100644 --- a/SL/DB/Manager/Part.pm +++ b/SL/DB/Manager/Part.pm @@ -3,6 +3,9 @@ package SL::DB::Manager::Part; use strict; use SL::DB::Helper::Manager; +use SL::DB::Helper::Sorted; +use SL::DB::Helper::Paginated; +use SL::DB::Helper::Filtered; use base qw(SL::DB::Helper::Manager); use Carp; @@ -12,31 +15,49 @@ use SL::MoreCommon qw(listify); sub object_class { 'SL::DB::Part' } __PACKAGE__->make_manager_methods; +__PACKAGE__->add_filter_specs( + type => sub { + my ($key, $value, $prefix) = @_; + return __PACKAGE__->type_filter($value, $prefix); + }, + all => sub { + my ($key, $value, $prefix) = @_; + return or => [ map { $prefix . $_ => $value } qw(partnumber description) ] + } +); sub type_filter { - my ($class, $type) = @_; + my ($class, $type, $prefix) = @_; return () unless $type; + $prefix //= ''; + + # this is to make selection like type => { part => 1, service => 1 } work + if ('HASH' eq ref $type) { + $type = grep { $type->{$_} } keys %$type; + } + my @types = listify($type); my @filter; for my $type (@types) { if ($type =~ m/^part/) { - push @filter, (and => [ or => [ assembly => 0, assembly => undef ], - '!inventory_accno_id' => 0, - '!inventory_accno_id' => undef, + push @filter, (and => [ or => [ $prefix . assembly => 0, $prefix . assembly => undef ], + "!${prefix}inventory_accno_id" => 0, + "!${prefix}inventory_accno_id" => undef, ]); } elsif ($type =~ m/^service/) { - push @filter, (and => [ or => [ assembly => 0, assembly => undef ], - or => [ inventory_accno_id => 0, inventory_accno_id => undef ], + push @filter, (and => [ or => [ $prefix . assembly => 0, $prefix . assembly => undef ], + or => [ $prefix . inventory_accno_id => 0, $prefix . inventory_accno_id => undef ], ]); } elsif ($type =~ m/^assembl/) { - push @filter, (assembly => 1); + push @filter, ($prefix . assembly => 1); } } - return @filter ? (or => \@filter) : (); + return @filter > 2 ? (or => \@filter) : + @filter ? @filter : (); } sub get_ordered_qty { diff --git a/SL/DB/MetaSetup/Currency.pm b/SL/DB/MetaSetup/Currency.pm new file mode 100644 index 000000000..705467f03 --- /dev/null +++ b/SL/DB/MetaSetup/Currency.pm @@ -0,0 +1,23 @@ +# This file has been auto-generated. Do not modify it; it will be overwritten +# by rose_auto_create_model.pl automatically. +package SL::DB::Currency; + +use strict; + +use base qw(SL::DB::Object); + +__PACKAGE__->meta->setup( + table => 'currencies', + + columns => [ + id => { type => 'serial', not_null => 1 }, + name => { type => 'text', not_null => 1 }, + ], + + primary_key_columns => [ 'id' ], + + unique_key => [ 'name' ], +); + +1; +; diff --git a/SL/DB/MetaSetup/Customer.pm b/SL/DB/MetaSetup/Customer.pm index 82d808cf9..0bb7cd96b 100644 --- a/SL/DB/MetaSetup/Customer.pm +++ b/SL/DB/MetaSetup/Customer.pm @@ -53,8 +53,8 @@ __PACKAGE__->meta->setup( iban => { type => 'varchar', length => 100 }, bic => { type => 'varchar', length => 100 }, direct_debit => { type => 'boolean', default => 'false' }, - curr => { type => 'text' }, taxincluded_checked => { type => 'boolean' }, + currency_id => { type => 'integer', not_null => 1 }, ], primary_key_columns => [ 'id' ], @@ -67,6 +67,11 @@ __PACKAGE__->meta->setup( key_columns => { business_id => 'id' }, }, + currency => { + class => 'SL::DB::Currency', + key_columns => { currency_id => 'id' }, + }, + language_obj => { class => 'SL::DB::Language', key_columns => { language_id => 'id' }, diff --git a/SL/DB/MetaSetup/Default.pm b/SL/DB/MetaSetup/Default.pm index d45a7cfb4..6c1e5ecfb 100644 --- a/SL/DB/MetaSetup/Default.pm +++ b/SL/DB/MetaSetup/Default.pm @@ -20,7 +20,6 @@ __PACKAGE__->meta->setup( weightunit => { type => 'varchar', length => 5 }, businessnumber => { type => 'text' }, version => { type => 'varchar', length => 8 }, - curr => { type => 'text' }, closedto => { type => 'date' }, revtrans => { type => 'boolean', default => 'false' }, ponumber => { type => 'text' }, @@ -68,10 +67,31 @@ __PACKAGE__->meta->setup( ar_show_mark_as_paid => { type => 'boolean', default => 'true' }, ap_show_mark_as_paid => { type => 'boolean', default => 'true' }, assemblynumber => { type => 'text' }, + warehouse_id => { type => 'integer' }, + bin_id => { type => 'integer' }, + currency_id => { type => 'integer', not_null => 1 }, show_weight => { type => 'boolean', default => 'false', not_null => 1 }, ], primary_key_columns => [ 'id' ], + + allow_inline_column_values => 1, + + foreign_keys => [ + bin => { + class => 'SL::DB::Bin', + key_columns => { bin_id => 'id' }, + }, + + warehouse => { + class => 'SL::DB::Warehouse', + key_columns => { warehouse_id => 'id' }, + }, + currency => { + class => 'SL::DB::Currency', + key_columns => { currency_id => 'id' }, + }, + ], ); 1; diff --git a/SL/DB/MetaSetup/DeliveryOrder.pm b/SL/DB/MetaSetup/DeliveryOrder.pm index 523312160..86cdc6406 100644 --- a/SL/DB/MetaSetup/DeliveryOrder.pm +++ b/SL/DB/MetaSetup/DeliveryOrder.pm @@ -39,7 +39,7 @@ __PACKAGE__->meta->setup( taxzone_id => { type => 'integer' }, taxincluded => { type => 'boolean' }, terms => { type => 'integer' }, - curr => { type => 'text' }, + currency_id => { type => 'integer', not_null => 1 }, ], primary_key_columns => [ 'id' ], @@ -52,6 +52,11 @@ __PACKAGE__->meta->setup( key_columns => { cp_id => 'cp_id' }, }, + currency => { + class => 'SL::DB::Currency', + key_columns => { currency_id => 'id' }, + }, + customer => { class => 'SL::DB::Customer', key_columns => { customer_id => 'id' }, diff --git a/SL/DB/MetaSetup/Exchangerate.pm b/SL/DB/MetaSetup/Exchangerate.pm index 44bce842b..ddd09438b 100644 --- a/SL/DB/MetaSetup/Exchangerate.pm +++ b/SL/DB/MetaSetup/Exchangerate.pm @@ -10,18 +10,25 @@ __PACKAGE__->meta->setup( table => 'exchangerate', columns => [ - curr => { type => 'text' }, - transdate => { type => 'date' }, - buy => { type => 'numeric', precision => 5, scale => 15 }, - sell => { type => 'numeric', precision => 5, scale => 15 }, - itime => { type => 'timestamp', default => 'now()' }, - mtime => { type => 'timestamp' }, - id => { type => 'serial', not_null => 1 }, + transdate => { type => 'date' }, + buy => { type => 'numeric', precision => 5, scale => 15 }, + sell => { type => 'numeric', precision => 5, scale => 15 }, + itime => { type => 'timestamp', default => 'now()' }, + mtime => { type => 'timestamp' }, + id => { type => 'serial', not_null => 1 }, + currency_id => { type => 'integer', not_null => 1 }, ], primary_key_columns => [ 'id' ], allow_inline_column_values => 1, + + foreign_keys => [ + currency => { + class => 'SL::DB::Currency', + key_columns => { currency_id => 'id' }, + }, + ], ); 1; diff --git a/SL/DB/MetaSetup/Invoice.pm b/SL/DB/MetaSetup/Invoice.pm index b01b1417d..2a0e50ee2 100644 --- a/SL/DB/MetaSetup/Invoice.pm +++ b/SL/DB/MetaSetup/Invoice.pm @@ -26,7 +26,6 @@ __PACKAGE__->meta->setup( shippingpoint => { type => 'text' }, terms => { type => 'integer', default => '0' }, notes => { type => 'text' }, - curr => { type => 'text' }, ordnumber => { type => 'text' }, employee_id => { type => 'integer' }, quonumber => { type => 'text' }, @@ -57,6 +56,7 @@ __PACKAGE__->meta->setup( donumber => { type => 'text' }, invnumber_for_credit_note => { type => 'text' }, direct_debit => { type => 'boolean', default => 'false' }, + currency_id => { type => 'integer', not_null => 1 }, ], primary_key_columns => [ 'id' ], @@ -69,6 +69,11 @@ __PACKAGE__->meta->setup( key_columns => { cp_id => 'cp_id' }, }, + currency => { + class => 'SL::DB::Currency', + key_columns => { currency_id => 'id' }, + }, + customer => { class => 'SL::DB::Customer', key_columns => { customer_id => 'id' }, diff --git a/SL/DB/MetaSetup/Order.pm b/SL/DB/MetaSetup/Order.pm index f68c276ef..063ca0b0f 100644 --- a/SL/DB/MetaSetup/Order.pm +++ b/SL/DB/MetaSetup/Order.pm @@ -21,7 +21,6 @@ __PACKAGE__->meta->setup( taxincluded => { type => 'boolean' }, shippingpoint => { type => 'text' }, notes => { type => 'text' }, - curr => { type => 'character', length => 3 }, employee_id => { type => 'integer' }, closed => { type => 'boolean', default => 'false' }, quotation => { type => 'boolean', default => 'false' }, @@ -43,9 +42,10 @@ __PACKAGE__->meta->setup( delivered => { type => 'boolean', default => 'false' }, globalproject_id => { type => 'integer' }, salesman_id => { type => 'integer' }, - transaction_description => { type => 'text' }, marge_total => { type => 'numeric', precision => 5, scale => 15 }, marge_percent => { type => 'numeric', precision => 5, scale => 15 }, + transaction_description => { type => 'text' }, + currency_id => { type => 'integer', not_null => 1 }, ], primary_key_columns => [ 'id' ], @@ -58,6 +58,11 @@ __PACKAGE__->meta->setup( key_columns => { cp_id => 'cp_id' }, }, + currency => { + class => 'SL::DB::Currency', + key_columns => { currency_id => 'id' }, + }, + customer => { class => 'SL::DB::Customer', key_columns => { customer_id => 'id' }, diff --git a/SL/DB/MetaSetup/Part.pm b/SL/DB/MetaSetup/Part.pm index c94c66c38..bdc726ef1 100644 --- a/SL/DB/MetaSetup/Part.pm +++ b/SL/DB/MetaSetup/Part.pm @@ -26,7 +26,6 @@ __PACKAGE__->meta->setup( inventory_accno_id => { type => 'integer' }, income_accno_id => { type => 'integer' }, expense_accno_id => { type => 'integer' }, - bin => { type => 'text' }, shop => { type => 'boolean', default => 'false' }, obsolete => { type => 'boolean', default => 'false' }, bom => { type => 'boolean', default => 'false' }, @@ -48,13 +47,21 @@ __PACKAGE__->meta->setup( onhand => { type => 'numeric', default => '0', precision => 5, scale => 25 }, stockable => { type => 'boolean', default => 'false' }, has_sernumber => { type => 'boolean', default => 'false' }, + warehouse_id => { type => 'integer' }, + bin_id => { type => 'integer' }, ], primary_key_columns => [ 'id' ], allow_inline_column_values => 1, + unique_key => [ 'partnumber' ], foreign_keys => [ + bin => { + class => 'SL::DB::Bin', + key_columns => { bin_id => 'id' }, + }, + buchungsgruppen => { class => 'SL::DB::Buchungsgruppe', key_columns => { buchungsgruppen_id => 'id' }, @@ -79,6 +86,11 @@ __PACKAGE__->meta->setup( class => 'SL::DB::Unit', key_columns => { unit => 'name' }, }, + + warehouse => { + class => 'SL::DB::Warehouse', + key_columns => { warehouse_id => 'id' }, + }, ], ); diff --git a/SL/DB/MetaSetup/PurchaseInvoice.pm b/SL/DB/MetaSetup/PurchaseInvoice.pm index bee7eb2ae..e484fb933 100644 --- a/SL/DB/MetaSetup/PurchaseInvoice.pm +++ b/SL/DB/MetaSetup/PurchaseInvoice.pm @@ -23,7 +23,6 @@ __PACKAGE__->meta->setup( duedate => { type => 'date' }, invoice => { type => 'boolean', default => 'false' }, ordnumber => { type => 'text' }, - curr => { type => 'text' }, notes => { type => 'text' }, employee_id => { type => 'integer' }, quonumber => { type => 'text' }, @@ -44,6 +43,8 @@ __PACKAGE__->meta->setup( transaction_description => { type => 'text' }, storno_id => { type => 'integer' }, direct_debit => { type => 'boolean', default => 'false' }, + deliverydate => { type => 'date' }, + currency_id => { type => 'integer', not_null => 1 }, ], primary_key_columns => [ 'id' ], @@ -56,6 +57,11 @@ __PACKAGE__->meta->setup( key_columns => { cp_id => 'cp_id' }, }, + currency => { + class => 'SL::DB::Currency', + key_columns => { currency_id => 'id' }, + }, + department => { class => 'SL::DB::Department', key_columns => { department_id => 'id' }, diff --git a/SL/DB/MetaSetup/Vendor.pm b/SL/DB/MetaSetup/Vendor.pm index d8854fab5..14fb4a929 100644 --- a/SL/DB/MetaSetup/Vendor.pm +++ b/SL/DB/MetaSetup/Vendor.pm @@ -52,7 +52,7 @@ __PACKAGE__->meta->setup( iban => { type => 'varchar', length => 100 }, bic => { type => 'varchar', length => 100 }, direct_debit => { type => 'boolean', default => 'false' }, - curr => { type => 'text' }, + currency_id => { type => 'integer', not_null => 1 }, ], primary_key_columns => [ 'id' ], @@ -65,6 +65,11 @@ __PACKAGE__->meta->setup( key_columns => { business_id => 'id' }, }, + currency => { + class => 'SL::DB::Currency', + key_columns => { currency_id => 'id' }, + }, + language_obj => { class => 'SL::DB::Language', key_columns => { language_id => 'id' }, diff --git a/SL/DB/VC.pm b/SL/DB/VC.pm index 44b2dbb09..c41f28788 100644 --- a/SL/DB/VC.pm +++ b/SL/DB/VC.pm @@ -26,7 +26,7 @@ SQL $query = <connect(@_); } +sub get_options { + my $self = shift; + my $options = { + pg_enable_utf8 => $::locale->is_utf8, + @_ + }; + + return $options; +} + 1; diff --git a/SL/DN.pm b/SL/DN.pm index 46749ab37..8eb407b60 100644 --- a/SL/DN.pm +++ b/SL/DN.pm @@ -208,7 +208,7 @@ sub create_invoice_for_fees { $query = qq|INSERT INTO ar (id, invnumber, transdate, gldate, customer_id, taxincluded, amount, netamount, paid, duedate, - invoice, curr, notes, + invoice, currency_id, notes, employee_id) VALUES ( ?, -- id @@ -228,7 +228,7 @@ sub create_invoice_for_fees { -- duedate: (SELECT duedate FROM dunning WHERE dunning_id = ? LIMIT 1), 'f', -- invoice - ?, -- curr + (SELECT id FROM currencies WHERE name = ?), -- curr ?, -- notes -- employee_id: (SELECT id FROM employee WHERE login = ?) @@ -761,7 +761,7 @@ sub print_dunning { ar.transdate, ar.duedate, ar.customer_id, ar.invnumber, ar.ordnumber, ar.cp_id, ar.amount, ar.netamount, ar.paid, - ar.curr, + (SELECT cu.name FROM currencies cu WHERE cu.id=ar.currency_id) AS curr, ar.amount - ar.paid AS open_amount, ar.amount - ar.paid + da.fee + da.interest AS linetotal diff --git a/SL/DO.pm b/SL/DO.pm index e47b5d116..a4677bbd9 100644 --- a/SL/DO.pm +++ b/SL/DO.pm @@ -229,7 +229,7 @@ sub save { $query = qq|SELECT nextval('id')|; ($form->{id}) = selectrow_query($form, $dbh, $query); - $query = qq|INSERT INTO delivery_orders (id, donumber, employee_id) VALUES (?, '', ?)|; + $query = qq|INSERT INTO delivery_orders (id, donumber, employee_id, currency_id) VALUES (?, '', ?, (SELECT currency_id FROM defaults LIMIT 1))|; do_query($form, $dbh, $query, $form->{id}, conv_i($form->{employee_id})); } @@ -350,7 +350,7 @@ sub save { shippingpoint = ?, shipvia = ?, notes = ?, intnotes = ?, closed = ?, delivered = ?, department_id = ?, language_id = ?, shipto_id = ?, globalproject_id = ?, employee_id = ?, salesman_id = ?, cp_id = ?, transaction_description = ?, - is_sales = ?, taxzone_id = ?, taxincluded = ?, terms = ?, curr = ? + is_sales = ?, taxzone_id = ?, taxincluded = ?, terms = ?, currency_id = (SELECT id FROM currencies WHERE name = ?) WHERE id = ?|; @values = ($form->{donumber}, $form->{ordnumber}, @@ -364,7 +364,7 @@ sub save { conv_i($form->{salesman_id}), conv_i($form->{cp_id}), $form->{transaction_description}, $form->{type} =~ /^sales/ ? 't' : 'f', - conv_i($form->{taxzone_id}), $form->{taxincluded} ? 't' : 'f', conv_i($form->{terms}), substr($form->{currency}, 0, 3), + conv_i($form->{taxzone_id}), $form->{taxincluded} ? 't' : 'f', conv_i($form->{terms}), $form->{currency}, conv_i($form->{id})); do_query($form, $dbh, $query, @values); @@ -618,7 +618,7 @@ sub retrieve { d.description AS department, dord.language_id, dord.shipto_id, dord.globalproject_id, dord.delivered, dord.transaction_description, - dord.taxzone_id, dord.taxincluded, dord.terms, dord.curr AS currency + dord.taxzone_id, dord.taxincluded, dord.terms, (SELECT cu.name FROM currencies cu WHERE cu.id=dord.currency_id) AS currency FROM delivery_orders dord JOIN ${vc} cv ON (dord.${vc}_id = cv.id) LEFT JOIN employee e ON (dord.employee_id = e.id) @@ -640,9 +640,6 @@ sub retrieve { } $sth->finish(); - # remove any trailing whitespace - $form->{currency} =~ s/\s*$//; - $form->{donumber_array} =~ s/\s*$//g; $form->{saved_donumber} = $form->{donumber}; @@ -684,7 +681,7 @@ sub retrieve { $query = qq|SELECT doi.id AS delivery_order_items_id, p.partnumber, p.assembly, p.listprice, doi.description, doi.qty, - doi.sellprice, doi.parts_id AS id, doi.unit, doi.discount, p.bin, p.notes AS partnotes, + doi.sellprice, doi.parts_id AS id, doi.unit, doi.discount, p.notes AS partnotes, doi.reqdate, doi.project_id, doi.serialnumber, doi.lastcost, doi.ordnumber, doi.transdate, doi.cusordnumber, doi.longdescription, doi.price_factor_id, doi.price_factor, doi.marge_price_factor, doi.pricegroup_id, diff --git a/SL/Form.pm b/SL/Form.pm index 7b5d257b8..5a2b38df2 100644 --- a/SL/Form.pm +++ b/SL/Form.pm @@ -138,9 +138,15 @@ sub _flatten_variables_rec { foreach my $idx (0 .. scalar @{ $curr->{$key} } - 1) { my $first_array_entry = 1; - foreach my $hash_key (sort keys %{ $curr->{$key}->[$idx] }) { - push @result, $self->_flatten_variables_rec($curr->{$key}->[$idx], $prefix . $key . ($first_array_entry ? '[+].' : '[].'), $hash_key); - $first_array_entry = 0; + my $element = $curr->{$key}[$idx]; + + if ('HASH' eq ref $element) { + foreach my $hash_key (sort keys %{ $element }) { + push @result, $self->_flatten_variables_rec($element, $prefix . $key . ($first_array_entry ? '[+].' : '[].'), $hash_key); + $first_array_entry = 0; + } + } else { + @result = ({ 'key' => $prefix . $key . ($first_array_entry ? '[+]' : '[]'), 'value' => $element }); } } } @@ -1361,21 +1367,13 @@ sub datetonum { # Database routines used throughout -sub _dbconnect_options { - my $self = shift; - my $options = { pg_enable_utf8 => $::locale->is_utf8, - @_ }; - - return $options; -} - sub dbconnect { $main::lxdebug->enter_sub(2); my ($self, $myconfig) = @_; # connect to database - my $dbh = SL::DBConnect->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, $myconfig->{dbpasswd}, $self->_dbconnect_options) + my $dbh = SL::DBConnect->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, $myconfig->{dbpasswd}, SL::DBConnect->get_options) or $self->dberror; # set db options @@ -1394,7 +1392,7 @@ sub dbconnect_noauto { my ($self, $myconfig) = @_; # connect to database - my $dbh = SL::DBConnect->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, $myconfig->{dbpasswd}, $self->_dbconnect_options(AutoCommit => 0)) + my $dbh = SL::DBConnect->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, $myconfig->{dbpasswd}, SL::DBConnect->get_options(AutoCommit => 0)) or $self->dberror; # set db options @@ -1490,19 +1488,17 @@ sub update_exchangerate { $main::lxdebug->leave_sub(); return; } - $query = qq|SELECT curr FROM defaults|; - - my ($currency) = selectrow_query($self, $dbh, $query); - my ($defaultcurrency) = split m/:/, $currency; + $query = qq|SELECT name AS curr FROM currencies WHERE id=(SELECT currency_id FROM defaults)|; + my ($defaultcurrency) = selectrow_query($self, $dbh, $query); if ($curr eq $defaultcurrency) { $main::lxdebug->leave_sub(); return; } - $query = qq|SELECT e.curr FROM exchangerate e - WHERE e.curr = ? AND e.transdate = ? + $query = qq|SELECT e.currency_id FROM exchangerate e + WHERE e.currency_id = (SELECT cu.id FROM currencies cu WHERE cu.name=?) AND e.transdate = ? FOR UPDATE|; my $sth = prepare_execute_query($self, $dbh, $query, $curr, $transdate); @@ -1528,12 +1524,12 @@ sub update_exchangerate { if ($sth->fetchrow_array) { $query = qq|UPDATE exchangerate SET $set - WHERE curr = ? + WHERE currency_id = (SELECT id FROM currencies WHERE name = ?) AND transdate = ?|; } else { - $query = qq|INSERT INTO exchangerate (curr, buy, sell, transdate) - VALUES (?, $buy, $sell, ?)|; + $query = qq|INSERT INTO exchangerate (currency_id, buy, sell, transdate) + VALUES ((SELECT id FROM currencies WHERE name = ?), $buy, $sell, ?)|; } $sth->finish; do_query($self, $dbh, $query, $curr, $transdate); @@ -1573,18 +1569,17 @@ sub get_exchangerate { return 1; } - $query = qq|SELECT curr FROM defaults|; + $query = qq|SELECT name AS curr FROM currencies WHERE id = (SELECT currency_id FROM defaults)|; - my ($currency) = selectrow_query($self, $dbh, $query); - my ($defaultcurrency) = split m/:/, $currency; + my ($defaultcurrency) = selectrow_query($self, $dbh, $query); - if ($currency eq $defaultcurrency) { + if ($curr eq $defaultcurrency) { $main::lxdebug->leave_sub(); return 1; } $query = qq|SELECT e.$fld FROM exchangerate e - WHERE e.curr = ? AND e.transdate = ?|; + WHERE e.currency_id = (SELECT id FROM currencies WHERE name = ?) AND e.transdate = ?|; my ($exchangerate) = selectrow_query($self, $dbh, $query, $curr, $transdate); @@ -1617,7 +1612,7 @@ sub check_exchangerate { my $dbh = $self->get_standard_dbh($myconfig); my $query = qq|SELECT e.$fld FROM exchangerate e - WHERE e.curr = ? AND e.transdate = ?|; + WHERE e.currency_id = (SELECT id FROM currencies WHERE name = ?) AND e.transdate = ?|; my ($exchangerate) = selectrow_query($self, $dbh, $query, $currency, $transdate); @@ -1632,11 +1627,10 @@ sub get_all_currencies { my $self = shift; my $myconfig = shift || \%::myconfig; my $dbh = $self->get_standard_dbh($myconfig); + my @currencies =(); - my $query = qq|SELECT curr FROM defaults|; - - my ($curr) = selectrow_query($self, $dbh, $query); - my @currencies = grep { $_ } map { s/\s//g; $_ } split m/:/, $curr; + my $query = qq|SELECT name FROM currencies|; + my @currencies = map { $_->{name} } selectall_hashref_query($self, $dbh, $query); $main::lxdebug->leave_sub(); @@ -1647,11 +1641,14 @@ sub get_default_currency { $main::lxdebug->enter_sub(); my ($self, $myconfig) = @_; - my @currencies = $self->get_all_currencies($myconfig); + my $dbh = $self->get_standard_dbh($myconfig); + my $query = qq|SELECT name AS curr FROM currencies WHERE id = (SELECT currency_id FROM defaults)|; + + my ($defaultcurrency) = selectrow_query($self, $dbh, $query); $main::lxdebug->leave_sub(); - return $currencies[0]; + return $defaultcurrency; } sub set_payment_options { @@ -2191,9 +2188,7 @@ $main::lxdebug->enter_sub(); $key = "all_currencies" unless ($key); - my $query = qq|SELECT curr AS currency FROM defaults|; - - $self->{$key} = [split(/\:/ , selectfirst_hashref_query($self, $dbh, $query)->{currency})]; + $self->{$key} = [$self->get_all_currencies()]; $main::lxdebug->leave_sub(); } @@ -2501,7 +2496,7 @@ sub all_vc { my $query = qq|SELECT count(*) FROM $table $obsolete|; my ($count) = selectrow_query($self, $dbh, $query); - if ($count < $myconfig->{vclimit}) { + if ($count <= $myconfig->{vclimit}) { $query = qq|SELECT id, name, salesman_id FROM $table $obsolete ORDER BY name|; @@ -2704,7 +2699,7 @@ sub create_links { $query = qq|SELECT a.cp_id, a.invnumber, a.transdate, a.${table}_id, a.datepaid, - a.duedate, a.ordnumber, a.taxincluded, a.curr AS currency, a.notes, + a.duedate, a.ordnumber, a.taxincluded, (SELECT cu.name FROM currencies cu WHERE cu.id=a.currency_id) AS currency, a.notes, a.intnotes, a.department_id, a.amount AS oldinvtotal, a.paid AS oldtotalpaid, a.employee_id, a.gldate, a.type, a.globalproject_id, ${extra_columns} @@ -2722,9 +2717,6 @@ sub create_links { $self->{$key} = $ref->{$key}; } - # remove any trailing whitespace - $self->{currency} =~ s/\s*$//; - my $transdate = "current_date"; if ($self->{transdate}) { $transdate = $dbh->quote($self->{transdate}); @@ -2808,9 +2800,11 @@ sub create_links { } $sth->finish; + #check das: $query = qq|SELECT - d.curr AS currencies, d.closedto, d.revtrans, + d.closedto, d.revtrans, + (SELECT cu.name FROM currencies cu WHERE cu.id=d.currency_id) AS defaultcurrency, (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id) AS fxgain_accno, (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id) AS fxloss_accno FROM defaults d|; @@ -2822,7 +2816,8 @@ sub create_links { # get date $query = qq|SELECT - current_date AS transdate, d.curr AS currencies, d.closedto, d.revtrans, + current_date AS transdate, d.closedto, d.revtrans, + (SELECT cu.name FROM currencies cu WHERE cu.id=d.currency_id) AS defaultcurrency, (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id) AS fxgain_accno, (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id) AS fxloss_accno FROM defaults d|; @@ -2832,7 +2827,7 @@ sub create_links { if ($self->{"$self->{vc}_id"}) { # only setup currency - ($self->{currency}) = split(/:/, $self->{currencies}) if !$self->{currency}; + ($self->{currency}) = $self->{defaultcurrency} if !$self->{currency}; } else { @@ -2857,19 +2852,17 @@ sub lastname_used { my ($arap, $where); $table = $table eq "customer" ? "customer" : "vendor"; - my %column_map = ("a.curr" => "currency", - "a.${table}_id" => "${table}_id", + my %column_map = ("a.${table}_id" => "${table}_id", "a.department_id" => "department_id", "d.description" => "department", "ct.name" => $table, - "ct.curr" => "cv_curr", + "cu.name" => "currency", "current_date + ct.terms" => "duedate", ); if ($self->{type} =~ /delivery_order/) { $arap = 'delivery_orders'; - delete $column_map{"a.curr"}; - delete $column_map{"ct.curr"}; + delete $column_map{"cu.currency"}; } elsif ($self->{type} =~ /_order/) { $arap = 'oe'; @@ -2898,18 +2891,12 @@ sub lastname_used { FROM $arap a LEFT JOIN $table ct ON (a.${table}_id = ct.id) LEFT JOIN department d ON (a.department_id = d.id) + LEFT JOIN currencies cu ON (cu.id=ct.currency_id) WHERE a.id = ?|; my $ref = selectfirst_hashref_query($self, $dbh, $query, $trans_id); map { $self->{$_} = $ref->{$_} } values %column_map; - # remove any trailing whitespace - $self->{currency} =~ s/\s*$// if $self->{currency}; - $self->{cv_curr} =~ s/\s*$// if $self->{cv_curr}; - - # if customer/vendor currency is set use this - $self->{currency} = $self->{cv_curr} if $self->{cv_curr}; - $main::lxdebug->leave_sub(); } diff --git a/SL/Helper/Csv.pm b/SL/Helper/Csv.pm index e48161492..2d454be37 100644 --- a/SL/Helper/Csv.pm +++ b/SL/Helper/Csv.pm @@ -3,6 +3,7 @@ package SL::Helper::Csv; use strict; use warnings; +use version 0.77; use Carp; use IO::File; use Params::Validate qw(:all); @@ -156,11 +157,19 @@ sub _parse_data { push @data, \%hr; } else { last if $self->_csv->eof; - push @errors, [ - $self->_csv->error_input, - $self->_csv->error_diag, - $self->_io->input_line_number, - ]; + # Text::CSV_XS 0.89 added record number to error_diag + if (qv(Text::CSV_XS->VERSION) >= qv('0.89')) { + push @errors, [ + $self->_csv->error_input, + $self->_csv->error_diag, + ]; + } else { + push @errors, [ + $self->_csv->error_input, + $self->_csv->error_diag, + $self->_io->input_line_number, + ]; + } } last if $self->_csv->eof; } diff --git a/SL/IC.pm b/SL/IC.pm index 74cfe1702..0b8924ba3 100644 --- a/SL/IC.pm +++ b/SL/IC.pm @@ -346,7 +346,8 @@ sub save { notes = ?, formel = ?, rop = ?, - bin = ?, + warehouse_id = ?, + bin_id = ?, buchungsgruppen_id = ?, payment_id = ?, inventory_accno_id = $subq_inventory, @@ -378,7 +379,8 @@ sub save { $form->{notes}, $form->{formel}, $form->{rop}, - $form->{bin}, + conv_i($form->{warehouse_id}), + conv_i($form->{bin_id}), conv_i($form->{buchungsgruppen_id}), conv_i($form->{payment_id}), conv_i($form->{buchungsgruppen_id}), @@ -567,7 +569,7 @@ sub retrieve_assemblies { # retrieve assembly items my $query = qq|SELECT p.id, p.partnumber, p.description, - p.bin, p.onhand, p.rop, + p.onhand, p.rop, (SELECT sum(p2.inventory_accno_id) FROM parts p2, assembly a WHERE (p2.id = a.parts_id) AND (a.id = p.id)) AS inventory @@ -671,7 +673,7 @@ sub assembly_item { # partnumber ean description partsgroup microfiche drawing # # column flags: -# l_partnumber l_description l_listprice l_sellprice l_lastcost l_priceupdate l_weight l_unit l_bin l_rop l_image l_drawing l_microfiche l_partsgroup +# l_partnumber l_description l_listprice l_sellprice l_lastcost l_priceupdate l_weight l_unit l_rop l_image l_drawing l_microfiche l_partsgroup # # exclusives: # itemstatus = active | onhand | short | obsolete | orphaned @@ -721,7 +723,7 @@ sub all_parts { 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 bin rop image)); + my @simple_l_switches = (@all_columns, qw(notes listprice sellprice lastcost priceupdate weight unit rop image)); 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); @@ -964,7 +966,7 @@ sub all_parts { my $token_builder = $make_token_builder->(\%joins_needed); - my @sort_cols = (@simple_filters, qw(id bin 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)); $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'); @@ -1022,7 +1024,7 @@ sub all_parts { if ($form->{searchitems} eq 'assembly' && $form->{bom}) { $query = qq|SELECT p.id, p.partnumber, p.description, a.qty AS onhand, - p.unit, p.bin, p.notes, + p.unit, p.notes, p.sellprice, p.listprice, p.lastcost, p.rop, p.weight, p.priceupdate, p.image, p.drawing, p.microfiche, @@ -1496,7 +1498,7 @@ sub retrieve_accounts { $transdate = $form->{deliverydate}; } } elsif ($form->{script} eq 'ir.pl') { - # when a purchase invoice is opened from the report of purchase invoices + # when a purchase invoice is opened from the report of purchase invoices # $form->{type} isn't set, but $form->{script} is, not sure why this is or # whether this distinction matters in some other scenario. Otherwise one # could probably take out this elsif and add a diff --git a/SL/IR.pm b/SL/IR.pm index 06f84abbc..36f105ebd 100644 --- a/SL/IR.pm +++ b/SL/IR.pm @@ -57,6 +57,7 @@ sub post_invoice { # connect to database, turn off autocommit my $dbh = $provided_dbh ? $provided_dbh : $form->dbconnect_noauto($myconfig); $form->{defaultcurrency} = $form->get_default_currency($myconfig); + my $defaultcurrency = $form->{defaultcurrency}; my $ic_cvar_configs = CVar->get_configs(module => 'IC', dbh => $dbh); @@ -70,18 +71,16 @@ sub post_invoice { my $all_units = AM->retrieve_units($myconfig, $form); +#markierung if (!$payments_only) { if ($form->{id}) { &reverse_invoice($dbh, $form); } else { ($form->{id}) = selectrow_query($form, $dbh, qq|SELECT nextval('glid')|); - do_query($form, $dbh, qq|INSERT INTO ap (id, invnumber) VALUES (?, '')|, $form->{id}); + do_query($form, $dbh, qq|INSERT INTO ap (id, invnumber, currency_id) VALUES (?, '', (SELECT id FROM currencies WHERE name=?))|, $form->{id}, $form->{currency}); } } - my ($currencies) = selectfirst_array_query($form, $dbh, qq|SELECT curr FROM defaults|); - my $defaultcurrency = (split m/:/, $currencies)[0]; - if ($form->{currency} eq $defaultcurrency) { $form->{exchangerate} = 1; } else { @@ -686,7 +685,7 @@ sub post_invoice { orddate = ?, quodate = ?, vendor_id = ?, amount = ?, netamount = ?, paid = ?, duedate = ?, invoice = ?, taxzone_id = ?, notes = ?, taxincluded = ?, - intnotes = ?, curr = ?, storno_id = ?, storno = ?, + intnotes = ?, storno_id = ?, storno = ?, cp_id = ?, employee_id = ?, department_id = ?, globalproject_id = ?, direct_debit = ? WHERE id = ?|; @@ -695,7 +694,7 @@ sub post_invoice { conv_date($form->{orddate}), conv_date($form->{quodate}), conv_i($form->{vendor_id}), $amount, $netamount, $form->{paid}, conv_date($form->{duedate}), '1', $taxzone_id, $form->{notes}, $form->{taxincluded} ? 't' : 'f', - $form->{intnotes}, $form->{currency}, conv_i($form->{storno_id}), $form->{storno} ? 't' : 'f', + $form->{intnotes}, conv_i($form->{storno_id}), $form->{storno} ? 't' : 'f', conv_i($form->{cp_id}), conv_i($form->{employee_id}), conv_i($form->{department_id}), conv_i($form->{globalproject_id}), $form->{direct_debit} ? 't' : 'f', @@ -925,8 +924,7 @@ sub retrieve_invoice { (SELECT c.accno FROM chart c WHERE d.income_accno_id = c.id) AS income_accno, (SELECT c.accno FROM chart c WHERE d.expense_accno_id = c.id) AS expense_accno, (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id) AS fxgain_accno, - (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id) AS fxloss_accno, - d.curr AS currencies + (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id) AS fxloss_accno $q_invdate FROM defaults d|; $ref = selectfirst_hashref_query($form, $dbh, $query); @@ -943,15 +941,12 @@ sub retrieve_invoice { $query = qq|SELECT cp_id, invnumber, transdate AS invdate, duedate, orddate, quodate, globalproject_id, ordnumber, quonumber, paid, taxincluded, notes, taxzone_id, storno, gldate, - intnotes, curr AS currency, direct_debit + intnotes, (SELECT cu.name FROM currencies cu WHERE cu.id=ap.currency_id) AS currency, direct_debit FROM ap WHERE id = ?|; $ref = selectfirst_hashref_query($form, $dbh, $query, conv_i($form->{id})); map { $form->{$_} = $ref->{$_} } keys %$ref; - # remove any trailing whitespace - $form->{currency} =~ s/\s*$//; - $form->{exchangerate} = $form->get_exchangerate($dbh, $form->{currency}, $form->{invdate}, "sell"); # get shipto @@ -975,7 +970,7 @@ sub retrieve_invoice { i.id AS invoice_id, i.description, i.longdescription, i.qty, i.fxsellprice AS sellprice, i.parts_id AS id, i.unit, i.deliverydate, i.project_id, i.serialnumber, i.price_factor_id, i.price_factor, i.marge_price_factor, i.discount, - p.partnumber, p.inventory_accno_id AS part_inventory_accno_id, p.bin, pr.projectnumber, pg.partsgroup + p.partnumber, p.inventory_accno_id AS part_inventory_accno_id, pr.projectnumber, pg.partsgroup FROM invoice i JOIN parts p ON (i.parts_id = p.id) @@ -1093,21 +1088,19 @@ sub get_vendor { v.id AS vendor_id, v.name AS vendor, v.discount as vendor_discount, v.creditlimit, v.terms, v.notes AS intnotes, v.email, v.cc, v.bcc, v.language_id, v.payment_id, - v.street, v.zipcode, v.city, v.country, v.taxzone_id, v.curr, v.direct_debit, + 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 FROM vendor v LEFT JOIN business b ON (b.id = v.business_id) LEFT JOIN payment_terms pt ON (v.payment_id = pt.id) + LEFT JOIN currencies cu ON (v.currency_id = cu.id) WHERE 1=1 $where|; my $ref = selectfirst_hashref_query($form, $dbh, $query, @values); map { $params->{$_} = $ref->{$_} } keys %$ref; - # remove any trailing whitespace - $form->{curr} =~ s/\s*$//; - - # use vendor currency if not empty - $form->{currency} = $form->{curr} if $form->{curr}; + # use vendor currency + $form->{currency} = $form->{curr}; $params->{creditremaining} = $params->{creditlimit}; @@ -1118,7 +1111,7 @@ sub get_vendor { $query = qq|SELECT o.amount, (SELECT e.sell FROM exchangerate e - WHERE (e.curr = o.curr) + WHERE (e.currency_id = o.currency_id) AND (e.transdate = o.transdate)) AS exch FROM oe o WHERE (o.vendor_id = ?) AND (o.quotation = '0') AND (o.closed = '0')|; @@ -1234,7 +1227,7 @@ sub retrieve_item { my $query = qq|SELECT p.id, p.partnumber, p.description, p.lastcost AS sellprice, p.listprice, - p.unit, p.assembly, p.bin, p.onhand, p.formel, + p.unit, p.assembly, p.onhand, p.formel, p.notes AS partnotes, p.notes AS longdescription, p.not_discountable, p.inventory_accno_id, p.price_factor_id, @@ -1393,9 +1386,10 @@ sub vendor_details { # fax and phone and email as vendor* my $query = qq|SELECT ct.*, cp.*, ct.notes as vendornotes, phone as vendorphone, fax as vendorfax, email as vendoremail, - ct.curr AS currency + cu.name AS currency FROM vendor ct LEFT JOIN contacts cp ON (ct.id = cp.cp_cv_id) + LEFT JOIN currencies cu ON (ct.currency_id = cu.id) WHERE (ct.id = ?) $contact ORDER BY cp.cp_id LIMIT 1|; @@ -1412,8 +1406,6 @@ sub vendor_details { } map { $form->{$_} = $ref->{$_} } keys %$ref; - # remove any trailing whitespace - $form->{currency} =~ s/\s*$// if ($form->{currency}); my $custom_variables = CVar->get_custom_variables('dbh' => $dbh, 'module' => 'CT', diff --git a/SL/IS.pm b/SL/IS.pm index 2db51a5ae..1dc1eff60 100644 --- a/SL/IS.pm +++ b/SL/IS.pm @@ -471,9 +471,10 @@ sub customer_details { my $query = qq|SELECT ct.*, cp.*, ct.notes as customernotes, ct.phone AS customerphone, ct.fax AS customerfax, ct.email AS customeremail, - ct.curr AS currency + cu.name AS currency FROM customer ct LEFT JOIN contacts cp on ct.id = cp.cp_cv_id + LEFT JOIN currencies cu ON (ct.currency_id = cu.id) WHERE (ct.id = ?) $where ORDER BY cp.cp_id LIMIT 1|; @@ -491,9 +492,6 @@ sub customer_details { map { $form->{$_} = $ref->{$_} } keys %$ref; - # remove any trailing whitespace - $form->{currency} =~ s/\s*$// if ($form->{currency}); - if ($form->{delivery_customer_id}) { $query = qq|SELECT *, notes as customernotes @@ -549,6 +547,8 @@ sub post_invoice { } $form->{defaultcurrency} = $form->get_default_currency($myconfig); + my $defaultcurrency = $form->{defaultcurrency}; + # Seit neuestem wird die department_id schon übergeben UND $form->department nicht mehr # korrekt zusammengebaut. Sehr wahrscheinlich beim Umstieg auf T8 kaputt gegangen # Ich lass den Code von 2005 erstmal noch stehen ;-) jb 03-2011 @@ -569,8 +569,8 @@ sub post_invoice { $query = qq|SELECT nextval('glid')|; ($form->{"id"}) = selectrow_query($form, $dbh, $query); - $query = qq|INSERT INTO ar (id, invnumber) VALUES (?, ?)|; - do_query($form, $dbh, $query, $form->{"id"}, $form->{"id"}); + $query = qq|INSERT INTO ar (id, invnumber, currency_id) VALUES (?, ?, (SELECT id FROM currencies WHERE name=?))|; + do_query($form, $dbh, $query, $form->{"id"}, $form->{"id"}, $form->{currency}); if (!$form->{invnumber}) { $form->{invnumber} = @@ -583,9 +583,6 @@ sub post_invoice { my ($netamount, $invoicediff) = (0, 0); my ($amount, $linetotal, $lastincomeaccno); - my ($currencies) = selectfirst_array_query($form, $dbh, qq|SELECT curr FROM defaults|); - my $defaultcurrency = (split m/:/, $currencies)[0]; - if ($form->{currency} eq $defaultcurrency) { $form->{exchangerate} = 1; } else { @@ -1098,7 +1095,8 @@ sub post_invoice { amount = ?, netamount = ?, paid = ?, duedate = ?, deliverydate = ?, invoice = ?, shippingpoint = ?, shipvia = ?, terms = ?, notes = ?, intnotes = ?, - curr = ?, department_id = ?, payment_id = ?, taxincluded = ?, + currency_id = (SELECT id FROM currencies WHERE name = ?), + department_id = ?, payment_id = ?, taxincluded = ?, type = ?, language_id = ?, taxzone_id = ?, shipto_id = ?, employee_id = ?, salesman_id = ?, storno_id = ?, storno = ?, cp_id = ?, marge_total = ?, marge_percent = ?, @@ -1572,8 +1570,7 @@ sub retrieve_invoice { (SELECT c.accno FROM chart c WHERE d.income_accno_id = c.id) AS income_accno, (SELECT c.accno FROM chart c WHERE d.expense_accno_id = c.id) AS expense_accno, (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id) AS fxgain_accno, - (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id) AS fxloss_accno, - d.curr AS currencies + (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id) AS fxloss_accno ${query_transdate} FROM defaults d|; @@ -1592,7 +1589,7 @@ sub retrieve_invoice { a.orddate, a.quodate, a.globalproject_id, a.transdate AS invdate, a.deliverydate, a.paid, a.storno, a.gldate, a.shippingpoint, a.shipvia, a.terms, a.notes, a.intnotes, a.taxzone_id, - a.duedate, a.taxincluded, a.curr AS currency, a.shipto_id, a.cp_id, + a.duedate, a.taxincluded, (SELECT cu.name FROM currencies cu WHERE cu.id=a.currency_id) AS currency, a.shipto_id, a.cp_id, a.employee_id, a.salesman_id, a.payment_id, a.language_id, a.delivery_customer_id, a.delivery_vendor_id, a.type, a.transaction_description, a.donumber, a.invnumber_for_credit_note, @@ -1604,9 +1601,6 @@ sub retrieve_invoice { $ref = selectfirst_hashref_query($form, $dbh, $query, $id); map { $form->{$_} = $ref->{$_} } keys %{ $ref }; - # remove any trailing whitespace - $form->{currency} =~ s/\s*$//; - $form->{exchangerate} = $form->get_exchangerate($dbh, $form->{currency}, $form->{invdate}, "buy"); # get shipto @@ -1651,7 +1645,7 @@ sub retrieve_invoice { i.description, i.longdescription, i.qty, i.fxsellprice AS sellprice, i.discount, i.parts_id AS id, i.unit, i.deliverydate AS reqdate, i.project_id, i.serialnumber, i.id AS invoice_pos, i.pricegroup_id, i.ordnumber, i.transdate, i.cusordnumber, i.subtotal, i.lastcost, i.price_factor_id, i.price_factor, i.marge_price_factor, - p.partnumber, p.assembly, p.bin, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id, p.formel, p.listprice, + p.partnumber, p.assembly, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id, p.formel, p.listprice, pr.projectnumber, pg.partsgroup, prg.pricegroup FROM invoice i @@ -1771,13 +1765,14 @@ sub get_customer { c.id AS customer_id, c.name AS customer, c.discount as customer_discount, c.creditlimit, c.terms, c.email, c.cc, c.bcc, c.language_id, c.payment_id, c.street, c.zipcode, c.city, c.country, - c.notes AS intnotes, c.klass as customer_klass, c.taxzone_id, c.salesman_id, c.curr, + c.notes AS intnotes, c.klass as customer_klass, c.taxzone_id, c.salesman_id, cu.name AS curr, c.taxincluded_checked, c.direct_debit, $duedate + COALESCE(pt.terms_netto, 0) AS duedate, b.discount AS tradediscount, b.description AS business FROM customer c LEFT JOIN business b ON (b.id = c.business_id) LEFT JOIN payment_terms pt ON ($payment_id (c.payment_id = pt.id)) + LEFT JOIN currencies cu ON (c.currency_id=cu.id) WHERE c.id = ?|; push @values, $cid; $ref = selectfirst_hashref_query($form, $dbh, $query, @values); @@ -1786,11 +1781,8 @@ sub get_customer { map { $form->{$_} = $ref->{$_} } keys %$ref; - # remove any trailing whitespace - $form->{curr} =~ s/\s*$//; - - # use customer currency if not empty - $form->{currency} = $form->{curr} if $form->{curr}; + # use customer currency + $form->{currency} = $form->{curr}; $query = qq|SELECT sum(amount - paid) AS dunning_amount @@ -1819,7 +1811,7 @@ sub get_customer { $query = qq|SELECT o.amount, (SELECT e.buy FROM exchangerate e - WHERE e.curr = o.curr + WHERE e.currency_id = o.currency_id AND e.transdate = o.transdate) FROM oe o WHERE o.customer_id = ? @@ -1959,7 +1951,7 @@ sub retrieve_item { c3.new_chart_id AS expense_new_chart, date($transdate) - c3.valid_from AS expense_valid, - p.unit, p.assembly, p.bin, p.onhand, + p.unit, p.assembly, p.onhand, p.notes AS partnotes, p.notes AS longdescription, p.not_discountable, p.formel, p.payment_id AS part_payment_id, p.price_factor_id, p.weight, diff --git a/SL/InstanceConfiguration.pm b/SL/InstanceConfiguration.pm index 1e9c6a10f..d9b7129a7 100644 --- a/SL/InstanceConfiguration.pm +++ b/SL/InstanceConfiguration.pm @@ -15,9 +15,9 @@ sub init { $self->{data} = selectfirst_hashref_query($::form, $::form->get_standard_dbh, qq|SELECT * FROM defaults|); - my $curr = $self->{data}->{curr} || ''; - $curr =~ s/\s+//g; - $self->{currencies} = [ split m/:/, $curr ]; + #To get all currencies and the default currency: + ($self->{data}->{curr}) = selectrow_query($::form, $::form->get_standard_dbh, qq|SELECT name AS curr FROM currencies WHERE id = (SELECT currency_id FROM defaults)|); + $self->{currencies} = [ map { $_->{name} } selectall_hashref_query($::form, $::form->get_standard_dbh, qq|SELECT name FROM currencies ORDER BY id|) ]; return $self; } @@ -25,13 +25,13 @@ sub init { sub get_default_currency { my ($self) = @_; - return ($self->get_currencies)[0]; + return $self->{data}->{curr}; } sub get_currencies { my ($self) = @_; - return $self->{currencies} ? @{ $self->{currencies} } : (); + return @{ $self->{currencies} }; } sub get_accounting_method { @@ -144,6 +144,16 @@ sub get_purchase_delivery_order_show_delete { return $self->{data}->{purchase_delivery_order_show_delete}; } +sub get_default_warehouse_id { + my ($self) = @_; + return ($self->{data}->{warehouse_id}); +} + +sub get_default_bin_id { + my ($self) = @_; + return ($self->{data}->{bin_id}); +} + 1; __END__ @@ -256,6 +266,14 @@ corresponding record type (true or false). Returns the default behavior for showing the delete button for the corresponding record type (true or false). +=item C + +Returns the default warehouse_id + +=item C + +Returns the default bin_id + =back =head1 BUGS diff --git a/SL/Mailer.pm b/SL/Mailer.pm index 2857e1c75..f5b3b7764 100644 --- a/SL/Mailer.pm +++ b/SL/Mailer.pm @@ -83,16 +83,23 @@ sub _create_message_id { sub _create_address_headers { my ($self) = @_; + # $self->{addresses} collects the recipients for use in e.g. the + # SMTP 'RCPT TO:' envelope command. $self->{headers} collects the + # headers that make up the actual email. 'BCC' should not be + # included there for certain transportation methods (SMTP). + $self->{addresses} = {}; foreach my $item (qw(from to cc bcc)) { $self->{addresses}->{$item} = []; - next if !$self->{$item} || $self->{driver}->keep_from_header($item); + next if !$self->{$item}; my @header_addresses; foreach my $addr_obj (Email::Address->parse($self->{$item})) { push @{ $self->{addresses}->{$item} }, $addr_obj->address; + next if $self->{driver}->keep_from_header($item); + my $phrase = $addr_obj->phrase(); if ($phrase) { $phrase =~ s/^\"//; @@ -187,7 +194,7 @@ sub send { $self->{contenttype} ||= "text/plain"; $self->{headers} = [ Subject => $self->{subject}, - 'Message-ID' => $self->_create_message_id, + 'Message-ID' => '<' . $self->_create_message_id . '>', 'X-Mailer' => "kivitendo $self->{version}", ]; diff --git a/SL/MoreCommon.pm b/SL/MoreCommon.pm index dfc5a7d80..41cac3c08 100644 --- a/SL/MoreCommon.pm +++ b/SL/MoreCommon.pm @@ -3,7 +3,7 @@ package SL::MoreCommon; require Exporter; our @ISA = qw(Exporter); -our @EXPORT = qw(save_form restore_form compare_numbers any cross); +our @EXPORT = qw(save_form restore_form compare_numbers cross); our @EXPORT_OK = qw(ary_union ary_intersect ary_diff listify ary_to_hash uri_encode uri_decode); use List::MoreUtils qw(zip); @@ -76,15 +76,6 @@ sub compare_numbers { return $a <=> $b; } -sub any (&@) { - my $f = shift; - return if ! @_; - for (@_) { - return 1 if $f->(); - } - return 0; -} - sub cross(&\@\@) { my $op = shift; use vars qw/@A @B/; diff --git a/SL/OE.pm b/SL/OE.pm index 3d6b32837..0c0b76796 100644 --- a/SL/OE.pm +++ b/SL/OE.pm @@ -91,7 +91,7 @@ sub transactions { qq|JOIN $vc ct ON (o.${vc}_id = ct.id) | . qq|LEFT JOIN employee e ON (o.employee_id = e.id) | . qq|LEFT JOIN employee s ON (o.salesman_id = s.id) | . - qq|LEFT JOIN exchangerate ex ON (ex.curr = o.curr | . + qq|LEFT JOIN exchangerate ex ON (ex.currency_id = o.currency_id | . qq| AND ex.transdate = o.transdate) | . qq|LEFT JOIN project pr ON (o.globalproject_id = pr.id) | . qq|$periodic_invoices_joins | . @@ -311,7 +311,7 @@ sub save { $query = qq|SELECT nextval('id')|; ($form->{id}) = selectrow_query($form, $dbh, $query); - $query = qq|INSERT INTO oe (id, ordnumber, employee_id) VALUES (?, '', ?)|; + $query = qq|INSERT INTO oe (id, ordnumber, employee_id, currency_id) VALUES (?, '', ?, (SELECT currency_id FROM defaults))|; do_query($form, $dbh, $query, $form->{id}, $form->{employee_id}); } @@ -494,7 +494,7 @@ sub save { qq|UPDATE oe SET ordnumber = ?, quonumber = ?, cusordnumber = ?, transdate = ?, vendor_id = ?, customer_id = ?, amount = ?, netamount = ?, reqdate = ?, taxincluded = ?, - shippingpoint = ?, shipvia = ?, notes = ?, intnotes = ?, curr = ?, closed = ?, + shippingpoint = ?, shipvia = ?, notes = ?, intnotes = ?, currency_id = (SELECT id FROM currencies WHERE name=?), closed = ?, delivered = ?, proforma = ?, quotation = ?, department_id = ?, language_id = ?, taxzone_id = ?, shipto_id = ?, payment_id = ?, delivery_vendor_id = ?, delivery_customer_id = ?, globalproject_id = ?, employee_id = ?, salesman_id = ?, cp_id = ?, transaction_description = ?, marge_total = ?, marge_percent = ? @@ -506,7 +506,7 @@ sub save { $amount, $netamount, conv_date($reqdate), $form->{taxincluded} ? 't' : 'f', $form->{shippingpoint}, $form->{shipvia}, $form->{notes}, $form->{intnotes}, - substr($form->{currency}, 0, 3), $form->{closed} ? 't' : 'f', + $form->{currency}, $form->{closed} ? 't' : 'f', $form->{delivered} ? "t" : "f", $form->{proforma} ? 't' : 'f', $quotation, conv_i($form->{department_id}), conv_i($form->{language_id}), conv_i($form->{taxzone_id}), @@ -759,14 +759,13 @@ sub retrieve { (SELECT c.accno FROM chart c WHERE d.income_accno_id = c.id) AS income_accno, (SELECT c.accno FROM chart c WHERE d.expense_accno_id = c.id) AS expense_accno, (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id) AS fxgain_accno, - (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id) AS fxloss_accno, - d.curr AS currencies + (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id) AS fxloss_accno $query_add FROM defaults d|; my $ref = selectfirst_hashref_query($form, $dbh, $query); map { $form->{$_} = $ref->{$_} } keys %$ref; - ($form->{currency}) = split(/:/, $form->{currencies}) unless ($form->{currency}); + $form->{currency} = $form->get_default_currency($myconfig); # set reqdate if this is an invoice->order conversion. If someone knows a better check to ensure # we come from invoices, feel free. @@ -785,7 +784,7 @@ sub retrieve { $query = qq|SELECT o.cp_id, o.ordnumber, o.transdate, o.reqdate, o.taxincluded, o.shippingpoint, o.shipvia, o.notes, o.intnotes, - o.curr AS currency, e.name AS employee, o.employee_id, o.salesman_id, + (SELECT cu.name FROM currencies cu WHERE cu.id=o.currency_id) AS currency, e.name AS employee, o.employee_id, o.salesman_id, o.${vc}_id, cv.name AS ${vc}, o.amount AS invtotal, o.closed, o.reqdate, o.quonumber, o.department_id, o.cusordnumber, d.description AS department, o.payment_id, o.language_id, o.taxzone_id, @@ -807,9 +806,6 @@ sub retrieve { if ($ref) { map { $form->{$_} = $ref->{$_} } keys %$ref; - # remove any trailing whitespace - $form->{currency} =~ s/\s*$//; - $form->{saved_xyznumber} = $form->{$form->{type} =~ /_quotation$/ ? "quonumber" : "ordnumber"}; # set all entries for multiple ids blank that yield different information @@ -874,7 +870,7 @@ sub retrieve { c3.accno AS expense_accno, c3.new_chart_id AS expense_new_chart, date($transdate) - c3.valid_from as expense_valid, oe.ordnumber AS ordnumber_oe, oe.transdate AS transdate_oe, oe.cusordnumber AS cusordnumber_oe, p.partnumber, p.assembly, p.listprice, o.description, o.qty, - o.sellprice, o.parts_id AS id, o.unit, o.discount, p.bin, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id, + o.sellprice, o.parts_id AS id, o.unit, o.discount, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id, o.reqdate, o.project_id, o.serialnumber, o.ship, o.lastcost, o.ordnumber, o.transdate, o.cusordnumber, o.subtotal, o.longdescription, o.price_factor_id, o.price_factor, o.marge_price_factor, diff --git a/SL/Presenter/Tag.pm b/SL/Presenter/Tag.pm index 9ead2f24a..e5690e79d 100644 --- a/SL/Presenter/Tag.pm +++ b/SL/Presenter/Tag.pm @@ -19,6 +19,15 @@ sub _call_on { return $object->$method(@params); } +{ # This will give you an id for identifying html tags and such. + # It's guaranteed to be unique unless you exceed 10 mio calls per request. + # Do not use these id's to store information across requests. +my $_id_sequence = int rand 1e7; +sub _id { + return ( $_id_sequence = ($_id_sequence + 1) % 1e7 ); +} +} + sub stringify_attributes { my ($self, %params) = @_; @@ -68,6 +77,7 @@ sub man_days_tag { sub name_to_id { my ($self, $name) = @_; + $name =~ s/\[\+?\]/ _id() /ge; # give constructs with [] or [+] unique ids $name =~ s/[^\w_]/_/g; $name =~ s/_+/_/g; diff --git a/SL/RP.pm b/SL/RP.pm index 65cb30295..095f7581c 100644 --- a/SL/RP.pm +++ b/SL/RP.pm @@ -1238,7 +1238,7 @@ sub aging { "duedate", invoice, ${arap}.id, date_part('days', now() - duedate) as overduedays, (SELECT $buysell FROM exchangerate - WHERE (${arap}.curr = exchangerate.curr) + WHERE (${arap}.currency_id = exchangerate.currency_id) AND (exchangerate.transdate = ${arap}.transdate)) AS exchangerate FROM ${arap}, ${ct} WHERE ((paid != amount) OR (datepaid > (date $todate) AND datepaid is not null)) diff --git a/SL/Template/Plugin/L.pm b/SL/Template/Plugin/L.pm index 5ef07b75b..3e157a366 100644 --- a/SL/Template/Plugin/L.pm +++ b/SL/Template/Plugin/L.pm @@ -390,8 +390,10 @@ JAVASCRIPT my $filter = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })"; $filter .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })"; + my $params_js = $params{params} ? qq| + ($params{params})| : ''; + $stop_event = < on odd and C on even entries. +=item C + +An optional JavaScript string that is evaluated before sending the +POST request. The result must be a string that is appended to the URL. + =back Example: diff --git a/SL/User.pm b/SL/User.pm index 368a0a278..e11fda4ce 100644 --- a/SL/User.pm +++ b/SL/User.pm @@ -105,7 +105,7 @@ sub login { my %myconfig = $main::auth->read_user(login => $self->{login}); # check if database is down - my $dbh = SL::DBConnect->connect($myconfig{dbconnect}, $myconfig{dbuser}, $myconfig{dbpasswd}) + my $dbh = SL::DBConnect->connect($myconfig{dbconnect}, $myconfig{dbuser}, $myconfig{dbpasswd}, SL::DBConnect->get_options) or $self->error($DBI::errstr); # we got a connection, check the version @@ -245,7 +245,7 @@ sub dbsources { $form->{sid} = $form->{dbdefault}; &dbconnect_vars($form, $form->{dbdefault}); - my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) + my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}, SL::DBConnect->get_options) or $form->dberror; if ($form->{dbdriver} eq 'Pg') { @@ -262,7 +262,7 @@ sub dbsources { next if ($db =~ /^template/); &dbconnect_vars($form, $db); - my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) + my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}, SL::DBConnect->get_options) or $form->dberror; $query = @@ -317,7 +317,7 @@ sub dbclusterencoding { dbconnect_vars($form, $form->{dbdefault}); - my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) || $form->dberror(); + my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}, SL::DBConnect->get_options) || $form->dberror(); my $query = qq|SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = 'template0'|; my ($cluster_encoding) = $dbh->selectrow_array($query); $dbh->disconnect(); @@ -335,7 +335,7 @@ sub dbcreate { $form->{sid} = $form->{dbdefault}; &dbconnect_vars($form, $form->{dbdefault}); my $dbh = - SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) + SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}, SL::DBConnect->get_options) or $form->dberror; $form->{db} =~ s/\"//g; my %dbcreate = ( @@ -377,7 +377,7 @@ sub dbcreate { &dbconnect_vars($form, $form->{db}); - $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) + $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}, SL::DBConnect->get_options) or $form->dberror; my $db_charset = $Common::db_encoding_to_charset{$form->{encoding}}; @@ -398,6 +398,8 @@ sub dbcreate { do_query($form, $dbh, $query, $form->{profit_determination}); $query = "UPDATE defaults SET inventory_system = ?"; do_query($form, $dbh, $query, $form->{inventory_system}); + $query = "UPDATE defaults SET curr = ?"; + do_query($form, $dbh, $query, $form->{defaultcurrency}); $dbh->disconnect; @@ -414,7 +416,7 @@ sub dbdelete { $form->{sid} = $form->{dbdefault}; &dbconnect_vars($form, $form->{dbdefault}); - my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) + my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}, SL::DBConnect->get_options) or $form->dberror; my $query = $dbdelete{$form->{dbdriver}}; do_query($form, $dbh, $query); @@ -460,7 +462,7 @@ sub dbneedsupdate { map { $form->{$_} = $member->{$_} } qw(dbname dbuser dbpasswd dbhost dbport); dbconnect_vars($form, $form->{dbname}); - my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}); + my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}, SL::DBConnect->get_options); next unless $dbh; @@ -590,7 +592,7 @@ sub dbupdate { $db =~ s/^db//; &dbconnect_vars($form, $db); - my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) + my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}, SL::DBConnect->get_options) or $form->dberror; $dbh->do($form->{dboptions}) if ($form->{dboptions}); @@ -654,7 +656,7 @@ sub dbupdate2 { $db =~ s/^db//; &dbconnect_vars($form, $db); - my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) or $form->dberror; + my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}, SL::DBConnect->get_options) or $form->dberror; $dbh->do($form->{dboptions}) if ($form->{dboptions}); @@ -694,7 +696,7 @@ sub save_member { $main::auth->save_user($self->{login}, map { $_, $self->{$_} } config_vars()); - my $dbh = SL::DBConnect->connect($self->{dbconnect}, $self->{dbuser}, $self->{dbpasswd}); + my $dbh = SL::DBConnect->connect($self->{dbconnect}, $self->{dbuser}, $self->{dbpasswd}, SL::DBConnect->get_options); if ($dbh) { $self->create_employee_entry($::form, $dbh, $self, 1); $dbh->disconnect(); diff --git a/SL/WH.pm b/SL/WH.pm index 28e836e48..f7ca693e3 100644 --- a/SL/WH.pm +++ b/SL/WH.pm @@ -132,7 +132,13 @@ sub transfer { bin => $dst_bin->id, qty => $qty, )->save; - } + # Standardlagerplatz in Stammdaten gleich mitverschieben + if (defined($transfer->{change_default_bin})){ + my $part = SL::DB::Part->new(id => conv_i($transfer->{parts_id}))->load; + $part->update_attributes(warehouse_id => conv_i($transfer->{dst_warehouse_id})); + $part->update_attributes(bin_id => conv_i($transfer->{dst_bin_id})); + } + } push @trans_ids, $trans_id; } diff --git a/bin/mozilla/admin.pl b/bin/mozilla/admin.pl index 7f0b80de8..84c8b0699 100755 --- a/bin/mozilla/admin.pl +++ b/bin/mozilla/admin.pl @@ -780,6 +780,7 @@ sub dbcreate { my $locale = $main::locale; $form->isblank("db", $locale->text('Dataset missing!')); + $form->isblank("defaultcurrency", $locale->text('Default currency missing!')); User->dbcreate(\%$form); diff --git a/bin/mozilla/cp.pl b/bin/mozilla/cp.pl index a70c32eeb..b9aed484a 100644 --- a/bin/mozilla/cp.pl +++ b/bin/mozilla/cp.pl @@ -109,10 +109,9 @@ sub payment { # geben und hier reinparsen, oder besser multibox oder html auslagern? # Antwort: form->currency wird mit oldcurrency oder curr[0] überschrieben # Wofür macht das Sinn? - @curr = split(/:/, $form->{currencies}); - chomp $curr[0]; + @curr = $form->get_all_currencies(); $form->{defaultcurrency} = $form->{currency} = $form->{oldcurrency} = - $curr[0]; + $form->get_default_currency(\%myconfig); # Entsprechend präventiv die Auswahlliste für Währungen # auch mit value= zusammenbauen (s.a. oben bugfix 1771) diff --git a/bin/mozilla/ic.pl b/bin/mozilla/ic.pl index 222a4d1d5..04257ce97 100644 --- a/bin/mozilla/ic.pl +++ b/bin/mozilla/ic.pl @@ -1489,8 +1489,7 @@ sub link_part { IC->create_links("IC", \%myconfig, \%$form); # currencies - map({ $form->{selectcurrency} .= "