From 0601f32f9e782b0b93235bf7fc1e95c698163e60 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bernd=20Ble=C3=9Fmann?= Date: Mon, 20 Jul 2015 14:40:37 +0200 Subject: [PATCH] =?utf8?q?Aufrtags-Controller:=20erste=20Version=20des=20C?= =?utf8?q?ontrollers=20und=20Men=C3=BC-Eintrag.?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- SL/Controller/Order.pm | 357 ++++++++++++++++++ menus/user/10-order-controller.yaml | 9 + templates/webpages/order/form.html | 36 ++ templates/webpages/order/tabs/_row.html | 55 +++ templates/webpages/order/tabs/basic_data.html | 232 ++++++++++++ 5 files changed, 689 insertions(+) create mode 100644 SL/Controller/Order.pm create mode 100644 menus/user/10-order-controller.yaml create mode 100644 templates/webpages/order/form.html create mode 100644 templates/webpages/order/tabs/_row.html create mode 100644 templates/webpages/order/tabs/basic_data.html diff --git a/SL/Controller/Order.pm b/SL/Controller/Order.pm new file mode 100644 index 000000000..2c866ce9c --- /dev/null +++ b/SL/Controller/Order.pm @@ -0,0 +1,357 @@ +package SL::Controller::Order; + +use strict; +use parent qw(SL::Controller::Base); + +use SL::Helper::Flash; +use SL::ClientJS; +use SL::Presenter; + +use SL::DB::Order; +use SL::DB::Customer; +use SL::DB::Vendor; +use SL::DB::TaxZone; +use SL::DB::Employee; +use SL::DB::Project; +use SL::DB::Default; +use SL::DB::Unit; + +use SL::Helper::DateTime; + +use List::Util qw(max); +use List::MoreUtils qw(none); + +use Rose::Object::MakeMethods::Generic +( + 'scalar' => [ qw(order) ], + 'scalar --get_set_init' => [ qw(valid_types type cv js p) ], +); + + +# safety +__PACKAGE__->run_before('_check_auth'); + +__PACKAGE__->run_before('_load_or_new_order', + only => [ qw(add edit update save customer_vendor_changed set_item_values) ]); + +__PACKAGE__->run_before('_setup', + only => [ qw(edit update save) ]); + + +# +# actions +# + +sub action_add { + my ($self) = @_; + + $self->order->transdate(DateTime->now_local()); + + $self->_pre_render(); + $self->render( + 'order/form', + title => $self->type eq _sales_order_type() ? $::locale->text('Add Sales Order') + : $self->type eq _purchase_order_type() ? $::locale->text('Add Purchase Order') + : '', + %{$self->{template_args}} + ); +} + +sub action_edit { + my ($self) = @_; + + $self->_pre_render(); + $self->render( + 'order/form', + title => $self->type eq _sales_order_type() ? $::locale->text('Edit Sales Order') + : $self->type eq _purchase_order_type() ? $::locale->text('Edit Purchase Order') + : '', + %{$self->{template_args}} + ); +} + +sub action_update { + my ($self) = @_; + + $self->_pre_render(); + $self->render( + 'order/form', + title => $self->type eq _sales_order_type() ? $::locale->text('Edit Sales Order') + : $self->type eq _purchase_order_type() ? $::locale->text('Edit Purchase Order') + : '', + %{$self->{template_args}} + ); +} + +sub action_save { + my ($self) = @_; + + $self->_save(); + + my @redirect_params = ( + action => 'edit', + type => $self->type, + id => $self->order->id, + ); + + $self->redirect_to(@redirect_params); +} + +sub action_customer_vendor_changed { + my ($self) = @_; + + if ($self->type eq _sales_order_type()) { + $self->order->customer(SL::DB::Manager::Customer->find_by_or_create(id => $::form->{cv_id})); + + } elsif ($self->type eq _purchase_order_type()) { + $self->order->vendor(SL::DB::Manager::Vendor->find_by_or_create(id => $::form->{cv_id})); + } + + if ($self->order->{$self->cv}->contacts && scalar @{ $self->order->{$self->cv}->contacts } > 0) { + $self->js->show('#cp_row'); + } else { + $self->js->hide('#cp_row'); + } + + if ($self->order->{$self->cv}->shipto && scalar @{ $self->order->{$self->cv}->shipto } > 0) { + $self->js->show('#shipto_row'); + } else { + $self->js->hide('#shipto_row'); + } + + $self->js + ->replaceWith('#order_cp_id', $self->build_contact_select) + ->replaceWith('#order_shipto_id', $self->build_shipto_select) + ->val('#order_taxzone_id', $self->order->{$self->cv}->taxzone_id) + ->focus('#order_cv_id') + ->render($self); +} + +sub action_add_item_row { + my ($self) = @_; + + my $random_id = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000); + my $row_as_html = $self->p->render('order/tabs/_row', ITEM => {id => $random_id}); + + $self->js + ->append('#row_table_id tbody', $row_as_html) + ->focus('#row_table_id tr:last [id$="parts_id_name"]') + ->off('[id^="order_orderitems"][id$="parts_id"]', 'change', 'set_item_values') + ->on('[id^="order_orderitems"][id$="parts_id"]', 'change', 'set_item_values') + ->render($self); +} + +sub action_set_item_values { + my ($self) = @_; + + my $part = SL::DB::Part->new(id => $::form->{parts_id})->load; + + my $is_new = $::form->{item_id} =~ m{^new_}; + my $item_id = $is_new ? undef : $::form->{item_id}; + my $item = SL::DB::Manager::OrderItem->find_by_or_create(id => $item_id); + + my $cv_class = "SL::DB::" . ucfirst($self->cv); + my $cv_discount = $::form->{cv_id}? $cv_class->new(id => $::form->{cv_id})->load->discount :0.0; + + $item->assign_attributes( + parts_id => $part->id, + qty => $::form->{qty} ? $::form->parse_amount(\%::myconfig, $::form->{qty}) : 1.0, + unit => $part->unit, + discount => $::form->{discount} ? $::form->parse_amount(\%::myconfig, $::form->{discount}) : $cv_discount, + sellprice => $::form->{sellprice} ? $::form->parse_amount(\%::myconfig, $::form->{sellprice}) : $part->sellprice, + ); + + $self->order->add_items([$item]); + $self->_setup(); + my $linetotal = _linetotal($self->order, $item); + + $self->js + ->val( '#' . $::form->{qty_dom_id}, $item->qty_as_number) + ->val( '#' . $::form->{unit_dom_id}, $item->unit) + ->val( '#' . $::form->{sellprice_dom_id}, $item->sellprice_as_number) + ->val( '#' . $::form->{discount_dom_id}, $item->discount_as_number) + ->run('recalc_linetotal', $::form->{item_id}, $::form->format_amount(\%::myconfig, $linetotal, -2)) + ->render($self); +} + + +# +# helpers +# + +sub init_valid_types { + [ _sales_order_type(), _purchase_order_type() ]; +} + +sub init_type { + my ($self) = @_; + + if (none { $::form->{type} eq $_ } @{$self->valid_types}) { + die "Not a valid type for order"; + } + + $self->type($::form->{type}); +} + +sub init_cv { + my ($self) = @_; + + my $cv = $self->type eq _sales_order_type() ? 'customer' + : $self->type eq _purchase_order_type() ? 'vendor' + : die "Not a valid type for order"; + + return $cv; +} + +sub init_js { + SL::ClientJS->new; +} + +sub init_p { + SL::Presenter->get; +} + +sub _check_auth { + my ($self) = @_; + + my $right_for = { map { $_ => $_.'_edit' } @{$self->valid_types} }; + + my $right = $right_for->{ $self->type }; + $right ||= 'DOES_NOT_EXIST'; + + $::auth->assert($right); +} + +sub build_contact_select { + my ($self) = @_; + + $self->p->select_tag('order.cp_id', [ $self->order->{$self->cv}->contacts ], + value_key => 'cp_id', + title_key => 'full_name_dep', + default => $self->order->cp_id, + with_empty => 1, + style => 'width: 300px', + ); +} + +sub build_shipto_select { + my ($self) = @_; + + $self->p->select_tag('order.shipto_id', [ $self->order->{$self->cv}->shipto ], + value_key => 'shipto_id', + title_key => 'displayable_id', + default => $self->order->shipto_id, + with_empty => 1, + style => 'width: 300px', + ); +} + +sub _load_or_new_order { + my ($self) = @_; + + return $self->order(SL::DB::Manager::Order->find_by_or_create(id => $::form->{id})); +} + +sub _setup { + my ($self) = @_; + + $::form->{order}->{ $self->cv . '_id' } = delete $::form->{order}->{cv_id} if $::form->{order}->{cv_id}; + $self->order->assign_attributes(%{$::form->{order}}); + + # bb: todo: currency later + $self->order->currency_id($::instance_conf->get_currency_id()); + + my %pat = $self->order->calculate_prices_and_taxes(); + + foreach my $tax_chart_id (keys %{ $pat{taxes} }) { + my $tax = SL::DB::Manager::Tax->find_by(chart_id => $tax_chart_id); + push(@{ $self->{taxes} }, { amount => $pat{taxes}->{$tax_chart_id}, + tax => $tax }); + } + + $self->{totalweight} = 0; + foreach my $item ($self->order->items) { + $item->{linetotal} = _linetotal($self->order, $item); + my $item_unit = SL::DB::Manager::Unit->find_by(name => $item->unit); + my $part_unit = SL::DB::Manager::Unit->find_by(name => $item->part->unit); + my $base_qty = $item_unit->convert_to($item->qty, $part_unit); + $item->{weight} = $base_qty * $item->part->weight; + + # Calculate total weight/tare weight + $self->{totalweight} += $item->{weight}; + } +} + +sub _save { + my ($self) = @_; + + my $db = $self->order->db; + + $db->do_transaction( + sub { + $self->order->save(); + }) || die($db->error); +} + + +sub _pre_render { + my ($self) = @_; + + $self->{all_taxzones} = SL::DB::Manager::TaxZone->get_all(); + $self->{all_employees} = SL::DB::Manager::Employee->get_all(where => [ or => [ id => $self->order->employee_id, + deleted => 0 ] ], + sort_by => 'name'); + $self->{all_projects} = SL::DB::Manager::Project->get_all(where => [ or => [ id => $self->order->globalproject_id, + active => 1 ] ], + sort_by => 'projectnumber'); + + $self->{current_employee_id} = SL::DB::Manager::Employee->current->id; + $self->{show_weight} = SL::DB::Default->get()->show_weight; +} + +# The following subs are more or less copied/pasted from SL::DB::Helper::PriceTaxCalculator. +sub _linetotal { + my ($order, $item) = @_; + + # bb: todo: currencies are not handled by now + my $exchangerate = _get_exchangerate($order); + + my $num_dec = max 2, _num_decimal_places($item->sellprice); + my $discount = _round($item->sellprice * ($item->discount || 0), $num_dec); + my $sellprice = _round($item->sellprice - $discount, $num_dec); + my $linetotal = _round($sellprice * $item->qty / $item->price_factor, 2 ) * $exchangerate; + $linetotal = _round($linetotal, 2 ); + + return $linetotal; +} + +sub _get_exchangerate { + my ($order) = @_; + require SL::DB::Default; + + my $exchangerate = 1; + my $currency = $order->currency_id ? $order->currency->name || '' : ''; + if ($currency ne SL::DB::Default->get_default_currency) { + $exchangerate = $::form->check_exchangerate(\%::myconfig, $currency, $order->transdate, $order->is_sales ? 'buy' : 'sell'); + } + + return $exchangerate; +} + +sub _num_decimal_places { + return length( (split(/\./, '' . ($_[0] * 1), 2))[1] || '' ); +} + +sub _round { + return $::form->round_amount(@_); +} + +sub _sales_order_type { + 'sales_order'; +} + +sub _purchase_order_type { + 'purchase_order'; +} + +1; diff --git a/menus/user/10-order-controller.yaml b/menus/user/10-order-controller.yaml new file mode 100644 index 000000000..7e38465d7 --- /dev/null +++ b/menus/user/10-order-controller.yaml @@ -0,0 +1,9 @@ +- parent: ar + id: ar_add_sales_order_new + name: Add Sales Order (new) + icon: sales_order_add + order: 350 + access: sales_order_edit + params: + action: Order/add + type: sales_order diff --git a/templates/webpages/order/form.html b/templates/webpages/order/form.html new file mode 100644 index 000000000..37cd0e7d2 --- /dev/null +++ b/templates/webpages/order/form.html @@ -0,0 +1,36 @@ +[%- USE T8 %] +[%- USE LxERP %] +[%- USE L %] + +
+
[% FORM.title %]
+ + [% L.hidden_tag('callback', FORM.callback) %] + [% L.hidden_tag('type', FORM.type) %] + [% L.hidden_tag('id', SELF.order.id) %] + + [%- INCLUDE 'common/flash.html' %] + +
+ + + [% PROCESS "order/tabs/basic_data.html" %] + [% PROCESS 'webdav/_list.html' %] +
+ [%- LxERP.t8("Loading...") %] +
+
+ +
+ + [% L.hidden_tag('action', 'Order/dispatch') %] + + [% L.submit_tag('action_update', LxERP.t8('Update')) %] + [% L.submit_tag('action_save', LxERP.t8('Save')) %] + +
diff --git a/templates/webpages/order/tabs/_row.html b/templates/webpages/order/tabs/_row.html new file mode 100644 index 000000000..14ba18d9c --- /dev/null +++ b/templates/webpages/order/tabs/_row.html @@ -0,0 +1,55 @@ +[%- USE T8 %] +[%- USE HTML %] +[%- USE LxERP %] +[%- USE L %] + + + + [% L.hidden_tag("item_id", ITEM.id, id='item_' _ ITEM.id, ) %] + + + [%- LxERP.t8('reorder item') %] + + + [%- L.button_tag("delete_order_item_row(this)", + LxERP.t8("X"), + confirm=LxERP.t8("Are you sure?")) %] + + [% L.part_picker("order.orderitems[+].parts_id", + ITEM.part, + style='width: 300px') %] + + + [%- L.input_tag("order.orderitems[].qty_as_number", + ITEM.qty_as_number, + size = 5, + style='text-align:right') %] + + + [%- L.input_tag("order.orderitems[].price_factor", + ITEM.price_factor, + size = 5, + style='text-align:right') %] + + + [%- L.input_tag("order.orderitems[].unit", + ITEM.unit, + size = 5) %] + + + [%- L.input_tag("order.orderitems[].sellprice_as_number", + ITEM.sellprice_as_number, + size = 10, + style='text-align:right') %] + + + [%- L.input_tag("order.orderitems[].discount_as_percent", + ITEM.discount_as_percent, + size = 5, + style='text-align:right') %] + + + [%- L.div_tag(LxERP.format_amount(ITEM.linetotal, 2, 0), name="linetotal") %] + + + diff --git a/templates/webpages/order/tabs/basic_data.html b/templates/webpages/order/tabs/basic_data.html new file mode 100644 index 000000000..f3cd724b8 --- /dev/null +++ b/templates/webpages/order/tabs/basic_data.html @@ -0,0 +1,232 @@ +[%- USE T8 %] +[%- USE HTML %] +[%- USE LxERP %] +[%- USE L %] + +
+ + + + + + +
+ + + + [% SET cv_id = SELF.cv _ '_id' %] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
[% SELF.cv | $T8 %][% L.customer_vendor_picker('order.cv_id', SELF.order.$cv_id, type=SELF.cv, style='width: 300px') %]
[% 'Contact Person' | $T8 %][% L.select_tag('order.cp_id', + SELF.order.${SELF.cv}.contacts, + default=SELF.order.cp_id, + title_key='full_name_dep', + value_key='cp_id', + with_empty=1, + style='width: 300px') %]
[% 'Shipping Address' | $T8 %][% L.select_tag('order.shipto_id', + SELF.order.${SELF.cv}.shipto, + default=SELF.order.shipto_id, + title_key='displayable_id', + value_key='shipto_id', + with_empty=1, + style='width: 300px') %]
[% 'Steuersatz' | $T8 %][% L.select_tag('order.taxzone_id', SELF.all_taxzones, default=SELF.order.taxzone_id, title_key='description', style='width: 300px') %]
[% 'Shipping Point' | $T8 %][% L.input_tag('order.shippingpoint', SELF.order.shippingpoint, style='width: 300px') %]
[% 'Ship via' | $T8 %][% L.input_tag('order.shipvia', SELF.order.shipvia, style='width: 300px') %]
[% 'Transaction description' | $T8 %][% L.input_tag('order.transaction_description', SELF.order.transaction_description, style='width: 300px') %]
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ [%- IF SELF.order.closed %] [% 'Closed' | $T8 %] [%- ELSE %] [% 'Open' | $T8 %] [%- END %] +
[% 'Employee' | $T8 %][% L.select_tag('order.employee_id', + SELF.all_employees, + default=(SELF.order.employee_id ? SELF.order.employee_id : SELF.current_employee_id), + title_key='safe_name') %]
[% 'Order Number' | $T8 %][% L.input_tag('order.ordnumber', SELF.order.ordnumber, size = 11) %]
[% 'Customer Order Number' | $T8 %][% L.input_tag('order.cusordnumber', SELF.order.cusordnumber, size = 11) %]
[% 'Order Date' | $T8 %][% L.date_tag('order.transdate', SELF.order.transdate) %]
[% 'Project Number' | $T8 %][%- L.select_tag('order.globalproject_id', SELF.all_projects, default=SELF.order.globalproject_id, title_key='projectnumber', with_empty = 1) %]
+ +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + [%- FOREACH item = SELF.order.items_sorted %] + [%- PROCESS order/tabs/_row.html ITEM=item %] + [%- END %] + [%- IF !SELF.order.items.size %] + [%- PROCESS order/tabs/_row.html ITEM='' %] + [%- END %] + + +
[%- LxERP.t8('reorder item') %][%- LxERP.t8('delete item') %][%- 'Part' | $T8 %] [%- 'Qty' | $T8 %] [%- 'Price Factor' | $T8 %] [%- 'Unit' | $T8 %] [%- 'Price' | $T8 %] [%- 'Discount' | $T8 %] [%- 'Extended' | $T8 %]
+
[%- L.button_tag('add_order_item_row()', LxERP.t8("Add Row")) -%]
+ + [%- IF NOT taxincluded %] + + + + + [%- END %] + [%- FOREACH tax = SELF.taxes %] + + + + + [%- END %] + + + + +
[%- 'Subtotal' | $T8 %][%- SELF.order.netamount_as_number %]
[%- tax.tax.description %] [% tax.tax.rate_as_percent %]%[%- LxERP.format_amount(tax.amount, 2, 0) %]
[%- 'Total' | $T8 %][%- SELF.order.amount_as_number %]
+
+ +
+ + +[% L.sortable_element('#row_table_id tbody') %] + + -- 2.20.1