]> wagnertech.de Git - mfinanz.git/commitdiff
Merge branch 'von-opendynamic'
authorMoritz Bunkus <m.bunkus@linet-services.de>
Tue, 4 Mar 2014 12:18:38 +0000 (13:18 +0100)
committerMoritz Bunkus <m.bunkus@linet-services.de>
Tue, 4 Mar 2014 12:22:24 +0000 (13:22 +0100)
Conflicts:
SL/DB/MetaSetup/InvoiceItem.pm

29 files changed:
SL/DB/DeliveryOrder.pm
SL/DB/DeliveryOrderItem.pm
SL/DB/Helper/FlattenToForm.pm
SL/DB/Invoice.pm
SL/DB/InvoiceItem.pm
SL/DB/Manager/DeliveryOrder.pm
SL/DB/Manager/Invoice.pm
SL/DB/Manager/Order.pm
SL/DB/Manager/PurchaseInvoice.pm
SL/DB/MetaSetup/DeliveryOrderItem.pm
SL/DB/MetaSetup/InvoiceItem.pm
SL/DB/MetaSetup/OrderItem.pm
SL/DB/Order.pm
SL/DB/OrderItem.pm
SL/DB/PurchaseInvoice.pm
SL/DB/Unit.pm
SL/DO.pm
SL/Form.pm
SL/Helper/CreatePDF.pm [new file with mode: 0644]
SL/IS.pm
SL/Presenter.pm
SL/Template/Plugin/LxERP.pm
bin/mozilla/do.pl
bin/mozilla/io.pl
doc/dokumentation.xml
js/kivi.js
sql/Pg-upgrade2/invoice_add_donumber.sql [new file with mode: 0644]
sql/Pg-upgrade2/unit_foreign_key_for_line_items.sql [new file with mode: 0644]
templates/webpages/amcvar/render_inputs_block.html

index 372a5cc3a60df0cca375478ab6dc949378e68bb5..75eab64b0f6ff392aca394311a38a03f937ba163 100644 (file)
@@ -4,8 +4,11 @@ use strict;
 
 use Carp;
 
+use Rose::DB::Object::Helpers ();
+
 use SL::DB::MetaSetup::DeliveryOrder;
 use SL::DB::Manager::DeliveryOrder;
+use SL::DB::Helper::FlattenToForm;
 use SL::DB::Helper::LinkedRecords;
 use SL::DB::Helper::TransNumberGenerator;
 
@@ -16,6 +19,12 @@ __PACKAGE__->meta->add_relationship(orderitems => { type         => 'one to many
                                                     column_map   => { id => 'delivery_order_id' },
                                                     manager_args => { with_objects => [ 'part' ] }
                                                   },
+                                    custom_shipto => {
+                                      type        => 'one to one',
+                                      class       => 'SL::DB::Shipto',
+                                      column_map  => { id => 'trans_id' },
+                                      query_args  => [ module => 'DO' ],
+                                    },
                                    );
 
 __PACKAGE__->meta->initialize;
@@ -74,4 +83,189 @@ sub date {
   goto &transdate;
 }
 
+sub _clone_orderitem_cvar {
+  my ($cvar) = @_;
+
+  my $cloned = Rose::DB::Object::Helpers::clone_and_reset($_);
+  $cloned->sub_module('delivery_order_items');
+
+  return $cloned;
+}
+
+sub new_from {
+  my ($class, $source, %params) = @_;
+
+  croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) eq 'SL::DB::Order';
+
+  my ($item_parent_id_column, $item_parent_column);
+
+  if (ref($source) eq 'SL::DB::Order') {
+    $item_parent_id_column = 'trans_id';
+    $item_parent_column    = 'order';
+  }
+
+  my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_terms->terms_netto : 0;
+
+  my %args = ( map({ ( $_ => $source->$_ ) } qw(cp_id currency_id customer_id cusordnumber department_id employee_id globalproject_id intnotes language_id notes
+                                                ordnumber reqdate salesman_id shippingpoint shipvia taxincluded taxzone_id transaction_description vendor_id
+                                             )),
+               closed    => 0,
+               is_sales  => !!$source->customer_id,
+               delivered => 0,
+               terms     => $terms,
+               transdate => DateTime->today_local,
+            );
+
+  # Custom shipto addresses (the ones specific to the sales/purchase
+  # record and not to the customer/vendor) are only linked from
+  # shipto -> delivery_orders. Meaning delivery_orders.shipto_id
+  # will not be filled in that case. Therefore we have to return the
+  # new shipto object as a separate object so that the caller can
+  # save it, too.
+  my $custom_shipto;
+  if (!$source->shipto_id && $source->id) {
+    my $old = $source->custom_shipto;
+    if ($old) {
+      $custom_shipto = SL::DB::Shipto->new(
+        map  { +($_ => $old->$_) }
+        grep { !m{^ (?: itime | mtime | shipto_id | trans_id ) $}x }
+        map  { $_->name }
+        @{ $old->meta->columns }
+      );
+      $custom_shipto->module('DO');
+    }
+
+  } else {
+    $args{shipto_id} = $source->shipto_id;
+  }
+
+  my $delivery_order = $class->new(%args, %{ $params{attributes} || {} });
+  my $items          = delete($params{items}) || $source->items_sorted;
+  my %item_parents;
+
+  my @items = map {
+    my $source_item      = $_;
+    my $source_item_id   = $_->$item_parent_id_column;
+    my @custom_variables = map { _clone_orderitem_cvar($_) } @{ $source_item->custom_variables };
+
+    $item_parents{$source_item_id} ||= $source_item->$item_parent_column;
+    my $item_parent                  = $item_parents{$source_item_id};
+
+    SL::DB::DeliveryOrderItem->new(map({ ( $_ => $source_item->$_ ) }
+                                         qw(base_qty cusordnumber description discount lastcost longdescription marge_price_factor parts_id price_factor price_factor_id
+                                            project_id qty reqdate sellprice serialnumber transdate unit
+                                         )),
+                                   custom_variables => \@custom_variables,
+                                   ordnumber        => ref($item_parent) eq 'SL::DB::Order' ? $item_parent->ordnumber : $source_item->ordnumber,
+                                 );
+
+  } @{ $items };
+
+  @items = grep { $_->qty * 1 } @items if $params{skip_items_zero_qty};
+
+  $delivery_order->items(\@items);
+
+  return ($delivery_order, $custom_shipto);
+}
+
 1;
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::DB::DeliveryOrder - Rose model for delivery orders (table
+"delivery_orders")
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<date>
+
+An alias for C<transdate> for compatibility with other sales/purchase models.
+
+=item C<displayable_state>
+
+Returns a human-readable description of the state regarding being
+closed and delivered.
+
+=item C<items>
+
+An alias for C<deliver_orer_items> for compatibility with other
+sales/purchase models.
+
+=item C<items_sorted>
+
+Returns the delivery order items sorted by their ID (same order they
+appear in the frontend delivery order masks).
+
+=item C<new_from $source, %params>
+
+Creates a new C<SL::DB::DeliveryOrder> instance and copies as much
+information from C<$source> as possible. At the moment only instances
+of C<SL::DB::Order> (sales quotations, sales orders, requests for
+quotations and purchase orders) are supported as sources.
+
+The conversion copies order items into delivery order items. Dates are copied
+as appropriate, e.g. the C<transdate> field will be set to the current date.
+
+Returns one or two objects depending on the context. In list context
+the new delivery order instance and a shipto instance will be
+returned. In scalar instance only the delivery order instance is
+returned.
+
+Custom shipto addresses (the ones specific to the sales/purchase
+record and not to the customer/vendor) are only linked from C<shipto>
+to C<delivery_orders>. Meaning C<delivery_orders.shipto_id> will not
+be filled in that case. That's why a separate shipto object is created
+and returned.
+
+The objects returned are not saved.
+
+C<%params> can include the following options:
+
+=over 2
+
+=item C<items>
+
+An optional array reference of RDBO instances for the items to use. If
+missing then the method C<items_sorted> will be called on
+C<$source>. This option can be used to override the sorting, to
+exclude certain positions or to add additional ones.
+
+=item C<skip_items_zero_qty>
+
+If trueish then items with a quantity of 0 are skipped.
+
+=item C<attributes>
+
+An optional hash reference. If it exists then it is passed to C<new>
+allowing the caller to set certain attributes for the new delivery
+order.
+
+=back
+
+=item C<sales_order>
+
+TODO: Describe sales_order
+
+=item C<type>
+
+Returns a stringdescribing this record's type: either
+C<sales_delivery_order> or C<purchase_delivery_order>.
+
+=back
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut
index 29c85a2cb21c5a90e60d98e7a3d13c3d23e00376..638082ccf61d58cbd15a77c2ef444602c8213aba 100644 (file)
@@ -15,13 +15,6 @@ use SL::DB::Helper::CustomVariables (
 );
 
 __PACKAGE__->meta->make_manager_class;
-__PACKAGE__->meta->add_relationship(
-  unit_obj       => {
-    type         => 'many to one',
-    class        => 'SL::DB::Unit',
-    column_map   => { unit => 'name' },
-  },
-);
 
 __PACKAGE__->meta->initialize;
 
index 105cf020f9265c9d09f30ea897eaf0ca38f4648b..570724b64f1eab128b2da7351c47f785b3fe843b 100644 (file)
@@ -62,7 +62,7 @@ sub flatten_to_form {
     $form->{"partsgroup_${idx}"} = $item->part->partsgroup->partsgroup if _has($item->part, 'partsgroup_id');
     _copy($item->part,    $form, '',        "_${idx}", 0,               qw(id partnumber weight));
     _copy($item->part,    $form, '',        "_${idx}", $format_amounts, qw(listprice));
-    _copy($item,          $form, '',        "_${idx}", 0,               qw(description project_id ship serialnumber pricegroup_id ordnumber cusordnumber unit
+    _copy($item,          $form, '',        "_${idx}", 0,               qw(description project_id ship serialnumber pricegroup_id ordnumber donumber cusordnumber unit
                                                                            subtotal longdescription price_factor_id marge_price_factor approved_sellprice reqdate transdate));
     _copy($item,          $form, '',        "_${idx}", $format_amounts, qw(qty sellprice marge_total marge_percent lastcost));
     _copy($item,          $form, '',        "_${idx}", $format_notnull, qw(discount));
index 72844b4dbc0c25380160855d85d715a5f7bfec22..9cf52a24cf28f6817af31a7326f10ed845e589c4 100644 (file)
@@ -7,7 +7,8 @@ use strict;
 
 use Carp;
 use List::Util qw(first);
-use List::MoreUtils qw(pairwise);
+
+use Rose::DB::Object::Helpers ();
 
 use SL::DB::MetaSetup::Invoice;
 use SL::DB::Manager::Invoice;
@@ -16,7 +17,6 @@ use SL::DB::Helper::LinkedRecords;
 use SL::DB::Helper::PriceTaxCalculator;
 use SL::DB::Helper::PriceUpdater;
 use SL::DB::Helper::TransNumberGenerator;
-use SL::DB::CustomVariable;
 
 __PACKAGE__->meta->add_relationship(
   invoiceitems => {
@@ -38,6 +38,12 @@ __PACKAGE__->meta->add_relationship(
     column_map      => { id => 'ar_id' },
     manager_args    => { with_objects => [ 'sepa_export' ] }
   },
+  custom_shipto     => {
+    type            => 'one to one',
+    class           => 'SL::DB::Shipto',
+    column_map      => { id => 'trans_id' },
+    query_args      => [ module => 'AR' ],
+  },
 );
 
 __PACKAGE__->meta->initialize;
@@ -107,6 +113,15 @@ sub closed {
   return $self->paid >= $self->amount;
 }
 
+sub _clone_orderitem_delivery_order_item_cvar {
+  my ($cvar) = @_;
+
+  my $cloned = Rose::DB::Object::Helpers::clone_and_reset($_);
+  $cloned->sub_module('invoice');
+
+  return $cloned;
+}
+
 sub new_from {
   my ($class, $source, %params) = @_;
 
@@ -116,10 +131,24 @@ sub new_from {
   require SL::DB::Employee;
 
   my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_terms->terms_netto : 0;
+  my (@columns, @item_columns, $item_parent_id_column, $item_parent_column);
+
+  if (ref($source) eq 'SL::DB::Order') {
+    @columns      = qw(quonumber payment_id delivery_customer_id delivery_vendor_id);
+    @item_columns = qw(subtotal);
 
-  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 currency_id delivery_term_id)),
+    $item_parent_id_column = 'trans_id';
+    $item_parent_column    = 'order';
+
+  } else {
+    @columns      = qw(donumber);
+
+    $item_parent_id_column = 'delivery_order_id';
+    $item_parent_column    = 'delivery_order';
+  }
+
+  my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes salesman_id cusordnumber ordnumber department_id
+                                                cp_id language_id taxzone_id shipto_id globalproject_id transaction_description currency_id delivery_term_id), @columns),
                transdate   => DateTime->today_local,
                gldate      => DateTime->today_local,
                duedate     => DateTime->today_local->add(days => $terms * 1),
@@ -137,25 +166,31 @@ sub new_from {
     $args{quodate}      = $source->transdate;
   }
 
-  my $invoice = $class->new(%args, %params);
+  my $invoice = $class->new(%args, %{ $params{attributes} || {} });
+  my $items   = delete($params{items}) || $source->items_sorted;
+  my %item_parents;
 
   my @items = map {
-    my $source_item = $_;
+    my $source_item      = $_;
+    my $source_item_id   = $_->$item_parent_id_column;
+    my @custom_variables = map { _clone_orderitem_delivery_order_item_cvar($_) } @{ $source_item->custom_variables };
+
+    $item_parents{$source_item_id} ||= $source_item->$item_parent_column;
+    my $item_parent                  = $item_parents{$source_item_id};
+
     SL::DB::InvoiceItem->new(map({ ( $_ => $source_item->$_ ) }
-                                 qw(parts_id description qty sellprice discount project_id
-                                    serialnumber pricegroup_id ordnumber transdate cusordnumber unit
-                                    base_qty subtotal longdescription lastcost price_factor_id)),
-                            deliverydate => $source_item->reqdate,
-                            fxsellprice  => $source_item->sellprice,);
-  } @{ $source->items_sorted };
-
-  my $i = 0;
-  foreach my $item (@items) {
-    my $source_cvars = $source->items_sorted->[$i]->cvars_by_config;
-    my $target_cvars = $item->cvars_by_config;
-    pairwise { $a->value($b->value) } @{ $target_cvars }, @{ $source_cvars };
-    $i++;
-  }
+                                 qw(parts_id description qty sellprice discount project_id serialnumber pricegroup_id transdate cusordnumber unit
+                                    base_qty longdescription lastcost price_factor_id), @item_columns),
+                             deliverydate     => $source_item->reqdate,
+                             fxsellprice      => $source_item->sellprice,
+                             custom_variables => \@custom_variables,
+                             ordnumber        => ref($item_parent) eq 'SL::DB::Order'         ? $item_parent->ordnumber : $source_item->ordnumber,
+                             donumber         => ref($item_parent) eq 'SL::DB::DeliveryOrder' ? $item_parent->donumber  : $source_item->can('donumber') ? $source_item->donumber : '',
+                           );
+
+  } @{ $items };
+
+  @items = grep { $_->qty * 1 } @items if $params{skip_items_zero_qty};
 
   $invoice->invoiceitems(\@items);
 
@@ -294,7 +329,7 @@ SL::DB::Invoice: Rose model for invoices (table "ar")
 
 =over 4
 
-=item C<new_from $source>
+=item C<new_from $source, %params>
 
 Creates a new C<SL::DB::Invoice> instance and copies as much
 information from C<$source> as possible. At the moment only sales
@@ -304,6 +339,29 @@ The conversion copies order items into invoice items. Dates are copied
 as appropriate, e.g. the C<transdate> field from an order will be
 copied into the invoice's C<orddate> field.
 
+C<%params> can include the following options:
+
+=over 2
+
+=item C<items>
+
+An optional array reference of RDBO instances for the items to use. If
+missing then the method C<items_sorted> will be called on
+C<$source>. This option can be used to override the sorting, to
+exclude certain positions or to add additional ones.
+
+=item C<skip_items_zero_qty>
+
+If trueish then items with a quantity of 0 are skipped.
+
+=item C<attributes>
+
+An optional hash reference. If it exists then it is passed to C<new>
+allowing the caller to set certain attributes for the new delivery
+order.
+
+=back
+
 Amounts, prices and taxes are not
 calculated. L<SL::DB::Helper::PriceTaxCalculator::calculate_prices_and_taxes>
 can be used for this.
index 8f9a4dde7bae629beb2df365c38f0786c7a0a793..4374a6cdc9973bd0210bb18467fa3177a414f8b9 100644 (file)
@@ -14,16 +14,21 @@ use SL::DB::Helper::CustomVariables (
   },
 );
 
-__PACKAGE__->meta->add_relationship(
-  unit_obj       => {
-    type         => 'many to one',
-    class        => 'SL::DB::Unit',
-    column_map   => { unit => 'name' },
+__PACKAGE__->meta->make_manager_class;
+
+__PACKAGE__->meta->add_relationships(
+  invoice          => {
+    type           => 'one to one',
+    class          => 'SL::DB::Invoice',
+    column_map     => { trans_id => 'id' },
   },
-);
 
-# Creates get_all, get_all_count, get_all_iterator, delete_all and update_all.
-__PACKAGE__->meta->make_manager_class;
+  purchase_invoice => {
+    type           => 'one to one',
+    class          => 'SL::DB::PurchaseInvoice',
+    column_map     => { trans_id => 'id' },
+  },
+);
 
 __PACKAGE__->meta->initialize;
 
index fa67f6675780771dd660ca322d9f5631e57ea594..793e88bf76b0e8ea31538c1bbf15cc7d29ed6829 100644 (file)
@@ -32,8 +32,8 @@ sub _sort_spec {
     },
     columns                   => {
       SIMPLE                  => 'ALL',
-      customer                => 'customer.name',
-      vendor                  => 'vendor.name',
+      customer                => 'lower(customer.name)',
+      vendor                  => 'lower(vendor.name)',
       globalprojectnumber     => 'lower(globalproject.projectnumber)',
 
       # Bug in Rose::DB::Object: the next should be
index 493789e84a39461690f468b1c8dacfdc6feb5f08..03c2296da49d11553215a845d3064e795a74fa67 100644 (file)
@@ -34,7 +34,7 @@ sub _sort_spec {
     },
     columns                   => {
       SIMPLE                  => 'ALL',
-      customer                => 'customer.name',
+      customer                => 'lower(customer.name)',
       globalprojectnumber     => 'lower(globalproject.projectnumber)',
 
       # Bug in Rose::DB::Object: the next should be
index e03386d0441beba4a0913db38d79d2bf6cc3acb9..d844683ece9629297059e9cab4c9a1852b3596d0 100644 (file)
@@ -35,8 +35,8 @@ sub _sort_spec {
     },
     columns                   => {
       SIMPLE                  => 'ALL',
-      customer                => 'customer.name',
-      vendor                  => 'vendor.name',
+      customer                => 'lower(customer.name)',
+      vendor                  => 'lower(vendor.name)',
       globalprojectnumber     => 'lower(globalproject.projectnumber)',
 
       # Bug in Rose::DB::Object: the next should be
index 2dd3fad178ffd8c3e5680d1db4a34e47e009eed0..ca0bb106eff23e82906b6d20988108f14b1e9bf3 100644 (file)
@@ -32,7 +32,7 @@ sub _sort_spec {
     },
     columns                   => {
       SIMPLE                  => 'ALL',
-      vendor                  => 'vendor.name',
+      vendor                  => 'lower(vendor.name)',
       globalprojectnumber     => 'lower(globalproject.projectnumber)',
 
       # Bug in Rose::DB::Object: the next should be
index 00bce88867214e00235bc4556fb2d517a3a8d2a2..76c890abcbc91c41ca77107436644992059de650 100644 (file)
@@ -63,6 +63,11 @@ __PACKAGE__->meta->foreign_keys(
     class       => 'SL::DB::Project',
     key_columns => { project_id => 'id' },
   },
+
+  unit_obj => {
+    class       => 'SL::DB::Unit',
+    key_columns => { unit => 'name' },
+  },
 );
 
 1;
index e788b5f6c0c67bcd38cb6ece927c5d03c85d03d7..7cd853ec23e243941bdf7a6a955078b9a806a970 100644 (file)
@@ -16,6 +16,7 @@ __PACKAGE__->meta->columns(
   deliverydate       => { type => 'date' },
   description        => { type => 'text' },
   discount           => { type => 'float', scale => 4 },
+  donumber           => { type => 'text' },
   fxsellprice        => { type => 'numeric', precision => 15, scale => 5 },
   id                 => { type => 'integer', not_null => 1, sequence => 'invoiceid' },
   itime              => { type => 'timestamp', default => 'now()' },
@@ -64,6 +65,11 @@ __PACKAGE__->meta->foreign_keys(
     class       => 'SL::DB::Project',
     key_columns => { project_id => 'id' },
   },
+
+  unit_obj => {
+    class       => 'SL::DB::Unit',
+    key_columns => { unit => 'name' },
+  },
 );
 
 1;
index 7a30dc8ee40eb0d39d80446f012b466ae24927e6..85bf5bee6d8fa100eeb3d8f011993465881e0259 100644 (file)
@@ -67,6 +67,11 @@ __PACKAGE__->meta->foreign_keys(
     class       => 'SL::DB::Project',
     key_columns => { project_id => 'id' },
   },
+
+  unit_obj => {
+    class       => 'SL::DB::Unit',
+    key_columns => { unit => 'name' },
+  },
 );
 
 1;
index 194431dcd31fea66da7143f9216daf49830fc072..a605a36892c6b8a473c337320da43f0e32ba45db 100644 (file)
@@ -30,6 +30,12 @@ __PACKAGE__->meta->add_relationship(
     class                  => 'SL::DB::PeriodicInvoicesConfig',
     column_map             => { id => 'oe_id' },
   },
+  custom_shipto            => {
+    type                   => 'one to one',
+    class                  => 'SL::DB::Shipto',
+    column_map             => { id => 'trans_id' },
+    query_args             => [ module => 'OE' ],
+  },
 );
 
 __PACKAGE__->meta->initialize;
@@ -130,11 +136,12 @@ sub convert_to_invoice {
   croak("Conversion to invoices is only supported for sales records") unless $self->customer_id;
 
   my $invoice;
-  if (!$self->db->do_transaction(sub {
+  if (!$self->db->with_transaction(sub {
+    require SL::DB::Invoice;
     $invoice = SL::DB::Invoice->new_from($self)->post(%params) || die;
     $self->link_to_record($invoice);
     $self->update_attributes(closed => 1);
-    # die;
+    1;
   })) {
     return undef;
   }
@@ -142,6 +149,25 @@ sub convert_to_invoice {
   return $invoice;
 }
 
+sub convert_to_delivery_order {
+  my ($self, @args) = @_;
+
+  my ($delivery_order, $custom_shipto);
+  if (!$self->db->with_transaction(sub {
+    require SL::DB::DeliveryOrder;
+    ($delivery_order, $custom_shipto) = SL::DB::DeliveryOrder->new_from($self, @args);
+    $delivery_order->save;
+    $custom_shipto->save if $custom_shipto;
+    $self->link_to_record($delivery_order);
+    $self->update_attributes(delivered => 1);
+    1;
+  })) {
+    return wantarray ? () : undef;
+  }
+
+  return wantarray ? ($delivery_order, $custom_shipto) : $delivery_order;
+}
+
 sub number {
   my $self = shift;
 
@@ -189,6 +215,28 @@ Returns one of the following string types:
 
 Returns true if the order is of the given type.
 
+=head2 C<convert_to_delivery_order %params>
+
+Creates a new delivery order with C<$self> as the basis by calling
+L<SL::DB::DeliveryOrder::new_from>. That delivery order is saved, and
+C<$self> is linked to the new invoice via
+L<SL::DB::RecordLink>. C<$self>'s C<delivered> attribute is set to
+C<true>, and C<$self> is saved.
+
+The arguments in C<%params> are passed to
+L<SL::DB::DeliveryOrder::new_from>.
+
+Returns C<undef> on failure. Otherwise the return value depends on the
+context. In list context the new delivery order and a shipto instance
+will be returned. In scalar instance only the delivery order instance
+is returned.
+
+Custom shipto addresses (the ones specific to the sales/purchase
+record and not to the customer/vendor) are only linked from C<shipto>
+to C<delivery_orders>. Meaning C<delivery_orders.shipto_id> will not
+be filled in that case. That's why a separate shipto object is created
+and returned.
+
 =head2 C<convert_to_invoice %params>
 
 Creates a new invoice with C<$self> as the basis by calling
index 37520977bfc9ca486e049d91b269efb54083287d..e75c78f5ffcbddf28191e006af23c819f777439e 100644 (file)
@@ -17,14 +17,6 @@ use SL::DB::Helper::CustomVariables (
   },
 );
 
-__PACKAGE__->meta->add_relationship(
-  unit_obj       => {
-    type         => 'many to one',
-    class        => 'SL::DB::Unit',
-    column_map   => { unit => 'name' },
-  },
-);
-
 __PACKAGE__->meta->initialize;
 
 sub is_price_update_available {
index 61d93558bbed0cea3aa4b52bacf24a36cc63e961..2457cdefb858a318e43d022d44be77fe5e711f67 100644 (file)
@@ -23,6 +23,12 @@ __PACKAGE__->meta->add_relationship(
     column_map      => { id => 'ap_id' },
     manager_args    => { with_objects => [ 'sepa_export' ] }
   },
+  custom_shipto     => {
+    type            => 'one to one',
+    class           => 'SL::DB::Shipto',
+    column_map      => { id => 'trans_id' },
+    query_args      => [ module => 'AP' ],
+  },
 );
 
 __PACKAGE__->meta->initialize;
index 927e31b2eef87a7a3cb40f26a46ac1ca6e7d7e2c..b133d8f131263cf39e3dea9c4b7da4827cf97c7b 100644 (file)
@@ -27,10 +27,12 @@ sub unit_class {
 
 sub convertible_units {
   my $self = shift;
+  my $all_units = scalar(@_) && (ref($_[0]) eq 'ARRAY') ? $_[0] : \@_;
+  $all_units    = SL::DB::Manager::Unit->get_all if !@{ $all_units };
   return [
     sort { $a->sortkey <=> $b->sortkey }
     grep { $_->unit_class->name eq $self->unit_class->name }
-    @{ SL::DB::Manager::Unit->get_all }
+    @{ $all_units }
   ];
 }
 
index 31b01c4245b482ae93638191e063f8a849d2ac59..a0087dee0adb84d1130ec839592bd5f3314e5a60 100644 (file)
--- a/SL/DO.pm
+++ b/SL/DO.pm
@@ -680,7 +680,7 @@ sub retrieve {
          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,
-         pr.projectnumber, dord.transdate AS dord_transdate,
+         pr.projectnumber, dord.transdate AS dord_transdate, dord.donumber,
          pg.partsgroup
        FROM delivery_order_items doi
        JOIN parts p ON (doi.parts_id = p.id)
index 21817242701799503cb2e31a37aca46910a2977c..875b726c151cfcc1697f1b89ed28719a41fedf34 100644 (file)
@@ -673,6 +673,7 @@ sub init_template {
      'COMPILE_EXT'  => '.tcc',
      'COMPILE_DIR'  => $::lx_office_conf{paths}->{userspath} . '/templates-cache',
      'ERROR'        => 'templates/webpages/generic/exception.html',
+     'ENCODING'     => 'utf8',
   })) || die;
 }
 
@@ -3326,6 +3327,13 @@ sub prepare_for_printing {
   # compatibility.
   $self->{$_} = $defaults->$_ for qw(company address taxnumber co_ustid duns sepa_creditor_id);
 
+  $self->{"myconfig_${_}"} = $::myconfig{$_} for grep { $_ ne 'dbpasswd' } keys %::myconfig;
+
+  if (!$self->{employee_id}) {
+    $self->{"employee_${_}"} = $::myconfig{$_} for qw(email tel fax name signature);
+    $self->{"employee_${_}"} = $defaults->$_   for qw(address businessnumber co_ustid company duns sepa_creditor_id taxnumber);
+  }
+
   # set shipto from billto unless set
   my $has_shipto = any { $self->{"shipto$_"} } qw(name street zipcode city country contact);
   if (!$has_shipto && ($self->{type} =~ m/^(?:purchase_order|request_quotation)$/)) {
@@ -3344,6 +3352,10 @@ sub prepare_for_printing {
     $output_longdates    = 1;
   }
 
+  $self->{myconfig_output_dateformat}   = $output_dateformat;
+  $self->{myconfig_output_longdates}    = $output_longdates;
+  $self->{myconfig_output_numberformat} = $output_numberformat;
+
   # Retrieve accounts for tax calculation.
   IC->retrieve_accounts(\%::myconfig, $self, map { $_ => $self->{"id_$_"} } 1 .. $self->{rowcount});
 
diff --git a/SL/Helper/CreatePDF.pm b/SL/Helper/CreatePDF.pm
new file mode 100644 (file)
index 0000000..8d36eb7
--- /dev/null
@@ -0,0 +1,287 @@
+package SL::Helper::CreatePDF;
+
+use strict;
+
+use Carp;
+use Cwd;
+use English qw(-no_match_vars);
+use File::Slurp ();
+use File::Temp ();
+use List::MoreUtils qw(uniq);
+use List::Util qw(first);
+use String::ShellQuote ();
+
+use SL::Form;
+use SL::Common;
+use SL::DB::Language;
+use SL::DB::Printer;
+use SL::MoreCommon;
+use SL::Template;
+use SL::Template::LaTeX;
+
+use Exporter 'import';
+our @EXPORT_OK = qw(create_pdf merge_pdfs find_template);
+our %EXPORT_TAGS = (
+  all => \@EXPORT_OK,
+);
+
+sub create_pdf {
+  my ($class, %params) = @_;
+
+  my $userspath       = $::lx_office_conf{paths}->{userspath};
+  my $form            = Form->new('');
+  $form->{format}     = 'pdf';
+  $form->{cwd}        = getcwd();
+  $form->{templates}  = $::instance_conf->get_templates;
+  $form->{IN}         = $params{template};
+  $form->{tmpdir}     = $form->{cwd} . '/' . $userspath;
+  my ($suffix)        = $params{template} =~ m{\.(.+)};
+
+  my $vars            = $params{variables} || {};
+  $form->{$_}         = $vars->{$_} for keys %{ $vars };
+
+  my $temp_fh;
+  ($temp_fh, $form->{tmpfile}) = File::Temp::tempfile(
+    'kivitendo-printXXXXXX',
+    SUFFIX => ".${suffix}",
+    DIR    => $userspath,
+    UNLINK => ($::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files})? 0 : 1,
+  );
+
+  my $parser = SL::Template::LaTeX->new(
+    $form->{IN},
+    $form,
+    \%::myconfig,
+    $userspath,
+  );
+
+  my $result = $parser->parse($temp_fh);
+
+  close $temp_fh;
+  chdir $form->{cwd};
+
+  if (!$result) {
+    $form->cleanup;
+    die $parser->get_error;
+  }
+
+  if (($params{return} || 'content') eq 'file_name') {
+    my $new_name = $userspath . '/keep-' . $form->{tmpfile};
+    rename $userspath . '/' . $form->{tmpfile}, $new_name;
+
+    $form->cleanup;
+
+    return $new_name;
+  }
+
+  my $pdf = File::Slurp::read_file($userspath . '/' . $form->{tmpfile});
+
+  $form->cleanup;
+
+  return $pdf;
+}
+
+sub merge_pdfs {
+  my ($class, %params) = @_;
+
+  return scalar(File::Slurp::read_file($params{file_names}->[0])) if scalar(@{ $params{file_names} }) < 2;
+
+  my ($temp_fh, $temp_name) = File::Temp::tempfile(
+    'kivitendo-printXXXXXX',
+    SUFFIX => '.pdf',
+    DIR    => $::lx_office_conf{paths}->{userspath},
+    UNLINK => ($::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files})? 0 : 1,
+  );
+  close $temp_fh;
+
+  my $input_names = join ' ', String::ShellQuote::shell_quote(@{ $params{file_names} });
+  my $exe         = $::lx_office_conf{applications}->{ghostscript} || 'gs';
+  my $output      = `$exe -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile=${temp_name} ${input_names} 2>&1`;
+
+  die "Executing gs failed: $ERRNO" if !defined $output;
+  die $output                       if $? != 0;
+
+  return scalar File::Slurp::read_file($temp_name);
+}
+
+sub find_template {
+  my ($class, %params) = @_;
+
+  $params{name} or croak "Missing parameter 'name'";
+
+  my $path                 = $::instance_conf->get_templates;
+  my $extension            = $params{extension} || "tex";
+  my ($printer, $language) = ('', '');
+
+  if ($params{printer} || $params{printer_id}) {
+    if ($params{printer} && !ref $params{printer}) {
+      $printer = '_' . $params{printer};
+    } else {
+      $printer = $params{printer} || SL::DB::Printer->new(id => $params{printer_id})->load;
+      $printer = $printer->template_code ? '_' . $printer->template_code : '';
+    }
+  }
+
+  if ($params{language} || $params{language_id}) {
+    if ($params{language} && !ref $params{language}) {
+      $language = '_' . $params{language};
+    } else {
+      $language = $params{language} || SL::DB::Language->new(id => $params{language_id})->load;
+      $language = $language->template_code ? '_' . $language->template_code : '';
+    }
+  }
+
+  my @template_files = (
+    $params{name} . "${language}${printer}",
+    $params{name} . "${language}",
+    $params{name},
+    "default",
+  );
+
+  if ($params{email}) {
+    unshift @template_files, (
+      $params{name} . "_email${language}${printer}",
+      $params{name} . "_email${language}",
+    );
+  }
+
+  @template_files = map { "${_}.${extension}" } uniq grep { $_ } @template_files;
+
+  my $template = first { -f ($path . "/$_") } @template_files;
+
+  return wantarray ? ($template, @template_files) : $template;
+}
+
+1;
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::Helper::CreatePDF - A helper for creating PDFs from template files
+
+=head1 SYNOPSIS
+
+  # Retrieve a sales order from the database and create a PDF for
+  # it:
+  my $order               = SL::DB::Order->new(id => …)->load;
+  my $print_form          = Form->new('');
+  $print_form->{type}     = 'invoice';
+  $print_form->{formname} = 'invoice',
+  $print_form->{format}   = 'pdf',
+  $print_form->{media}    = 'file';
+
+  $order->flatten_to_form($print_form, format_amounts => 1);
+  $print_form->prepare_for_printing;
+
+  my $pdf = SL::Helper::CreatePDF->create_pdf(
+    template  => 'sales_order',
+    variables => $print_form,
+  );
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<create_pdf %params>
+
+Parses a LaTeX template file, creates a PDF for it and returns either
+its content or its file name. The recognized parameters are:
+
+=over 2
+
+=item * C<template> – mandatory. The template file name relative to
+the users' templates directory. Must be an existing file name,
+e.g. one retrieved by L</find_template>.
+
+=item * C<variables> – optional hash reference containing variables
+available to the template.
+
+=item * C<return> – optional scalar containing either C<content> (the
+default) or C<file_name>. If it is set to C<file_name> then the file
+name of the temporary file containing the PDF is returned, and the
+caller is responsible for deleting it. Otherwise a scalar containing
+the PDF itself is returned and all temporary files have already been
+deleted by L</create_pdf>.
+
+=back
+
+=item C<find_template %params>
+
+Searches the user's templates directory for a template file name to
+use. The file names considered depend on the parameters; they can
+contain a template base name and suffixes for email, language and
+printers. As a fallback the name C<default.$extension> is also
+considered.
+
+The return value depends on the context. In scalar context the
+template file name that matches the given parameters is returned. It's
+a file name relative to the user's templates directory. If no template
+file is found then C<undef> is returned.
+
+In list context the first element is the same value as in scalar
+context. Additionally a list of considered template file names is
+returned.
+
+The recognized parameters are:
+
+=over 2
+
+=item * C<name> – mandatory. The template's file name basis
+without any additional suffix or extension, e.g. C<sales_quotation>.
+
+=item * C<extension> – optional file name extension to use without the
+dot. Defaults to C<tex>.
+
+=item * C<email> – optional flag indicating whether or not the
+template is to be sent via email. If set to true then template file
+names containing C<_email> are considered as well.
+
+=item * C<language> and C<language_id> – optional parameters
+indicating the language to be used. C<language> can be either a string
+containing the language code to use or an instance of
+C<SL::DB::Language>. C<language_id> can contain the ID of the
+C<SL::DB:Language> instance to load and use. If given template file
+names containing C<_language_template_code> are considered as well.
+
+=item * C<printer> and C<printer_id> – optional parameters indicating
+the printer to be used. C<printer> can be either a string containing
+the printer code to use or an instance of
+C<SL::DB::Printer>. C<printer_id> can contain the ID of the
+C<SL::DB:Printer> instance to load and use. If given template file
+names containing C<_printer_template_code> are considered as well.
+
+=back
+
+=item C<merge_pdfs %params>
+
+Merges two or more PDFs into a single PDF by using the external
+application ghostscript.
+
+The recognized parameters are:
+
+=over 2
+
+=item * C<file_names> – mandatory array reference containing the file
+names to merge.
+
+=back
+
+Note that this function relies on the presence of the external
+application ghostscript. The executable to use is configured via
+kivitendo's configuration file setting C<application.ghostscript>.
+
+=back
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut
index 9470f1a9a10689c3652013738e72a2b16f75dee8..c5fd8821f4e1a98c8a499a1c660edce2f748803e 100644 (file)
--- a/SL/IS.pm
+++ b/SL/IS.pm
@@ -149,7 +149,7 @@ sub invoice_details {
 
   my @arrays =
     qw(runningnumber number description longdescription qty ship unit bin
-       deliverydate_oe ordnumber_oe transdate_oe validuntil
+       deliverydate_oe ordnumber_oe donumber_do transdate_oe validuntil
        partnotes serialnumber reqdate sellprice listprice netprice
        discount p_discount discount_sub nodiscount_sub
        linetotal  nodiscount_linetotal tax_rate projectnumber projectdescription
@@ -210,6 +210,7 @@ sub invoice_details {
       push @{ $form->{TEMPLATE_ARRAYS}->{sellprice} },         $form->{"sellprice_$i"};
       push @{ $form->{TEMPLATE_ARRAYS}->{sellprice_nofmt} },   $form->parse_amount($myconfig, $form->{"sellprice_$i"});
       push @{ $form->{TEMPLATE_ARRAYS}->{ordnumber_oe} },      $form->{"ordnumber_$i"};
+      push @{ $form->{TEMPLATE_ARRAYS}->{donumber_do} },       $form->{"donumber_$i"};
       push @{ $form->{TEMPLATE_ARRAYS}->{transdate_oe} },      $form->{"transdate_$i"};
       push @{ $form->{TEMPLATE_ARRAYS}->{invnumber} },         $form->{"invnumber"};
       push @{ $form->{TEMPLATE_ARRAYS}->{invdate} },           $form->{"invdate"};
@@ -741,10 +742,10 @@ sub post_invoice {
         qq|INSERT INTO invoice (id, trans_id, parts_id, description, longdescription, qty,
                                 sellprice, fxsellprice, discount, allocated, assemblyitem,
                                 unit, deliverydate, project_id, serialnumber, pricegroup_id,
-                                ordnumber, transdate, cusordnumber, base_qty, subtotal,
+                                ordnumber, donumber, transdate, cusordnumber, base_qty, subtotal,
                                 marge_percent, marge_total, lastcost,
                                 price_factor_id, price_factor, marge_price_factor)
-           VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
+           VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
                    (SELECT factor FROM price_factors WHERE id = ?), ?)|;
 
       @values = ($invoice_id, conv_i($form->{id}), conv_i($form->{"id_$i"}),
@@ -753,7 +754,7 @@ sub post_invoice {
                  $form->{"discount_$i"}, $allocated, 'f',
                  $form->{"unit_$i"}, conv_date($form->{"reqdate_$i"}), conv_i($form->{"project_id_$i"}),
                  $form->{"serialnumber_$i"}, $pricegroup_id,
-                 $form->{"ordnumber_$i"}, conv_date($form->{"transdate_$i"}),
+                 $form->{"ordnumber_$i"}, $form->{"donumber_$i"}, conv_date($form->{"transdate_$i"}),
                  $form->{"cusordnumber_$i"}, $baseqty, $form->{"subtotal_$i"} ? 't' : 'f',
                  $form->{"marge_percent_$i"}, $form->{"marge_absolut_$i"},
                  $form->{"lastcost_$i"},
@@ -1652,7 +1653,7 @@ sub retrieve_invoice {
 
            i.id AS invoice_id,
            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.project_id, i.serialnumber, i.id AS invoice_pos, i.pricegroup_id, i.ordnumber, i.donumber, i.transdate, i.cusordnumber, i.subtotal, i.lastcost,
            i.price_factor_id, i.price_factor, i.marge_price_factor,
            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
index 36186c4ecb035cae164a0ffd3acd2ff5235535c6..23b8ad4728ec58c789f8436abb94c8eeabdcc487 100644 (file)
@@ -116,6 +116,7 @@ sub get_template {
                     COMPILE_EXT  => '.tcc',
                     COMPILE_DIR  => $::lx_office_conf{paths}->{userspath} . '/templates-cache',
                     ERROR        => 'templates/webpages/generic/exception.html',
+                    ENCODING     => 'utf8',
                   }) || croak;
 
   return $self->{template};
index 40bf195f205f2569b422ed92448fcf621e6c3f7b..f935a59108d5fabe5cfcaa8faaebf21f8617285f 100644 (file)
@@ -1,6 +1,7 @@
 package SL::Template::Plugin::LxERP;
 
 use base qw( Template::Plugin );
+use Scalar::Util qw();
 use Template::Plugin;
 
 use List::Util qw(min);
@@ -16,6 +17,16 @@ sub new {
   bless { }, $class;
 }
 
+sub is_rdbo {
+  my ($self, $obj, $wanted_class) = @_;
+
+  $wanted_class = !$wanted_class         ? 'Rose::DB::Object'
+                : $wanted_class =~ m{::} ? $wanted_class
+                :                          "SL::DB::${wanted_class}";
+
+  return Scalar::Util::blessed($obj) ? $obj->isa($wanted_class) : 0;
+}
+
 sub format_amount {
   my ($self, $var, $places, $skip_zero, $dash) = @_;
 
index e946e4e4b6573c27744a7b07b208a96288d3eb9b..06b8f65524b87751149f29aadbb5e6801d7ba0c1 100644 (file)
@@ -775,6 +775,7 @@ sub invoice {
       }
     }
     map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice listprice lastcost basefactor);
+    $form->{"donumber_$i"} = $form->{donumber};
   }
 
   $form->{type} = "invoice";
index e10389171a949a1a00e0a38ffdf5c135e17d7b16..d47168bc476155e28d4a5302c0e3cc2add2203f8 100644 (file)
@@ -50,6 +50,7 @@ use SL::IO;
 use SL::DB::Default;
 use SL::DB::Language;
 use SL::DB::Printer;
+use SL::Helper::CreatePDF;
 use SL::Helper::Flash;
 
 require "bin/mozilla/common.pl";
@@ -436,7 +437,7 @@ sub display_row {
           $cgi->hidden("-name" => "price_new_$i", "-value" => $form->format_amount(\%myconfig, $form->{"price_new_$i"})),
           map { ($cgi->hidden("-name" => $_, "-id" => $_, "-value" => $form->{$_})); } map { $_."_$i" }
             (qw(orderitems_id bo pricegroup_old price_old id inventory_accno bin partsgroup partnotes
-                income_accno expense_accno listprice assembly taxaccounts ordnumber transdate cusordnumber
+                income_accno expense_accno listprice assembly taxaccounts ordnumber donumber transdate cusordnumber
                 longdescription basefactor marge_absolut marge_percent marge_price_factor weight), @hidden_vars)
     );
 
@@ -749,7 +750,7 @@ sub remove_emptied_rows {
                 taxaccounts bin assembly weight projectnumber project_id
                 oldprojectnumber runningnumber serialnumber partsgroup payment_id
                 not_discountable shop ve gv buchungsgruppen_id language_values
-                sellprice_pg pricegroup_old price_old price_new unit_old ordnumber
+                sellprice_pg pricegroup_old price_old price_new unit_old ordnumber donumber
                 transdate longdescription basefactor marge_total marge_percent
                 marge_price_factor lastcost price_factor_id partnotes
                 stock_out stock_in has_sernumber reqdate);
@@ -1505,18 +1506,20 @@ sub print_form {
   }
 
   # search for the template
-  my @template_files;
-  push @template_files, "$form->{formname}_email$form->{language}$printer_code.$extension" if $form->{media} eq 'email';
-  push @template_files, "$form->{formname}$form->{language}$printer_code.$extension";
-  push @template_files, "$form->{formname}.$extension";
-  push @template_files, "default.$extension";
-  @template_files = uniq @template_files;
-  $form->{IN}     = first { -f ($defaults->templates . "/$_") } @template_files;
-
-  if (!defined $form->{IN}) {
+  my ($template_file, @template_files) = SL::Helper::CreatePDF->find_template(
+    name        => $form->{formname},
+    email       => $form->{media} eq 'email',
+    language_id => $form->{language_id},
+    printer_id  => $form->{printer_id},
+    extension   => $extension,
+  );
+
+  if (!defined $template_file) {
     $::form->error($::locale->text('Cannot find matching template for this print request. Please contact your template maintainer. I tried these: #1.', join ', ', map { "'$_'"} @template_files));
   }
 
+  $form->{IN} = $template_file;
+
   delete $form->{OUT};
 
   if ($form->{media} eq 'printer') {
index 0f6fca557776ae2946af5196e6cc45b8850826cd..aeeb56fcef69655ba00ab4dda33d1db32f683127 100644 (file)
@@ -3777,6 +3777,15 @@ ln -s $(pwd)/kivitendo-task-server.service /etc/systemd/system/</programlisting>
               </listitem>
             </varlistentry>
 
+            <varlistentry>
+              <term><varname>donumber_do</varname></term>
+
+              <listitem>
+                <para>Lieferscheinnummer desjenigen Lieferscheins, aus dem die Position stammt, sofern die Rechnung aus einem oder
+                mehreren Lieferscheinen erstellt wurde</para>
+              </listitem>
+            </varlistentry>
+
             <varlistentry>
               <term><varname>p_discount</varname></term>
 
index 81a978fc1e06f5baa52da1aff26de054736fd1f6..ac008725a8f714e8b1fd0b875363d15677cf064e 100644 (file)
@@ -36,6 +36,10 @@ namespace("kivi", function(ns) {
       ns.run_once_for('input.part_autocomplete', 'part_picker', function(elt) {
         kivi.PartPicker($(elt));
       });
+
+    var func = kivi.get_function_by_name('local_reinit_widgets');
+    if (func)
+      func();
   };
 
   ns.submit_ajax_form = function(url, form_selector, additional_data) {
diff --git a/sql/Pg-upgrade2/invoice_add_donumber.sql b/sql/Pg-upgrade2/invoice_add_donumber.sql
new file mode 100644 (file)
index 0000000..4b3aa72
--- /dev/null
@@ -0,0 +1,4 @@
+-- @tag: invoice_add_donumber
+-- @description: invoice_add_donumber
+-- @depends: release_3_0_0
+ALTER TABLE invoice ADD COLUMN donumber TEXT;
diff --git a/sql/Pg-upgrade2/unit_foreign_key_for_line_items.sql b/sql/Pg-upgrade2/unit_foreign_key_for_line_items.sql
new file mode 100644 (file)
index 0000000..d99e1a7
--- /dev/null
@@ -0,0 +1,6 @@
+-- @tag: unit_foreign_key_for_line_items
+-- @description: Fremdschlüssel auf »unit« für Beleg-Positionstabellen
+-- @depends: release_3_0_0
+ALTER TABLE orderitems           ADD FOREIGN KEY (unit) REFERENCES units (name);
+ALTER TABLE delivery_order_items ADD FOREIGN KEY (unit) REFERENCES units (name);
+ALTER TABLE invoice              ADD FOREIGN KEY (unit) REFERENCES units (name);
index 33623f61d43026feba817efe66536d1e6f79bbb0..1117c1f7e54d0d3c282f25e8af11c3bbc787b806 100644 (file)
@@ -1,45 +1,72 @@
 [%- USE T8 %]
 [%- USE HTML %]
 [%- USE L %]
-[%- USE LxERP %]
-[%- BLOCK cvar_name %][% HTML.escape(cvar.name_prefix) _ "cvar_" _ HTML.escape(cvar.var.name) _ HTML.escape(cvar.name_postfix) -%][% END %]
+[%- USE LxERP %][%- USE P -%]
 [%- BLOCK cvar_inputs %]
-[% render_input_blocks__cvar_name = PROCESS cvar_name %]
-[%- %]
+[%- SET render_cvar_tag_options = {};
+    render_cvar_tag_options.import(cvar_tag_options) IF cvar_tag_options;
+    SET cvar_tag_name = HTML.escape(cvar.name_prefix) _ "cvar_" _ HTML.escape(cvar.var.name) _ HTML.escape(cvar.name_postfix);
+    IF !render_cvar_tag_options.id && (cvar.var.type != 'select');
+      SET render_cvar_tag_options.no_id = 1;
+    END;
+%]
 [%- IF cvar.hide_non_editable && !cvar.var.flag_editable %]
-<input type="hidden" name="[% PROCESS cvar_name %]" value="[% HTML.escape(cvar.var.value) %]">
+[%- L.hidden_tag(cvar_tag_name, cvar.var.value, render_cvar_tag_options) %]
 [%- ELSIF !cvar.valid %]
   [%- IF show_disabled_message %]
 <i>[% 'Element disabled' | $T8 %]</i>
   [%- END %]
+
 [%- ELSIF cvar.var.type == 'bool' %]
-<input type="checkbox" name="[% PROCESS cvar_name %]" value="1"[% IF cvar.value %] checked[% END %]>
+[%- render_cvar_tag_options.import(checked=cvar.value, value=1);
+    L.checkbox_tag(cvar_tag_name, render_cvar_tag_options) %]
+
 [%- ELSIF cvar.var.type == 'textfield' %]
-[% L.textarea_tag(render_input_blocks__cvar_name, cvar.value, cols=cvar.var.width, rows=cvar.var.height) %]
+[% render_cvar_tag_options.import(cols=cvar.var.width, rows=cvar.var.height);
+   L.textarea_tag(cvar_tag_name, cvar.value, render_cvar_tag_options) %]
+
 [%- ELSIF cvar.var.type == 'date' %]
-[%- L.date_tag(render_input_blocks__cvar_name, cvar.value) %]
+[%- L.date_tag(cvar_tag_name, cvar.value, render_cvar_tag_options) %]
 
 [%- ELSIF cvar.var.type == 'timestamp' %]
-<input name="[% PROCESS cvar_name %]" value="[% HTML.escape(cvar.value) %]">
+[%- L.input_tag(cvar_tag_name, cvar.value, render_cvar_tag_options) %]
+
 [%- ELSIF cvar.var.type == 'select' %]
-<select name="[% PROCESS cvar_name %]">
- [%- FOREACH option = cvar.var.OPTIONS %]
- <option[% IF option.value == cvar.value %] selected[% END %]>[% HTML.escape(option.value) %]</option>
+[%- render_cvar_tag_options.name=cvar_tag_name %]
+<select [% P.stringify_attributes(render_cvar_tag_options) %]>
+ [%- IF LxERP.is_rdbo(cvar.var, 'CustomVariableConfig') %]
+  [%- FOREACH option = cvar.var.processed_options %]
+   [%- SET render_cvar_opts = {
+        value = option
+       };
+       render_cvar_opts.selected = 'selected' IF option == cvar.value;
+       L.html_tag('option', option, render_cvar_opts) %]
+  [%- END %]
+ [%- ELSE %]
+  [%- FOREACH option = cvar.var.OPTIONS %]
+   [%- SET render_cvar_opts = {
+        value = option.value
+       };
+       render_cvar_opts.selected = 'selected' IF option.value == cvar.value;
+       L.html_tag('option', option.value, render_cvar_opts) %]
+  [%- END %]
  [%- END %]
 </select>
+
 [%- ELSIF cvar.var.type == 'customer' %]
-[%- L.customer_picker(render_input_blocks__cvar_name, cvar.value) %]
+[%- L.customer_picker(cvar_tag_name, cvar.value, render_cvar_tag_options) %]
 
 [%- ELSIF cvar.var.type == 'vendor' %]
-[% L.vendor_selector(render_input_blocks__cvar_name, cvar.value) %]
+[% L.vendor_selector(cvar_tag_name, cvar.value, render_cvar_tag_options) %]
 
 [%- ELSIF cvar.var.type == 'part' %]
-[% L.part_selector(render_input_blocks__cvar_name, cvar.value) %]
+[% L.part_selector(cvar_tag_name, cvar.value, render_cvar_tag_options) %]
 
 [%- ELSIF cvar.var.type == 'number' %]
-[%- L.input_tag(render_input_blocks__cvar_name, LxERP.format_amount(cvar.value, -2)) %]
+[%- L.input_tag(cvar_tag_name, LxERP.format_amount(cvar.value, -2), render_cvar_tag_options) %]
 
 [%- ELSE %]
-<input name="[% PROCESS cvar_name %]" value="[% HTML.escape(cvar.value) %]" [%- IF cvar.var.maxlength %] maxlength="[% HTML.escape(cvar.var.maxlength) %]"[% END -%]>
+[% render_cvar_tag_options.maxlength=cvar.var.maxlength IF cvar.var.maxlength;
+   L.input_tag(cvar_tag_name, cvar.value, render_cvar_tag_options) %]
 [%- END %]
 [%- END %]