Auftrags-Controller: PriceSources
authorBernd Bleßmann <bernd@kivitendo-premium.de>
Thu, 15 Oct 2015 23:35:05 +0000 (01:35 +0200)
committerBernd Bleßmann <bernd@kivitendo-premium.de>
Fri, 11 Mar 2016 11:45:30 +0000 (12:45 +0100)
SL/Controller/Order.pm
templates/webpages/order/tabs/_item_input.html
templates/webpages/order/tabs/_price_sources_dialog.html [new file with mode: 0644]
templates/webpages/order/tabs/_row.html
templates/webpages/order/tabs/basic_data.html

index 1c713a7..316198e 100644 (file)
@@ -18,12 +18,13 @@ use SL::DB::Employee;
 use SL::DB::Project;
 use SL::DB::Default;
 use SL::DB::Unit;
+use SL::DB::Price;
 
 use SL::Helper::DateTime;
 use SL::Helper::CreatePDF qw(:all);
 
 use List::Util qw(max first);
-use List::MoreUtils qw(none pairwise);
+use List::MoreUtils qw(none pairwise first_index);
 use English qw(-no_match_vars);
 use File::Spec;
 
@@ -315,17 +316,40 @@ sub action_add_item {
   my $item = SL::DB::OrderItem->new;
   $item->assign_attributes(%$form_attr);
 
-  my $part        = SL::DB::Part->new(id => $form_attr->{parts_id})->load;
-  my $cv_method   = $self->cv;
-  my $cv_discount = $self->order->$cv_method? $self->order->$cv_method->discount : 0.0;
+  my $part         = SL::DB::Part->new(id => $form_attr->{parts_id})->load;
+
+  my $price_source = SL::PriceSource->new(record_item => $item, record => $self->order);
+
+  my $price_src;
+  if ($item->sellprice) {
+    $price_src = $price_source->price_from_source("");
+    $price_src->price($item->sellprice);
+  } else {
+    $price_src = $price_source->best_price
+           ? $price_source->best_price
+           : $price_source->price_from_source("");
+    $price_src->price(0) if !$price_source->best_price;
+  }
+
+  my $discount_src;
+  if ($item->discount) {
+    $discount_src = $price_source->discount_from_source("");
+    $discount_src->discount($item->discount);
+  } else {
+    $discount_src = $price_source->best_discount
+                  ? $price_source->best_discount
+                  : $price_source->discount_from_source("");
+    $discount_src->discount(0) if !$price_source->best_discount;
+  }
 
   my %new_attr;
-  $new_attr{part}        = $part;
-  $new_attr{description} = $part->description if ! $item->description;
-  $new_attr{qty}         = 1.0                if ! $item->qty;
-  $new_attr{unit}        = $part->unit;
-  $new_attr{sellprice}   = $part->sellprice   if ! $item->sellprice;
-  $new_attr{discount}    = $cv_discount       if ! $item->discount;
+  $new_attr{part}                   = $part;
+  $new_attr{description}            = $part->description if ! $item->description;
+  $new_attr{qty}                    = 1.0                if ! $item->qty;
+  $new_attr{sellprice}              = $price_src->price;
+  $new_attr{discount}               = $discount_src->discount;
+  $new_attr{active_price_source}    = $price_src;
+  $new_attr{active_discount_source} = $discount_src;
 
   # add_custom_variables adds cvars to an orderitem with no cvars for saving, but
   # they cannot be retrieved via custom_variables until the order/orderitem is
@@ -364,6 +388,15 @@ sub action_recalc_amounts_and_taxes {
   $self->js->render();
 }
 
+sub action_price_popup {
+  my ($self) = @_;
+
+  my $idx  = first_index { $_ eq $::form->{item_id} } @{ $::form->{orderitem_ids} };
+  my $item = $self->order->items->[$idx];
+
+  $self->render_price_dialog($item);
+}
+
 sub _js_redisplay_linetotals {
   my ($self) = @_;
 
@@ -476,6 +509,27 @@ sub build_tax_rows {
 }
 
 
+sub render_price_dialog {
+  my ($self, $record_item) = @_;
+
+  my $price_source = SL::PriceSource->new(record_item => $record_item, record => $self->order);
+
+  $self->js
+    ->run(
+      'kivi.io.price_chooser_dialog',
+      t8('Available Prices'),
+      $self->render('order/tabs/_price_sources_dialog', { output => 0 }, price_source => $price_source)
+    )
+    ->reinit_widgets;
+
+#   if (@errors) {
+#     $self->js->text('#dialog_flash_error_content', join ' ', @errors);
+#     $self->js->show('#dialog_flash_error');
+#   }
+
+  $self->js->render;
+}
+
 sub _make_order {
   my ($self) = @_;
 
@@ -491,7 +545,6 @@ sub _make_order {
   return $order;
 }
 
-
 sub _recalc {
   my ($self) = @_;
 
@@ -520,12 +573,10 @@ sub _get_unalterable_data {
     if ($item->id) {
       # load data from orderitems (db)
       my $db_item = SL::DB::OrderItem->new(id => $item->id)->load;
-      $item->$_($db_item->$_) for qw(active_discount_source active_price_source longdescription);
+      $item->$_($db_item->$_) for qw(longdescription);
     } else {
       # set data from part (or other sources)
       $item->longdescription($item->part->notes);
-      #$item->active_price_source('');
-      #$item->active_discount_source('');
     }
 
     # autovivify all cvars that are not in the form (cvars_by_config can do it).
@@ -591,6 +642,13 @@ sub _pre_render {
 
   $self->{current_employee_id} = SL::DB::Manager::Employee->current->id;
 
+  foreach  my $item (@{$self->order->items}) {
+    my $price_source = SL::PriceSource->new(record_item => $item, record => $self->order);
+    $item->active_price_source(   $price_source->price_from_source(   $item->active_price_source   ));
+    $item->active_discount_source($price_source->discount_from_source($item->active_discount_source));
+
+  }
+
   $::request->{layout}->use_javascript("${_}.js")  for qw(ckeditor/ckeditor ckeditor/adapters/jquery);
 }
 
index f59c226..aacaa4b 100644 (file)
       <tr valign="top" class="listrow">
         <td>[% L.part_picker('add_item.parts_id', '', fat_set_item=1, style='width: 300px', class="add_item_input") %]</td>
         <td>[% L.input_tag('add_item.description', '', class="add_item_input") %]</td>
-        <td>[% L.input_tag('add_item.qty_as_number', '', size = 5, style='text-align:right', class="add_item_input") %]</td>
+        <td>
+          [% L.input_tag('add_item.qty_as_number', '', size = 5, style='text-align:right', class="add_item_input") %]
+          [% L.hidden_tag('add_item.unit', '', class="add_item_input") %]
+        </td>
         <td>[% L.input_tag('add_item.sellprice_as_number', '', size = 10, style='text-align:right', class="add_item_input") %]</td>
         <td>[% L.input_tag('add_item.discount_as_percent', '', size = 5, style='text-align:right', class="add_item_input") %]</td>
         <td>[% L.button_tag('add_item()', LxERP.t8('Add part')) %]</td>
diff --git a/templates/webpages/order/tabs/_price_sources_dialog.html b/templates/webpages/order/tabs/_price_sources_dialog.html
new file mode 100644 (file)
index 0000000..a73073e
--- /dev/null
@@ -0,0 +1,89 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE L %]
+[%- USE LxERP %]
+[% SET best_price = price_source.best_price %]
+[% SET best_discount = price_source.best_discount %]
+  <h2>[% 'Prices' | $T8 %]</h2>
+
+  <table>
+   <tr class='listheading'>
+    <th></th>
+    <th>[% 'Price Source' | $T8 %]</th>
+    <th>[% 'Price' | $T8 %]</th>
+    <th>[% 'Best Price' | $T8 %]</th>
+    <th>[% 'Details' | $T8 %]</th>
+   </tr>
+   <tr class='listrow'>
+[%- IF price_source.record_item.active_price_source %]
+    <td>[% L.button_tag('update_price_source(\'' _ FORM.item_id _ '\', \'\', \'' _ LxERP.t8('None (PriceSource)') _ '\')', LxERP.t8('Select')) %]</td>
+[%- ELSE %]
+    <td><b>[% 'Selected' | $T8 %]</b></td>
+[%- END %]
+    <td>[% 'None (PriceSource)' | $T8 %]</td>
+    <td>-</td>
+    <td></td>
+    <td></td>
+   </tr>
+   [%- FOREACH price IN price_source.available_prices %]
+    <tr class='listrow'>
+[%- IF price_source.record_item.active_price_source != price.source %]
+     <td>[% L.button_tag('update_price_source(\'' _ FORM.item_id _ '\', \'' _ price.source _ '\', \'' _ price.source_description _ '\', \'' _ LxERP.format_amount(price.price, -2) _ '\')', LxERP.t8('Select')) %]</td>
+[%- ELSIF price_source.record_item.sellprice * 1 != price.price * 1 %]
+     <td>[% L.button_tag('update_price_source(\'' _ FORM.item_id _ '\', \'' _ price.source _ '\', \'' _ price.source_description _ '\', \'' _ LxERP.format_amount(price.price, -2) _ '\')', LxERP.t8('Update Price')) %]</td>
+[%- ELSE %]
+    <td><b>[% 'Selected' | $T8 %]</b></td>
+[% END %]
+     <td>[% price.source_description | html %]</td>
+     <td>[% price.price_as_number %]</td>
+[% IF price.source == best_price.source %]
+     <td align='center'>&#x2022;</td>
+[% ELSE %]
+     <td></td>
+[% END %]
+     <td>[% price.description | html %]</td>
+    </tr>
+   [%- END %]
+  </table>
+
+  <h2>[% 'Discounts' | $T8 %]</h2>
+
+  <table>
+   <tr class='listheading'>
+    <th></th>
+    <th>[% 'Price Source' | $T8 %]</th>
+    <th>[% 'Discount' | $T8 %]</th>
+    <th>[% 'Best Discount' | $T8 %]</th>
+    <th>[% 'Details' | $T8 %]</th>
+   </tr>
+   <tr class='listrow'>
+[%- IF price_source.record_item.active_discount_source %]
+    <td>[% L.button_tag('update_discount_source(\'' _ FORM.item_id _ '\', \'\', \'' _ LxERP.t8('None (PriceSource Discount)') _ '\')', LxERP.t8('Select')) %]</td>
+[%- ELSE %]
+    <td><b>[% 'Selected' | $T8 %]</b></td>
+[%- END %]
+    <td>[% 'None (PriceSource Discount)' | $T8 %]</td>
+    <td>-</td>
+    <td></td>
+    <td></td>
+   </tr>
+   [%- FOREACH price IN price_source.available_discounts %]
+    <tr class='listrow'>
+[%- IF price_source.record_item.active_discount_source != price.source %]
+     <td>[% L.button_tag('update_discount_source(\'' _ FORM.item_id _ '\', \'' _ price.source _ '\', \'' _ price.source_description _ '\', \'' _ price.discount_as_percent _ '\')', LxERP.t8('Select')) %]</td>
+[%- ELSIF price_source.record_item.discount * 1 != price.discount * 1 %]
+     <td>[% L.button_tag('update_discount_source(\'' _ FORM.item_id _ '\', \'' _ price.source _ '\', \'' _ price.source_description _ '\', \'' _ price.discount_as_percent  _ '\')', LxERP.t8('Update Discount')) %]</td>
+[%- ELSE %]
+    <td><b>[% 'Selected' | $T8 %]</b></td>
+[% END %]
+     <td>[% price.source_description | html %]</td>
+     <td>[% price.discount_as_percent %] %</td>
+[% IF price.source == best_discount.source %]
+     <td align='center'>&#x2022;</td>
+[% ELSE %]
+     <td></td>
+[% END %]
+     <td>[% price.description | html %]</td>
+    </tr>
+   [%- END %]
+  </table>
index a74863d..009491b 100644 (file)
@@ -7,6 +7,7 @@
 
   <tr class="listrow0">
     <td style='display:none'>
+      [% L.hidden_tag("orderitem_ids[+]", ID) %]
       [% L.hidden_tag("order.orderitems[+].id", ITEM.id, id='item_' _ ID) %]
       [% L.hidden_tag("order.orderitems[].parts_id", ITEM.parts_id) %]
     </td>
                       class="recalc") %]
     </td>
     <td>
-      [%- L.input_tag("order.orderitems[].sellprice_as_number",
-                      ITEM.sellprice_as_number,
-                      size = 10,
-                      style='text-align:right',
-                      class="recalc") %]
+      [%- L.button_tag("price_chooser_item_row(this)",
+                       ITEM.active_price_source.source_description _ ' | ' _ ITEM.active_discount_source.source_description,
+                       name = "price_chooser_button") %]
     </td>
     <td>
-      [%- L.input_tag("order.orderitems[].discount_as_percent",
-                      ITEM.discount_as_percent,
-                      size = 5,
-                      style='text-align:right',
-                      class="recalc") %]
+      [%- L.hidden_tag("order.orderitems[].active_price_source", ITEM.active_price_source.source) %]
+      [%- SET EDIT_PRICE = (AUTH.assert('edit_prices', 1) && ITEM.active_price_source.source == '') %]
+      <div name="editable_price" [%- IF !EDIT_PRICE %]style="display:none"[%- END %]>
+        [%- L.input_tag("order.orderitems[].sellprice_as_number",
+                        ITEM.sellprice_as_number,
+                        size = 10,
+                        style='text-align:right',
+                        disabled=(EDIT_PRICE? '' : 1),
+                        class="recalc reformat_number") %]
+      </div>
+      <div name="not_editable_price" [%- IF EDIT_PRICE %]style="display:none"[%- END %]>
+        [%- L.div_tag(ITEM.sellprice_as_number, name="sellprice_text", style='text-align:right') %]
+        [%- L.hidden_tag("order.orderitems[].sellprice_as_number",
+                         ITEM.sellprice_as_number,
+                         disabled=(EDIT_PRICE? 1 : '')) %]
+      </div>
+    </td>
+    <td>
+      [%- L.hidden_tag("order.orderitems[].active_discount_source", ITEM.active_discount_source.source) %]
+      [%- SET EDIT_DISCOUNT = (AUTH.assert('edit_prices', 1) && ITEM.active_discount_source.source == '') %]
+      <div name="editable_discount" [%- IF !EDIT_DISCOUNT %]style="display:none"[%- END %]>
+        [%- L.input_tag("order.orderitems[].discount_as_percent",
+                        ITEM.discount_as_percent,
+                        size = 5,
+                        style='text-align:right',
+                        disabled=(EDIT_DISCOUNT? '' : 1),
+                        class="recalc reformat_number") %]
+      </div>
+      <div name="not_editable_discount" [%- IF EDIT_DISCOUNT %]style="display:none"[%- END %]>
+        [%- L.div_tag(ITEM.discount_as_percent, name="discount_text", style='text-align:right') %]
+        [%- L.hidden_tag("order.orderitems[].discount_as_percent",
+                         ITEM.discount_as_percent,
+                         disabled=(EDIT_DISCOUNT? 1 : '')) %]
+      </div>
     </td>
     <td align="right">
       [%- L.div_tag(LxERP.format_amount(ITEM.linetotal, 2, 0), name="linetotal") %]
index 8574424..934a386 100644 (file)
                 <th class="listheading" nowrap width="5" >[%- 'Qty'          | $T8 %] </th>
                 <th class="listheading" nowrap width="5" >[%- 'Price Factor' | $T8 %] </th>
                 <th class="listheading" nowrap width="5" >[%- 'Unit'         | $T8 %] </th>
+                <th class="listheading" nowrap width="5" >[%- 'Price Source' | $T8 %] </th>
                 <th class="listheading" nowrap width="15">[%- 'Price'        | $T8 %] </th>
                 <th class="listheading" nowrap width="5" >[%- 'Discount'     | $T8 %] </th>
                 <th class="listheading" nowrap width="10">[%- 'Extended'     | $T8 %] </th>
@@ -272,6 +273,87 @@ function delete_order_item_row(clicked) {
   recalc_amounts_and_taxes();
 }
 
+function price_chooser_item_row(clicked) {
+  var row = $(clicked).parents("tbody").first();
+  var item_id_dom = $(row).find('[name="orderitem_ids[+]"]');
+
+  var data = $('#order_form').serialize();
+  data += '&action=Order/price_popup';
+  data += '&item_id=' + item_id_dom.val();
+
+  $.post("controller.pl", data, kivi.eval_json_result);
+}
+
+function update_price_source(item_id, source, descr, price_str) {
+  var row = $('#item_' + item_id).parents("tbody").first();
+  var source_elt = $(row).find('[name="order.orderitems[].active_price_source"]');
+  var button_elt = $(row).find('[name="price_chooser_button"]');
+
+  button_elt.val(button_elt.val().replace(/.*\|/, descr + " |"));
+  source_elt.val(source);
+
+  var editable_div_elt = $(row).find('[name="editable_price"]');
+  var not_editable_div_elt = $(row).find('[name="not_editable_price"]');
+  if ([%- AUTH.assert('edit_prices', 1) %] == 1 && source == '') {
+    // editable
+    $(editable_div_elt).show();
+    $(not_editable_div_elt).hide();
+    $(editable_div_elt).find(':input').prop("disabled", false);
+    $(not_editable_div_elt).find(':input').prop("disabled", true);
+  } else {
+    // not editable
+    $(editable_div_elt).hide();
+    $(not_editable_div_elt).show();
+    $(editable_div_elt).find(':input').prop("disabled", true);
+    $(not_editable_div_elt).find(':input').prop("disabled", false);
+  }
+
+  if (price_str) {
+    var price_elt = $(row).find('[name="order.orderitems[].sellprice_as_number"]');
+    var html_elt  = $(row).find('[name="sellprice_text"]');
+    price_elt.val(price_str);
+    html_elt.html(price_str);
+    recalc_amounts_and_taxes();
+  }
+
+  kivi.io.close_dialog();
+}
+
+function update_discount_source(item_id, source, descr, discount_str) {
+  var row = $('#item_' + item_id).parents("tbody").first();
+  var source_elt = $(row).find('[name="order.orderitems[].active_discount_source"]');
+  var button_elt = $(row).find('[name="price_chooser_button"]');
+
+  button_elt.val(button_elt.val().replace(/\|.*/, "| " + descr));
+  source_elt.val(source);
+
+  var editable_div_elt = $(row).find('[name="editable_discount"]');
+  var not_editable_div_elt = $(row).find('[name="not_editable_discount"]');
+  if ([%- AUTH.assert('edit_prices', 1) %] == 1 && source == '') {
+    // editable
+    $(editable_div_elt).show();
+    $(not_editable_div_elt).hide();
+    $(editable_div_elt).find(':input').prop("disabled", false);
+    $(not_editable_div_elt).find(':input').prop("disabled", true);
+  } else {
+    // not editable
+    $(editable_div_elt).hide();
+    $(not_editable_div_elt).show();
+    $(editable_div_elt).find(':input').prop("disabled", true);
+    $(not_editable_div_elt).find(':input').prop("disabled", false);
+  }
+
+  if (discount_str) {
+    var discount_elt = $(row).find('[name="order.orderitems[].discount_as_percent"]');
+    var html_elt     = $(row).find('[name="discount_text"]');
+    discount_elt.val(discount_str);
+    html_elt.html(discount_str);
+    recalc_amounts_and_taxes();
+  }
+
+  kivi.io.close_dialog();
+}
+
 function reformat_number(event) {
   $(event.target).val(kivi.format_amount(kivi.parse_amount($(event.target).val()), -2));
 }
@@ -357,8 +439,13 @@ close_email_dialog = function() {
 
 $(function(){
   $('#order_[%- cv_id %]').change(reload_cv_dependend_selections);
-  $('#add_item_parts_id').on('set_item:PartPicker', function(e,o) { $('#add_item_sellprice_as_number').val(kivi.format_amount(o.sellprice, -2)) });
+  [%- IF SELF.cv == 'customer' %]
+    $('#add_item_parts_id').on('set_item:PartPicker', function(e,o) { $('#add_item_sellprice_as_number').val(kivi.format_amount(o.sellprice, -2)) });
+  [%- ELSE %]
+    $('#add_item_parts_id').on('set_item:PartPicker', function(e,o) { $('#add_item_sellprice_as_number').val(kivi.format_amount(o.lastcost, -2)) });
+  [%- END %]
   $('#add_item_parts_id').on('set_item:PartPicker', function(e,o) { $('#add_item_description').val(o.description) });
+  $('#add_item_parts_id').on('set_item:PartPicker', function(e,o) { $('#add_item_unit').val(o.unit) });
   $('.add_item_input').keydown(function(event) {
     if(event.keyCode == 13) {
       event.preventDefault();