Merge branch 'master' of lx-office.linet-services.de:lx-office-erp
[kivitendo-erp.git] / SL / OE.pm
index 0b9256e..0fd91a4 100644 (file)
--- a/SL/OE.pm
+++ b/SL/OE.pm
 package OE;
 
 use List::Util qw(max first);
+use YAML;
+
 use SL::AM;
 use SL::Common;
 use SL::CVar;
+use SL::DB::PeriodicInvoicesConfig;
 use SL::DBUtils;
 use SL::IC;
 
+use strict;
+
 sub transactions {
   $main::lxdebug->enter_sub();
 
   my ($self, $myconfig, $form) = @_;
 
   # connect to database
-  my $dbh = $form->dbconnect($myconfig);
+  my $dbh = $form->get_standard_dbh;
 
   my $query;
   my $ordnumber = 'ordnumber';
@@ -56,11 +61,17 @@ sub transactions {
   my @values;
   my $where;
 
+  my ($periodic_invoices_columns, $periodic_invoices_joins);
+
   my $rate = ($form->{vc} eq 'customer') ? 'buy' : 'sell';
 
   if ($form->{type} =~ /_quotation$/) {
     $quotation = '1';
     $ordnumber = 'quonumber';
+
+  } elsif ($form->{type} eq 'sales_order') {
+    $periodic_invoices_columns = qq| , COALESCE(pcfg.active, 'f') AS periodic_invoices |;
+    $periodic_invoices_joins   = qq| LEFT JOIN periodic_invoices_configs pcfg ON (o.id = pcfg.oe_id) |;
   }
 
   my $vc = $form->{vc} eq "customer" ? "customer" : "vendor";
@@ -74,7 +85,8 @@ sub transactions {
     qq|  ex.$rate AS exchangerate, | .
     qq|  pr.projectnumber AS globalprojectnumber, | .
     qq|  e.name AS employee, s.name AS salesman, | .
-    qq|  ct.country, ct.ustid  | .
+    qq|  ct.${vc}number AS vcnumber, ct.country, ct.ustid  | .
+    $periodic_invoices_columns .
     qq|FROM oe o | .
     qq|JOIN $vc ct ON (o.${vc}_id = ct.id) | .
     qq|LEFT JOIN employee e ON (o.employee_id = e.id) | .
@@ -82,6 +94,7 @@ sub transactions {
     qq|LEFT JOIN exchangerate ex ON (ex.curr = o.curr | .
     qq|  AND ex.transdate = o.transdate) | .
     qq|LEFT JOIN project pr ON (o.globalproject_id = pr.id) | .
+    qq|$periodic_invoices_joins | .
     qq|WHERE (o.quotation = ?) |;
   push(@values, $quotation);
 
@@ -97,7 +110,18 @@ sub transactions {
       qq|AND ((globalproject_id = ?) OR EXISTS | .
       qq|  (SELECT * FROM orderitems oi | .
       qq|   WHERE oi.project_id = ? AND oi.trans_id = o.id))|;
-    push(@values, $form->{"project_id"}, $form->{"project_id"});
+    push(@values, conv_i($form->{"project_id"}), conv_i($form->{"project_id"}));
+  }
+
+  if ($form->{"projectnumber"}) {
+    $query .= <<SQL;
+      AND (pr.projectnumber ILIKE ?) OR EXISTS (
+        SELECT * FROM orderitems oi
+        LEFT JOIN project proi ON proi.id = oi.project_id
+        WHERE proi.projectnumber ILIKE ? AND oi.trans_id = o.id
+      )
+SQL
+    push @values, "%" . $form->{"projectnumber"} . "%", "%" . $form->{"projectnumber"} . "%" ;
   }
 
   if ($form->{"${vc}_id"}) {
@@ -109,6 +133,10 @@ sub transactions {
     push(@values, '%' . $form->{$vc} . '%');
   }
 
+  if (!$main::auth->assert('sales_all_edit', 1)) {
+    $query .= " AND o.employee_id = (select id from employee where login= ?)";
+    push @values, $form->{login};
+  }
   if ($form->{employee_id}) {
     $query .= " AND o.employee_id = ?";
     push @values, conv_i($form->{employee_id});
@@ -161,6 +189,11 @@ sub transactions {
     push(@values, '%' . $form->{transaction_description} . '%');
   }
 
+  if ($form->{periodic_invoices_active} ne $form->{periodic_invoices_inactive}) {
+    my $not  = 'NOT' if ($form->{periodic_invoices_inactive});
+    $query  .= qq| AND ${not} COALESCE(pcfg.active, 'f')|;
+  }
+
   my $sortdir   = !defined $form->{sortdir} ? 'ASC' : $form->{sortdir} ? 'ASC' : 'DESC';
   my $sortorder = join(', ', map { "${_} ${sortdir} " } ("o.id", $form->sort_columns("transdate", $ordnumber, "name")));
   my %allowed_sort_columns = (
@@ -193,7 +226,6 @@ sub transactions {
   }
 
   $sth->finish;
-  $dbh->disconnect;
 
   $main::lxdebug->leave_sub();
 }
@@ -242,7 +274,7 @@ sub save {
   my ($self, $myconfig, $form) = @_;
 
   # connect to database, turn off autocommit
-  my $dbh = $form->dbconnect_noauto($myconfig);
+  my $dbh = $form->get_standard_dbh;
 
   my ($query, @values, $sth, $null);
   my $exchangerate = 0;
@@ -320,8 +352,9 @@ sub save {
       my $baseqty = $form->{"qty_$i"} * $basefactor;
 
       $form->{"marge_percent_$i"} = $form->parse_amount($myconfig, $form->{"marge_percent_$i"}) * 1;
-      $form->{"marge_total_$i"} = $form->parse_amount($myconfig, $form->{"marge_total_$i"}) * 1;
-      $form->{"lastcost_$i"} = $form->{"lastcost_$i"} * 1;
+      $form->{"marge_absolut_$i"} = $form->parse_amount($myconfig, $form->{"marge_absolut_$i"}) * 1;
+
+      $form->{"lastcost_$i"} = $form->parse_amount($myconfig, $form->{"lastcost_$i"});
 
       # set values to 0 if nothing entered
       $form->{"discount_$i"} = $form->parse_amount($myconfig, $form->{"discount_$i"}) / 100;
@@ -411,7 +444,7 @@ sub save {
            $form->{"serialnumber_$i"}, $form->{"ship_$i"}, conv_i($pricegroup_id),
            $form->{"ordnumber_$i"}, conv_date($form->{"transdate_$i"}),
            $form->{"cusordnumber_$i"}, $form->{"subtotal_$i"} ? 't' : 'f',
-           $form->{"marge_percent_$i"}, $form->{"marge_total_$i"},
+           $form->{"marge_percent_$i"}, $form->{"marge_absolut_$i"},
            $form->{"lastcost_$i"},
            conv_i($form->{"price_factor_id_$i"}), conv_i($form->{"price_factor_id_$i"}),
            conv_i($form->{"marge_price_factor_$i"}));
@@ -528,16 +561,50 @@ sub save {
   $form->{saved_xyznumber} = $form->{$form->{type} =~ /_quotation$/ ?
                                        "quonumber" : "ordnumber"};
 
-  Common::webdav_folder($form) if ($main::webdav);
+  Common::webdav_folder($form);
 
   my $rc = $dbh->commit;
-  $dbh->disconnect;
+
+  $self->save_periodic_invoices_config(dbh         => $dbh,
+                                       oe_id       => $form->{id},
+                                       config_yaml => $form->{periodic_invoices_config})
+    if ($form->{type} eq 'sales_order');
 
   $main::lxdebug->leave_sub();
 
   return $rc;
 }
 
+sub save_periodic_invoices_config {
+  my ($self, %params) = @_;
+
+  return if !$params{oe_id};
+
+  my $config = $params{config_yaml} ? YAML::Load($params{config_yaml}) : undef;
+  return if 'HASH' ne ref $config;
+
+  my $obj  = SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $params{oe_id})
+          || SL::DB::PeriodicInvoicesConfig->new(oe_id => $params{oe_id});
+  $obj->update_attributes(%{ $config });
+}
+
+sub load_periodic_invoice_config {
+  my $self = shift;
+  my $form = shift;
+
+  delete $form->{periodic_invoices_config};
+
+  if ($form->{id}) {
+    my $config_obj = SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $form->{id});
+
+    if ($config_obj) {
+      my $config = { map { $_ => $config_obj->$_ } qw(active terminated periodicity start_date_as_date end_date_as_date extend_automatically_by ar_chart_id
+                                                      print printer_id copies) };
+      $form->{periodic_invoices_config} = YAML::Dump($config);
+    }
+  }
+}
+
 sub _close_quotations_rfqs {
   $main::lxdebug->enter_sub();
 
@@ -587,10 +654,11 @@ sub _close_quotations_rfqs {
 sub delete {
   $main::lxdebug->enter_sub();
 
-  my ($self, $myconfig, $form, $spool) = @_;
+  my ($self, $myconfig, $form) = @_;
 
   # connect to database
-  my $dbh = $form->dbconnect_noauto($myconfig);
+  my $dbh = $form->get_standard_dbh;
+  $dbh->begin_work;
 
   # delete spool files
   my $query = qq|SELECT s.spoolfile FROM status s | .
@@ -610,6 +678,10 @@ sub delete {
   # delete-values
   @values = (conv_i($form->{id}));
 
+  # periodic invoices and their configuration
+  do_query($form, $dbh, qq|DELETE FROM periodic_invoices         WHERE config_id IN (SELECT id FROM periodic_invoices_configs WHERE oe_id = ?)|, @values);
+  do_query($form, $dbh, qq|DELETE FROM periodic_invoices_configs WHERE oe_id = ?|, @values);
+
   # delete status entries
   $query = qq|DELETE FROM status | .
            qq|WHERE trans_id = ?|;
@@ -630,9 +702,9 @@ sub delete {
   do_query($form, $dbh, $query, @values);
 
   my $rc = $dbh->commit;
-  $dbh->disconnect;
 
   if ($rc) {
+    my $spool = $::lx_office_conf{paths}->{spool};
     foreach $spoolfile (@spoolfiles) {
       unlink "$spool/$spoolfile" if $spoolfile;
     }
@@ -649,7 +721,7 @@ sub retrieve {
   my ($self, $myconfig, $form) = @_;
 
   # connect to database
-  my $dbh = $form->dbconnect_noauto($myconfig);
+  my $dbh = $form->get_standard_dbh;
 
   my ($query, $query_add, @values, @ids, $sth);
 
@@ -736,7 +808,7 @@ sub retrieve {
                                          "quonumber" : "ordnumber"};
 
     # set all entries for multiple ids blank that yield different information
-    while ($ref = $sth->fetchrow_hashref(NAME_lc)) {
+    while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
       map { $form->{$_} = '' if ($ref->{$_} ne $form->{$_}) } keys %$ref;
     }
 
@@ -920,23 +992,48 @@ sub retrieve {
 
   $form->{exchangerate} = $form->get_exchangerate($dbh, $form->{currency}, $form->{transdate}, ($form->{vc} eq 'customer') ? "buy" : "sell");
 
-  Common::webdav_folder($form) if ($main::webdav);
+  Common::webdav_folder($form);
+
+  $self->load_periodic_invoice_config($form);
 
   my $rc = $dbh->commit;
-  $dbh->disconnect;
 
   $main::lxdebug->leave_sub();
 
   return $rc;
 }
 
+sub retrieve_simple {
+  $main::lxdebug->enter_sub();
+
+  my $self     = shift;
+  my %params   = @_;
+
+  Common::check_params(\%params, qw(id));
+
+  my $myconfig    = \%main::myconfig;
+  my $form        = $main::form;
+
+  my $dbh         = $params{dbh} || $form->get_standard_dbh($myconfig);
+
+  my $oe_query    = qq|SELECT * FROM oe         WHERE id = ?|;
+  my $oi_query    = qq|SELECT * FROM orderitems WHERE trans_id = ?|;
+
+  my $order            = selectfirst_hashref_query($form, $dbh, $oe_query, conv_i($params{id}));
+  $order->{orderitems} = selectall_hashref_query(  $form, $dbh, $oi_query, conv_i($params{id}));
+
+  $main::lxdebug->leave_sub();
+
+  return $order;
+}
+
 sub order_details {
   $main::lxdebug->enter_sub();
 
   my ($self, $myconfig, $form) = @_;
 
   # connect to database
-  my $dbh = $form->dbconnect($myconfig);
+  my $dbh = $form->get_standard_dbh;
   my $query;
   my @values = ();
   my $sth;
@@ -960,7 +1057,7 @@ sub order_details {
   my %oid = ('Pg'     => 'oid',
              'Oracle' => 'rowid');
 
-  my (@project_ids, %projectnumbers);
+  my (@project_ids, %projectnumbers, %projectdescriptions);
 
   push(@project_ids, $form->{"globalproject_id"}) if ($form->{"globalproject_id"});
 
@@ -992,16 +1089,18 @@ sub order_details {
   }
 
   if (@project_ids) {
-    $query = "SELECT id, projectnumber FROM project WHERE id IN (" .
+    $query = "SELECT id, projectnumber, description FROM project WHERE id IN (" .
       join(", ", map("?", @project_ids)) . ")";
     $sth = prepare_execute_query($form, $dbh, $query, @project_ids);
     while (my $ref = $sth->fetchrow_hashref()) {
       $projectnumbers{$ref->{id}} = $ref->{projectnumber};
+      $projectdescriptions{$ref->{id}} = $ref->{description};
     }
     $sth->finish();
   }
 
   $form->{"globalprojectnumber"} = $projectnumbers{$form->{"globalproject_id"}};
+  $form->{"globalprojectdescription"} = $projectdescriptions{$form->{"globalproject_id"}};
 
   $form->{discount} = [];
 
@@ -1014,7 +1113,7 @@ sub order_details {
     qw(runningnumber number description longdescription qty ship unit bin
        partnotes serialnumber reqdate sellprice listprice netprice
        discount p_discount discount_sub nodiscount_sub
-       linetotal  nodiscount_linetotal tax_rate projectnumber
+       linetotal  nodiscount_linetotal tax_rate projectnumber projectdescription
        price_factor price_factor_name partsgroup);
 
   push @arrays, map { "ic_cvar_$_->{name}" } @{ $ic_cvar_configs };
@@ -1061,13 +1160,16 @@ sub order_details {
       push @{ $form->{TEMPLATE_ARRAYS}->{description} },       $form->{"description_$i"};
       push @{ $form->{TEMPLATE_ARRAYS}->{longdescription} },   $form->{"longdescription_$i"};
       push @{ $form->{TEMPLATE_ARRAYS}->{qty} },               $form->format_amount($myconfig, $form->{"qty_$i"});
+      push @{ $form->{TEMPLATE_ARRAYS}->{qty_nofmt} },         $form->{"qty_$i"};
       push @{ $form->{TEMPLATE_ARRAYS}->{ship} },              $form->format_amount($myconfig, $form->{"ship_$i"});
+      push @{ $form->{TEMPLATE_ARRAYS}->{ship_nofmt} },        $form->{"ship_$i"};
       push @{ $form->{TEMPLATE_ARRAYS}->{unit} },              $form->{"unit_$i"};
       push @{ $form->{TEMPLATE_ARRAYS}->{bin} },               $form->{"bin_$i"};
       push @{ $form->{TEMPLATE_ARRAYS}->{partnotes} },         $form->{"partnotes_$i"};
       push @{ $form->{TEMPLATE_ARRAYS}->{serialnumber} },      $form->{"serialnumber_$i"};
       push @{ $form->{TEMPLATE_ARRAYS}->{reqdate} },           $form->{"reqdate_$i"};
       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}->{listprice} },         $form->{"listprice_$i"};
       push @{ $form->{TEMPLATE_ARRAYS}->{price_factor} },      $price_factor->{formatted_factor};
       push @{ $form->{TEMPLATE_ARRAYS}->{price_factor_name} }, $price_factor->{description};
@@ -1085,12 +1187,14 @@ sub order_details {
       my $nodiscount_linetotal = $form->round_amount($form->{"qty_$i"} * $sellprice / $price_factor->{factor}, 2);
       $form->{"netprice_$i"}   = $form->round_amount($form->{"qty_$i"} ? ($linetotal / $form->{"qty_$i"}) : 0, 2);
 
-      push @{ $form->{TEMPLATE_ARRAYS}->{netprice} }, ($form->{"netprice_$i"} != 0) ? $form->format_amount($myconfig, $form->{"netprice_$i"}, $decimalplaces) : '';
+      push @{ $form->{TEMPLATE_ARRAYS}->{netprice} },       ($form->{"netprice_$i"} != 0) ? $form->format_amount($myconfig, $form->{"netprice_$i"}, $decimalplaces) : '';
+      push @{ $form->{TEMPLATE_ARRAYS}->{netprice_nofmt} }, ($form->{"netprice_$i"} != 0) ? $form->{"netprice_$i"} : '';
 
       $linetotal = ($linetotal != 0) ? $linetotal : '';
 
-      push @{ $form->{TEMPLATE_ARRAYS}->{discount} },  ($discount  != 0) ? $form->format_amount($myconfig, $discount * -1, 2) : '';
-      push @{ $form->{TEMPLATE_ARRAYS}->{p_discount} }, $form->{"discount_$i"};
+      push @{ $form->{TEMPLATE_ARRAYS}->{discount} },       ($discount != 0) ? $form->format_amount($myconfig, $discount * -1, 2) : '';
+      push @{ $form->{TEMPLATE_ARRAYS}->{discount_nofmt} }, ($discount != 0) ? $discount * -1 : '';
+      push @{ $form->{TEMPLATE_ARRAYS}->{p_discount} },     $form->{"discount_$i"};
 
       $form->{ordtotal}         += $linetotal;
       $form->{nodiscount_total} += $nodiscount_linetotal;
@@ -1102,8 +1206,10 @@ sub order_details {
       }
 
       if ($form->{"subtotal_$i"} && $subtotal_header && ($subtotal_header != $i)) {
-        push @{ $form->{TEMPLATE_ARRAYS}->{discount_sub} },   $form->format_amount($myconfig, $discount_subtotal,   2);
-        push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_sub} }, $form->format_amount($myconfig, $nodiscount_subtotal, 2);
+        push @{ $form->{TEMPLATE_ARRAYS}->{discount_sub} },         $form->format_amount($myconfig, $discount_subtotal,   2);
+        push @{ $form->{TEMPLATE_ARRAYS}->{discount_sub_nofmt} },   $discount_subtotal;
+        push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_sub} },       $form->format_amount($myconfig, $nodiscount_subtotal, 2);
+        push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_sub_nofmt} }, $nodiscount_subtotal;
 
         $discount_subtotal   = 0;
         $nodiscount_subtotal = 0;
@@ -1118,10 +1224,12 @@ sub order_details {
         $nodiscount += $linetotal;
       }
 
-      push @{ $form->{TEMPLATE_ARRAYS}->{linetotal} }, $form->format_amount($myconfig, $linetotal, 2);
-      push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_linetotal} }, $form->format_amount($myconfig, $nodiscount_linetotal, 2);
-
-      push(@{ $form->{TEMPLATE_ARRAYS}->{projectnumber} }, $projectnumbers{$form->{"project_id_$i"}});
+      push @{ $form->{TEMPLATE_ARRAYS}->{linetotal} },                  $form->format_amount($myconfig, $linetotal, 2);
+      push @{ $form->{TEMPLATE_ARRAYS}->{linetotal_nofmt} },            $linetotal_exact;
+      push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_linetotal} },       $form->format_amount($myconfig, $nodiscount_linetotal, 2);
+      push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_linetotal_nofmt} }, $nodiscount_linetotal;
+      push(@{ $form->{TEMPLATE_ARRAYS}->{projectnumber} },              $projectnumbers{$form->{"project_id_$i"}});
+      push(@{ $form->{TEMPLATE_ARRAYS}->{projectdescription} },         $projectdescriptions{$form->{"project_id_$i"}});
 
       my ($taxamount, $taxbase);
       my $taxrate = 0;
@@ -1160,13 +1268,13 @@ sub order_details {
         }
 
         $query = qq|SELECT p.partnumber, p.description, p.unit, a.qty, | .
-                      qq|pg.partsgroup | .
-                      qq|FROM assembly a | .
-                            qq|  JOIN parts p ON (a.parts_id = p.id) | .
-                            qq|    LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) | .
-                            qq|    WHERE a.bom = '1' | .
-                            qq|    AND a.id = ? | . $sortorder;
-                   @values = ($form->{"id_$i"});
+                 qq|pg.partsgroup | .
+                 qq|FROM assembly a | .
+                 qq|  JOIN parts p ON (a.parts_id = p.id) | .
+                 qq|    LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) | .
+                 qq|    WHERE a.bom = '1' | .
+                 qq|    AND a.id = ? | . $sortorder;
+        @values = ($form->{"id_$i"});
         $sth = $dbh->prepare($query);
         $sth->execute(@values) || $form->dberror($query);
 
@@ -1192,8 +1300,11 @@ sub order_details {
     $tax += $taxamount = $form->round_amount($taxaccounts{$item}, 2);
 
     push(@{ $form->{TEMPLATE_ARRAYS}->{taxbase} },        $form->format_amount($myconfig, $taxbase{$item}, 2));
+    push(@{ $form->{TEMPLATE_ARRAYS}->{taxbase_nofmt} },  $taxbase{$item});
     push(@{ $form->{TEMPLATE_ARRAYS}->{tax} },            $form->format_amount($myconfig, $taxamount,      2));
+    push(@{ $form->{TEMPLATE_ARRAYS}->{tax_nofmt} },      $taxamount);
     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate} },        $form->format_amount($myconfig, $form->{"${item}_rate"} * 100));
+    push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate_nofmt} },  $form->{"${item}_rate"} * 100);
     push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $form->{"${item}_description"} . q{ } . 100 * $form->{"${item}_rate"} . q{%});
     push(@{ $form->{TEMPLATE_ARRAYS}->{taxnumber} },      $form->{"${item}_taxnumber"});
   }
@@ -1204,9 +1315,11 @@ sub order_details {
   $form->{yesdiscount}         = $form->format_amount($myconfig, $form->{nodiscount_total} - $nodiscount, 2);
 
   if($form->{taxincluded}) {
-    $form->{subtotal} = $form->format_amount($myconfig, $form->{ordtotal} - $tax, 2);
+    $form->{subtotal}       = $form->format_amount($myconfig, $form->{ordtotal} - $tax, 2);
+    $form->{subtotal_nofmt} = $form->{ordtotal} - $tax;
   } else {
-    $form->{subtotal} = $form->format_amount($myconfig, $form->{ordtotal}, 2);
+    $form->{subtotal}       = $form->format_amount($myconfig, $form->{ordtotal}, 2);
+    $form->{subtotal_nofmt} = $form->{ordtotal};
   }
 
   $form->{ordtotal} = ($form->{taxincluded}) ? $form->{ordtotal} : $form->{ordtotal} + $tax;
@@ -1241,3 +1354,36 @@ sub project_description {
 }
 
 1;
+
+__END__
+
+=head1 NAME
+
+OE.pm - Order entry module
+
+=head1 DESCRIPTION
+
+OE.pm is part of the OE module. OE is responsible for sales and purchase orders, as well as sales quotations and purchase requests. This file abstracts the database tables C<oe> and C<orderitems>.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item retrieve_simple PARAMS
+
+simple OE retrieval by id. does not look up customer, vendor, units or any other stuff. only oe and orderitems.
+
+  my $order = retrieve_simple(id => 2);
+
+  $order => {
+    %_OE_CONTENT,
+    orderitems => [
+      %_ORDERITEM_ROW_1,
+      %_ORDERITEM_ROW_2,
+      ...
+    ]
+  }
+
+=back
+
+=cut