Auftrags-Controller: Speichern und schließen, …
[kivitendo-erp.git] / js / kivi.Order.js
index 1ab27bc..05cea60 100644 (file)
@@ -1,6 +1,6 @@
 namespace('kivi.Order', function(ns) {
   ns.check_cv = function() {
-    if ($('#type').val() == 'sales_order') {
+    if ($('#type').val() == 'sales_order' || $('#type').val() == 'sales_quotation') {
       if ($('#order_customer_id').val() === '') {
         alert(kivi.t8('Please select a customer.'));
         return false;
@@ -14,8 +14,8 @@ namespace('kivi.Order', function(ns) {
     return true;
   };
 
-  ns.check_save_duplicate_parts = function() {
-    var id_arr = $('[name="order.orderitems[].parts_id"]').map(function() {return this.value;}).get();
+  ns.check_duplicate_parts = function(question) {
+    var id_arr = $('[name="order.orderitems[].parts_id"]').map(function() { return this.value; }).get();
 
     var i, obj = {}, pos = [];
 
@@ -28,29 +28,32 @@ namespace('kivi.Order', function(ns) {
     }
 
     if (pos.length > 0) {
+      question = question || kivi.t8("Do you really want to continue?");
       return confirm(kivi.t8("There are duplicate parts at positions") + "\n"
                      + pos.join(', ') + "\n"
-                     + kivi.t8("Do you really want to save?"));
+                     + question);
     }
     return true;
   };
 
-  ns.save = function(warn_on_duplicates) {
-    if (!ns.check_cv()) return;
-    if (warn_on_duplicates && !ns.check_save_duplicate_parts()) return;
-
-    var data = $('#order_form').serializeArray();
-    data.push({ name: 'action', value: 'Order/save' });
-
-    $.post("controller.pl", data, kivi.eval_json_result);
+  ns.check_valid_reqdate = function() {
+    if ($('#order_reqdate_as_date').val() === '') {
+      alert(kivi.t8('Please select a delivery date.'));
+      return false;
+    } else {
+      return true;
+    }
   };
 
-  ns.save_and_delivery_order = function(warn_on_duplicates) {
+  ns.save = function(action, warn_on_duplicates, warn_on_reqdate, back_to_caller) {
     if (!ns.check_cv()) return;
-    if (warn_on_duplicates && !ns.check_save_duplicate_parts()) return;
+    if (warn_on_duplicates && !ns.check_duplicate_parts()) return;
+    if (warn_on_reqdate    && !ns.check_valid_reqdate())   return;
 
     var data = $('#order_form').serializeArray();
-    data.push({ name: 'action', value: 'Order/save_and_delivery_order' });
+    data.push({ name: 'action', value: 'Order/' + action });
+
+    if (back_to_caller) data.push({ name: 'back_to_caller', value: '1' });
 
     $.post("controller.pl", data, kivi.eval_json_result);
   };
@@ -62,8 +65,10 @@ namespace('kivi.Order', function(ns) {
     $.post("controller.pl", data, kivi.eval_json_result);
   };
 
-  ns.show_print_options = function() {
+  ns.show_print_options = function(warn_on_duplicates, warn_on_reqdate) {
     if (!ns.check_cv()) return;
+    if (warn_on_duplicates && !ns.check_duplicate_parts(kivi.t8("Do you really want to print?"))) return;
+    if (warn_on_reqdate    && !ns.check_valid_reqdate())   return;
 
     kivi.popup_dialog({
       id: 'print_options',
@@ -85,35 +90,40 @@ namespace('kivi.Order', function(ns) {
     $.post("controller.pl", data, kivi.eval_json_result);
   };
 
-  ns.download_pdf = function(pdf_filename, key) {
-    var data = [];
-    data.push({ name: 'action', value: 'Order/download_pdf' });
-    data.push({ name: 'type', value: $('#type').val() });
-    data.push({ name: 'pdf_filename', value: pdf_filename });
-    data.push({ name: 'key', value: key });
-    $.download("controller.pl", data);
-  };
+  var email_dialog;
 
-  ns.email = function() {
-    if (!ns.check_cv()) return;
-    var data = $('#order_form').serializeArray();
-    data.push({ name: 'action', value: 'Order/show_email_dialog' });
+  ns.setup_send_email_dialog = function() {
+    kivi.SalesPurchase.show_all_print_options_elements();
+    kivi.SalesPurchase.show_print_options_elements([ 'sendmode', 'media', 'copies', 'remove_draft' ], false);
 
-    $.post("controller.pl", data, kivi.eval_json_result);
+    $('#print_options_form table').first().remove().appendTo('#email_form_print_options');
+
+    $('select#format').change(kivi.Order.adjust_email_attachment_name_for_template_format);
+    kivi.Order.adjust_email_attachment_name_for_template_format();
+
+    var to_focus = $('#email_form_to').val() === '' ? 'to' : 'subject';
+    $('#email_form_' + to_focus).focus();
   };
 
-  var email_dialog;
+  ns.finish_send_email_dialog = function() {
+    kivi.SalesPurchase.show_all_print_options_elements();
+
+    $('#email_form_print_options table').first().remove().prependTo('#print_options_form');
+    return true;
+  };
 
   ns.show_email_dialog = function(html) {
-    var id            = 'jqueryui_popup_dialog';
+    var id            = 'send_email_dialog';
     var dialog_params = {
       id:     id,
       width:  800,
-      height: 500,
+      height: 600,
+      title:  kivi.t8('Send email'),
       modal:  true,
+      beforeClose: kivi.Order.finish_send_email_dialog,
       close: function(event, ui) {
         email_dialog.remove();
-      },
+      }
     };
 
     $('#' + id).remove();
@@ -122,23 +132,54 @@ namespace('kivi.Order', function(ns) {
     email_dialog.html(html);
     email_dialog.dialog(dialog_params);
 
+    kivi.Order.setup_send_email_dialog();
+
     $('.cancel').click(ns.close_email_dialog);
 
     return true;
   };
 
   ns.send_email = function() {
+    // push button only once -> slow response from mail server
+    ns.email_dialog_disable_send();
+
     var data = $('#order_form').serializeArray();
-    data = data.concat($('#email_form').serializeArray());
+    data = data.concat($('[name^="email_form."]').serializeArray());
+    data = data.concat($('[name^="print_options."]').serializeArray());
     data.push({ name: 'action', value: 'Order/send_email' });
     $.post("controller.pl", data, kivi.eval_json_result);
   };
 
+  ns.email_dialog_disable_send = function() {
+    // disable mail send event to prevent
+    // impatient users to send multiple times
+    $('#send_email').prop('disabled', true);
+  };
+
   ns.close_email_dialog = function() {
     email_dialog.dialog("close");
   };
 
-  ns.reload_cv_dependant_selections = function() {
+  ns.adjust_email_attachment_name_for_template_format = function() {
+    var $filename_elt = $('#email_form_attachment_filename');
+    var $format_elt   = $('select#format');
+
+    if (!$filename_elt || !$format_elt)
+      return;
+
+    var format   = $format_elt.val().toLowerCase();
+    var new_ext  = format == 'html' ? 'html' : format == 'opendocument' ? 'odt' : 'pdf';
+    var filename = $filename_elt.val();
+
+    $filename_elt.val(filename.replace(/[^.]+$/, new_ext));
+  };
+
+  ns.set_number_in_title = function(elt) {
+    $('#nr_in_title').html($(elt).val());
+  };
+
+  ns.reload_cv_dependent_selections = function() {
+    $('#order_shipto_id').val('');
     var data = $('#order_form').serializeArray();
     data.push({ name: 'action', value: 'Order/customer_vendor_changed' });
 
@@ -149,7 +190,71 @@ namespace('kivi.Order', function(ns) {
     $(event.target).val(kivi.format_amount(kivi.parse_amount($(event.target).val()), -2));
   };
 
+  ns.reformat_number_as_null_number = function(event) {
+    if ($(event.target).val() === '') {
+      return;
+    }
+    ns.reformat_number(event);
+  };
+
+  ns.update_exchangerate = function(event) {
+    if (!ns.check_cv()) {
+      $('#order_currency_id').val($('#old_currency_id').val());
+      return;
+    }
+
+    var rate_input = $('#order_exchangerate_as_null_number');
+    // unset exchangerate if currency changed
+    if ($('#order_currency_id').val() !== $('#old_currency_id').val()) {
+      rate_input.val('');
+    }
+
+    // only set exchangerate if unset
+    if (rate_input.val() !== '') {
+      return;
+    }
+
+    var data = $('#order_form').serializeArray();
+    data.push({ name: 'action', value: 'Order/update_exchangerate' });
+
+    $.ajax({
+      url: 'controller.pl',
+      data: data,
+      method: 'POST',
+      dataType: 'json',
+      success: function(data){
+        if (!data.is_standard) {
+          $('#currency_name').text(data.currency_name);
+          if (data.exchangerate) {
+            rate_input.val(data.exchangerate);
+          } else {
+            rate_input.val('');
+          }
+          $('#exchangerate_settings').show();
+        } else {
+          rate_input.val('');
+          $('#exchangerate_settings').hide();
+        }
+        if ($('#order_currency_id').val() != $('#old_currency_id').val() ||
+            !data.is_standard && data.exchangerate != $('#old_exchangerate').val()) {
+          kivi.display_flash('warning', kivi.t8('You have changed the currency or exchange rate. Please check prices.'));
+        }
+        $('#old_currency_id').val($('#order_currency_id').val());
+        $('#old_exchangerate').val(data.exchangerate);
+      }
+    });
+  };
+
+  ns.exchangerate_changed = function(event) {
+    if (kivi.parse_amount($('#order_exchangerate_as_null_number').val()) != kivi.parse_amount($('#old_exchangerate').val())) {
+      kivi.display_flash('warning', kivi.t8('You have changed the currency or exchange rate. Please check prices.'));
+      $('#old_exchangerate').val($('#order_exchangerate_as_null_number').val());
+    }
+  };
+
   ns.recalc_amounts_and_taxes = function() {
+    if (!kivi.validate_form('#order_form')) return;
+
     var data = $('#order_form').serializeArray();
     data.push({ name: 'action', value: 'Order/recalc_amounts_and_taxes' });
 
@@ -157,25 +262,25 @@ namespace('kivi.Order', function(ns) {
   };
 
   ns.unit_change = function(event) {
-    var row = $(event.target).parents("tbody").first();
-    var item_id_dom = $(row).find('[name="orderitem_ids[+]"]');
+    var row           = $(event.target).parents("tbody").first();
+    var item_id_dom   = $(row).find('[name="orderitem_ids[+]"]');
     var sellprice_dom = $(row).find('[name="order.orderitems[].sellprice_as_number"]');
-    var select_elt = $(row).find('[name="order.orderitems[].unit"]');
+    var select_elt    = $(row).find('[name="order.orderitems[].unit"]');
 
     var oldval = $(select_elt).data('oldval');
     $(select_elt).data('oldval', $(select_elt).val());
 
     var data = $('#order_form').serializeArray();
-    data.push({ name: 'action', value: 'Order/unit_changed' });
-    data.push({ name: 'item_id', value: item_id_dom.val() });
-    data.push({ name: 'old_unit', value: oldval });
-    data.push({ name: 'sellprice_dom_id', value: sellprice_dom.attr('id') });
+    data.push({ name: 'action',           value: 'Order/unit_changed'     },
+              { name: 'item_id',          value: item_id_dom.val()        },
+              { name: 'old_unit',         value: oldval                   },
+              { name: 'sellprice_dom_id', value: sellprice_dom.attr('id') });
 
     $.post("controller.pl", data, kivi.eval_json_result);
   };
 
   ns.update_sellprice = function(item_id, price_str) {
-    var row = $('#item_' + item_id).parents("tbody").first();
+    var row       = $('#item_' + item_id).parents("tbody").first();
     var price_elt = $(row).find('[name="order.orderitems[].sellprice_as_number"]');
     var html_elt  = $(row).find('[name="sellprice_text"]');
     price_elt.val(price_str);
@@ -184,14 +289,14 @@ namespace('kivi.Order', function(ns) {
 
   ns.load_second_row = function(row) {
     var item_id_dom = $(row).find('[name="orderitem_ids[+]"]');
-    var div_elt = $(row).find('[name="second_row"]');
+    var div_elt     = $(row).find('[name="second_row"]');
 
     if ($(div_elt).data('loaded') == 1) {
       return;
     }
     var data = $('#order_form').serializeArray();
-    data.push({ name: 'action', value: 'Order/load_second_rows' });
-    data.push({ name: 'item_ids[]', value: item_id_dom.val() });
+    data.push({ name: 'action',     value: 'Order/load_second_rows' },
+              { name: 'item_ids[]', value: item_id_dom.val()        });
 
     $.post("controller.pl", data, kivi.eval_json_result);
   };
@@ -219,24 +324,24 @@ namespace('kivi.Order', function(ns) {
 
   ns.hide_second_row = function(row) {
     $(row).children().not(':first').hide();
-    $(row).data('expanded', false);
+    $(row).data('expanded', 0);
     var elt = $(row).find('.expand');
-    elt.attr('src', "image/expand3.gif");
+    elt.attr('src', "image/expand.svg");
     elt.attr('alt', kivi.t8('Show details'));
     elt.attr('title', kivi.t8('Show details'));
   };
 
   ns.show_second_row = function(row) {
     $(row).children().not(':first').show();
-    $(row).data('expanded', true);
+    $(row).data('expanded', 1);
     var elt = $(row).find('.expand');
-    elt.attr('src', "image/collapse3.gif");
+    elt.attr('src', "image/collapse.svg");
     elt.attr('alt', kivi.t8('Hide details'));
     elt.attr('title', kivi.t8('Hide details'));
   };
 
   ns.toggle_second_row = function(row) {
-    if ($(row).data('expanded') === true) {
+    if ($(row).data('expanded') == 1) {
       ns.hide_second_row(row);
     } else {
       ns.show_second_row(row);
@@ -259,18 +364,19 @@ namespace('kivi.Order', function(ns) {
 
     kivi.run_once_for('.row_entry', 'on_kbd_click_show_hide', function(elt) {
       $(elt).keydown(function(event) {
-        if(event.keyCode == 40 && event.shiftKey === true) {
+        var row;
+        if (event.keyCode == 40 && event.shiftKey === true) {
           // shift arrow down
           event.preventDefault();
-          var row = $(event.target).parents(".row_entry").first();
+          row = $(event.target).parents(".row_entry").first();
           ns.load_second_row(row);
           ns.show_second_row(row);
           return false;
         }
-        if(event.keyCode == 38 && event.shiftKey === true) {
+        if (event.keyCode == 38 && event.shiftKey === true) {
           // shift arrow up
           event.preventDefault();
-          var row = $(event.target).parents(".row_entry").first();
+          row = $(event.target).parents(".row_entry").first();
           ns.hide_second_row(row);
           return false;
         }
@@ -304,10 +410,19 @@ namespace('kivi.Order', function(ns) {
     });
   };
 
+  ns.redisplay_cvpartnumbers = function(data) {
+    $('.row_entry').each(function(idx, elt) {
+      $(elt).find('[name="cvpartnumber"]').html(data[idx][0]);
+    });
+  };
+
   ns.renumber_positions = function() {
     $('.row_entry [name="position"]').each(function(idx, elt) {
       $(elt).html(idx+1);
     });
+    $('.row_entry').each(function(idx, elt) {
+      $(elt).data("position", idx+1);
+    });
   };
 
   ns.reorder_items = function(order_by) {
@@ -326,9 +441,9 @@ namespace('kivi.Order', function(ns) {
     $('#' + order_by + '_header_id a').append('<img border=0 data-sort-dir=' + dir + ' src=' + src + ' alt="' + kivi.t8('sort items') + '">');
 
     var data = $('#order_form').serializeArray();
-    data.push({ name: 'action', value: 'Order/reorder_items' });
-    data.push({ name: 'order_by', value: order_by });
-    data.push({ name: 'sort_dir', value: dir });
+    data.push({ name: 'action',   value: 'Order/reorder_items' },
+              { name: 'order_by', value: order_by              },
+              { name: 'sort_dir', value: dir                   });
 
     $.post("controller.pl", data, kivi.eval_json_result);
   };
@@ -343,40 +458,51 @@ namespace('kivi.Order', function(ns) {
     ns.renumber_positions();
   };
 
+  ns.get_insert_before_item_id = function(wanted_pos) {
+    if (wanted_pos === '') return;
+
+    var insert_before_item_id;
+    // selection by data does not seem to work if data is changed at runtime
+    // var elt = $('.row_entry [data-position="' + wanted_pos + '"]');
+    $('.row_entry').each(function(idx, elt) {
+      if ($(elt).data("position") == wanted_pos) {
+        insert_before_item_id = $(elt).find('[name="orderitem_ids[+]"]').val();
+        return false;
+      }
+    });
+
+    return insert_before_item_id;
+  };
+
   ns.add_item = function() {
     if ($('#add_item_parts_id').val() === '') return;
     if (!ns.check_cv()) return;
 
     $('#row_table_id thead a img').remove();
 
+    var insert_before_item_id = ns.get_insert_before_item_id($('#add_item_position').val());
+
     var data = $('#order_form').serializeArray();
-    data.push({ name: 'action', value: 'Order/add_item' });
+    data.push({ name: 'action', value: 'Order/add_item' },
+              { name: 'insert_before_item_id', value: insert_before_item_id });
 
     $.post("controller.pl", data, kivi.eval_json_result);
   };
 
-  ns.show_multi_items_dialog = function() {
+  ns.open_multi_items_dialog = function() {
     if (!ns.check_cv()) return;
 
-    $('#row_table_id thead a img').remove();
-
-    kivi.popup_dialog({
-      url: 'controller.pl?action=Order/show_multi_items_dialog',
-      data: { type: $('#type').val(),
-              callback: 'Order/add_multi_items',
-              callback_data_id: 'order_form' },
-      id: 'jq_multi_items_dialog',
-      dialog: {
-        title: kivi.t8('Add multiple items'),
-        width:  800,
-        height: 500
-      }
-    });
-    return true;
+    var pp = $("#add_item_parts_id").data("part_picker");
+    pp.o.multiple=1;
+    pp.open_dialog();
   };
 
-  ns.close_multi_items_dialog = function() {
-    $('#jq_multi_items_dialog').dialog('close');
+  ns.add_multi_items = function(data) {
+    var insert_before_item_id = ns.get_insert_before_item_id($('#multi_items_position').val());
+    data = data.concat($('#order_form').serializeArray());
+    data.push({ name: 'action', value: 'Order/add_multi_items' },
+              { name: 'insert_before_item_id', value: insert_before_item_id });
+    $.post("controller.pl", data, kivi.eval_json_result);
   };
 
   ns.delete_order_item_row = function(clicked) {
@@ -392,67 +518,46 @@ namespace('kivi.Order', function(ns) {
   };
 
   ns.show_longdescription_dialog = function(clicked) {
-    var row = $(clicked).parents("tbody").first();
-    var position = $(row).find('[name="position"]').html();
-    var partnumber = $(row).find('[name="partnumber"]').html();
-    var description_elt = $(row).find('[name="order.orderitems[].description"]');
-    var description = description_elt.val();
+    var row                 = $(clicked).parents("tbody").first();
+    var position            = $(row).find('[name="position"]').html();
+    var partnumber          = $(row).find('[name="partnumber"]').html();
+    var description_elt     = $(row).find('[name="order.orderitems[].description"]');
     var longdescription_elt = $(row).find('[name="order.orderitems[].longdescription"]');
-    var longdescription;
-
-    if (!longdescription_elt.length) {
-      var data = [];
-      data.push({ name: 'action', value: 'Order/get_item_longdescription' });
-      data.push({ name: 'type', value: $('#type').val() });
-      data.push({ name: 'item_id', value: $(row).find('[name="order.orderitems[+].id"]').val() });
-      data.push({ name: 'parts_id', value: $(row).find('[name="order.orderitems[].parts_id"]').val() });
-      $.ajax({
-        url: 'controller.pl',
-        data: data,
-        method: "GET",
-        async: false,
-        dataType: 'text',
-        success: function(val){
-          longdescription = val;
-        }
-      });
-    } else {
-      longdescription = longdescription_elt.val();
-    }
 
-    var params = { runningnumber: position,
-                   partnumber: partnumber,
-                   description: description,
-                   default_longdescription: longdescription,
-                   set_function: function(val){
-                     longdescription_elt.remove();
-                     $('<input type="hidden" name="order.orderitems[].longdescription">').insertAfter(description_elt).val(val);
-                   }
-                 };
+    var params = {
+      runningnumber:           position,
+      partnumber:              partnumber,
+      description:             description_elt.val(),
+      default_longdescription: longdescription_elt.val(),
+      set_function:            function(val) {
+        longdescription_elt.val(val);
+      }
+    };
 
     kivi.SalesPurchase.edit_longdescription_with_params(params);
   };
 
   ns.price_chooser_item_row = function(clicked) {
-    var row = $(clicked).parents("tbody").first();
+    if (!ns.check_cv()) return;
+    var row         = $(clicked).parents("tbody").first();
     var item_id_dom = $(row).find('[name="orderitem_ids[+]"]');
 
     var data = $('#order_form').serializeArray();
-    data.push({ name: 'action', value: 'Order/price_popup' });
-    data.push({ name: 'item_id', value: item_id_dom.val() });
+    data.push({ name: 'action',  value: 'Order/price_popup' },
+              { name: 'item_id', value: item_id_dom.val()   });
 
     $.post("controller.pl", data, kivi.eval_json_result);
   };
 
   ns.update_price_source = function(item_id, source, descr, price_str, price_editable) {
-    var row = $('#item_' + item_id).parents("tbody").first();
+    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 editable_div_elt     = $(row).find('[name="editable_price"]');
     var not_editable_div_elt = $(row).find('[name="not_editable_price"]');
     if (price_editable == 1 && source === '') {
       // editable
@@ -480,14 +585,14 @@ namespace('kivi.Order', function(ns) {
   };
 
   ns.update_discount_source = function(item_id, source, descr, discount_str, price_editable) {
-    var row = $('#item_' + item_id).parents("tbody").first();
+    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 editable_div_elt     = $(row).find('[name="editable_discount"]');
     var not_editable_div_elt = $(row).find('[name="not_editable_discount"]');
     if (price_editable == 1 && source === '') {
       // editable
@@ -514,16 +619,290 @@ namespace('kivi.Order', function(ns) {
     kivi.io.close_dialog();
   };
 
+  ns.show_periodic_invoices_config_dialog = function() {
+    if ($('#type').val() !== 'sales_order') return;
+
+    kivi.popup_dialog({
+      url:    'controller.pl?action=Order/show_periodic_invoices_config_dialog',
+      data:   { type:              $('#type').val(),
+                id:                $('#id').val(),
+                config:            $('#order_periodic_invoices_config').val(),
+                customer_id:       $('#order_customer_id').val(),
+                transdate_as_date: $('#order_transdate_as_date').val(),
+                language_id:       $('#language_id').val()
+              },
+      id:     'jq_periodic_invoices_config_dialog',
+      load:   kivi.reinit_widgets,
+      dialog: {
+        title:  kivi.t8('Edit the configuration for periodic invoices'),
+        width:  800,
+        height: 650
+      }
+    });
+    return true;
+  };
+
+  ns.close_periodic_invoices_config_dialog = function() {
+    $('#jq_periodic_invoices_config_dialog').dialog('close');
+  };
+
+  ns.assign_periodic_invoices_config = function() {
+    var data = $('[name="Form"]').serializeArray();
+    data.push({ name: 'type',   value: $('#type').val() },
+              { name: 'action', value: 'Order/assign_periodic_invoices_config' });
+    $.post("controller.pl", data, kivi.eval_json_result);
+  };
+
+  ns.check_save_active_periodic_invoices = function() {
+    var type = $('#type').val();
+    if (type !== 'sales_order') return true;
+
+    var active = false;
+    $.ajax({
+      url:      'controller.pl',
+      data:     { action: 'Order/get_has_active_periodic_invoices',
+                  type  : type,
+                  id    : $('#id').val(),
+                  config: $('#order_periodic_invoices_config').val(),
+                },
+      method:   "GET",
+      async:    false,
+      dataType: 'text',
+      success:  function(val) {
+        active = val;
+      }
+    });
+
+    if (active == 1) {
+      return confirm(kivi.t8('This sales order has an active configuration for periodic invoices. If you save then all subsequently created invoices will contain those changes as well, but not those that have already been created. Do you want to continue?'));
+    }
+
+    return true;
+  };
+
+  ns.show_vc_details_dialog = function() {
+    if (!ns.check_cv()) return;
+    var vc;
+    var vc_id;
+    var title;
+    if ($('#type').val() == 'sales_order' || $('#type').val() == 'sales_quotation' ) {
+      vc    = 'customer';
+      vc_id = $('#order_customer_id').val();
+      title = kivi.t8('Customer details');
+    } else {
+      vc    = 'vendor';
+      vc_id = $('#order_vendor_id').val();
+      title = kivi.t8('Vendor details');
+    }
+
+    kivi.popup_dialog({
+      url:    'controller.pl',
+      data:   { action: 'Order/show_customer_vendor_details_dialog',
+                type  : $('#type').val(),
+                vc    : vc,
+                vc_id : vc_id
+              },
+      id:     'jq_customer_vendor_details_dialog',
+      dialog: {
+        title:  title,
+        width:  800,
+        height: 650
+      }
+    });
+    return true;
+  };
+
+  ns.update_row_from_master_data = function(clicked) {
+    var row = $(clicked).parents("tbody").first();
+    var item_id_dom = $(row).find('[name="orderitem_ids[+]"]');
+
+    var data = $('#order_form').serializeArray();
+    data.push({ name: 'action', value: 'Order/update_row_from_master_data' });
+    data.push({ name: 'item_ids[]', value: item_id_dom.val() });
+
+    $.post("controller.pl", data, kivi.eval_json_result);
+  };
+
+  ns.update_all_rows_from_master_data = function() {
+    var item_ids = $.map($('.row_entry'), function(elt) {
+      var item_id = $(elt).find('[name="orderitem_ids[+]"]').val();
+      return { name: 'item_ids[]', value: item_id };
+    });
+
+    if (item_ids.length == 0) {
+      return;
+    }
+
+    var data = $('#order_form').serializeArray();
+    data.push({ name: 'action', value: 'Order/update_row_from_master_data' });
+    data = data.concat(item_ids);
+
+    $.post("controller.pl", data, kivi.eval_json_result);
+  };
+
+  ns.show_calculate_qty_dialog = function(clicked) {
+    var row        = $(clicked).parents("tbody").first();
+    var input_id   = $(row).find('[name="order.orderitems[].qty_as_number"]').attr('id');
+    var formula_id = $(row).find('[name="formula[+]"]').attr('id');
+
+    calculate_qty_selection_dialog("", input_id, "", formula_id);
+    return true;
+  };
+
+  ns.edit_custom_shipto = function() {
+    if (!ns.check_cv()) return;
+
+    kivi.SalesPurchase.edit_custom_shipto();
+  };
+
+  ns.purchase_order_check_for_direct_delivery = function() {
+    if ($('#type').val() != 'sales_order') {
+      kivi.submit_form_with_action($('#order_form'), 'Order/purchase_order');
+    }
+
+    var empty = true;
+    var shipto;
+    if ($('#order_shipto_id').val() !== '') {
+      empty = false;
+      shipto = $('#order_shipto_id option:selected').text();
+    } else {
+      $('#shipto_inputs [id^="shipto"]').each(function(idx, elt) {
+        if (!empty)                                     return true;
+        if (/^shipto_to_copy/.test($(elt).prop('id')))  return true;
+        if (/^shiptocp_gender/.test($(elt).prop('id'))) return true;
+        if (/^shiptocvar_/.test($(elt).prop('id')))     return true;
+        if ($(elt).val() !== '') {
+          empty = false;
+          return false;
+        }
+      });
+      var shipto_elements = [];
+      $([$('#shiptoname').val(), $('#shiptostreet').val(), $('#shiptozipcode').val(), $('#shiptocity').val()]).each(function(idx, elt) {
+        if (elt !== '') shipto_elements.push(elt);
+      });
+      shipto = shipto_elements.join('; ');
+    }
+
+    var use_it = false;
+    if (!empty) {
+      ns.direct_delivery_dialog(shipto);
+    } else {
+      kivi.submit_form_with_action($('#order_form'), 'Order/purchase_order');
+    }
+  };
+
+  ns.direct_delivery_callback = function(accepted) {
+    $('#direct-delivery-dialog').dialog('close');
+
+    if (accepted) {
+      $('<input type="hidden" name="use_shipto">').appendTo('#order_form').val('1');
+    }
+
+    kivi.submit_form_with_action($('#order_form'), 'Order/purchase_order');
+  };
+
+  ns.direct_delivery_dialog = function(shipto) {
+    $('#direct-delivery-dialog').remove();
+
+    var text1 = kivi.t8('You have entered or selected the following shipping address for this customer:');
+    var text2 = kivi.t8('Do you want to carry this shipping address over to the new purchase order so that the vendor can deliver the goods directly to your customer?');
+    var html  = '<div id="direct-delivery-dialog"><p>' + text1 + '</p><p>' + shipto + '</p><p>' + text2 + '</p>';
+    html      = html + '<hr><p>';
+    html      = html + '<input type="button" value="' + kivi.t8('Yes') + '" size="30" onclick="kivi.Order.direct_delivery_callback(true)">';
+    html      = html + '&nbsp;';
+    html      = html + '<input type="button" value="' + kivi.t8('No')  + '" size="30" onclick="kivi.Order.direct_delivery_callback(false)">';
+    html      = html + '</p></div>';
+    $(html).hide().appendTo('#order_form');
+
+    kivi.popup_dialog({id: 'direct-delivery-dialog',
+                       dialog: {title:  kivi.t8('Carry over shipping address'),
+                                height: 300,
+                                width:  500 }});
+  };
+
+  ns.follow_up_window = function() {
+    var id   = $('#id').val();
+    var type = $('#type').val();
+
+    var number_info = '';
+    if ($('#type').val() == 'sales_order' || $('#type').val() == 'purchase_order') {
+      number_info = $('#order_ordnumber').val();
+    } else if ($('#type').val() == 'sales_quotation' || $('#type').val() == 'request_quotation') {
+      number_info = $('#order_quonumber').val();
+    }
+
+    var name_info = '';
+    if ($('#type').val() == 'sales_order' || $('#type').val() == 'sales_quotation') {
+      name_info = $('#order_customer_id_name').val();
+    } else if ($('#type').val() == 'purchase_order' || $('#type').val() == 'request_quotation') {
+      name_info = $('#order_vendor_id_name').val();
+    }
+
+    var info = '';
+    if (number_info !== '') { info += ' (' + number_info + ')' }
+    if (name_info   !== '') { info += ' (' + name_info + ')' }
+
+    if (!$('#follow_up_rowcount').length) {
+      $('<input type="hidden" name="follow_up_rowcount"        id="follow_up_rowcount">').appendTo('#order_form');
+      $('<input type="hidden" name="follow_up_trans_id_1"      id="follow_up_trans_id_1">').appendTo('#order_form');
+      $('<input type="hidden" name="follow_up_trans_type_1"    id="follow_up_trans_type_1">').appendTo('#order_form');
+      $('<input type="hidden" name="follow_up_trans_info_1"    id="follow_up_trans_info_1">').appendTo('#order_form');
+      $('<input type="hidden" name="follow_up_trans_subject_1" id="follow_up_trans_subject_1">').appendTo('#order_form');
+    }
+    $('#follow_up_rowcount').val(1);
+    $('#follow_up_trans_id_1').val(id);
+    $('#follow_up_trans_type_1').val(type);
+    $('#follow_up_trans_info_1').val(info);
+    $('#follow_up_trans_subject_1').val($('#order_transaction_description').val());
+
+    follow_up_window();
+  };
+
+  ns.create_part = function() {
+    var data = $('#order_form').serializeArray();
+    data.push({ name: 'action', value: 'Order/create_part' });
+
+    $.post("controller.pl", data, kivi.eval_json_result);
+  };
+
+  ns.check_transport_cost_article_presence = function() {
+    var $form          = $('#order_form');
+    var wanted_part_id = $form.data('transport-cost-reminder-article-id');
+
+    if (!wanted_part_id) return true
+
+    var id_arr = $('[name="order.orderitems[].parts_id"]').map(function() { return this.value; }).get();
+    id_arr = $.grep(id_arr, function(elt) {
+      return ((elt*1) === wanted_part_id);
+    });
+
+    if (id_arr.length) return true;
+
+    var description = $form.data('transport-cost-reminder-article-description');
+    return confirm(kivi.t8("The transport cost article '#1' is missing. Do you want to continue anyway?", [ description ]));
+  };
+
+  ns.check_cusordnumber_presence = function() {
+    if ($('#order_cusordnumber').val() === '') {
+      return confirm(kivi.t8('The customer order number is missing. Do you want to continue anyway?'));
+    }
+    return true;
+  };
+
 });
 
-$(function(){
-  if ($('#type').val() == 'sales_order') {
-    $('#order_customer_id').change(kivi.Order.reload_cv_dependant_selections);
+$(function() {
+  if ($('#type').val() == 'sales_order' || $('#type').val() == 'sales_quotation' ) {
+    $('#order_customer_id').change(kivi.Order.reload_cv_dependent_selections);
   } else {
-    $('#order_vendor_id').change(kivi.Order.reload_cv_dependant_selections);
+    $('#order_vendor_id').change(kivi.Order.reload_cv_dependent_selections);
   }
 
-  if ($('#type').val() == 'sales_order') {
+  $('#order_currency_id').change(kivi.Order.update_exchangerate);
+  $('#order_transdate_as_date').change(kivi.Order.update_exchangerate);
+  $('#order_exchangerate_as_null_number').change(kivi.Order.exchangerate_changed);
+
+  if ($('#type').val() == 'sales_order' || $('#type').val() == 'sales_quotation' ) {
     $('#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)) });
@@ -532,7 +911,7 @@ $(function(){
   $('#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) {
+    if (event.keyCode == 13) {
       event.preventDefault();
       kivi.Order.add_item();
       return false;
@@ -548,17 +927,17 @@ $(function(){
 
   $('#expand_all').on('click', function(event) {
     event.preventDefault();
-    if ($('#expand_all').data('expanded') === true) {
-      $('#expand_all').data('expanded', false);
-      $('#expand_all').attr('src', 'image/expand3.gif');
+    if ($('#expand_all').data('expanded') == 1) {
+      $('#expand_all').data('expanded', 0);
+      $('#expand_all').attr('src', 'image/expand.svg');
       $('#expand_all').attr('alt', kivi.t8('Show all details'));
       $('#expand_all').attr('title', kivi.t8('Show all details'));
       $('.row_entry').each(function(idx, elt) {
         kivi.Order.hide_second_row(elt);
       });
     } else {
-      $('#expand_all').data('expanded', true);
-      $('#expand_all').attr('src', "image/collapse3.gif");
+      $('#expand_all').data('expanded', 1);
+      $('#expand_all').attr('src', "image/collapse.svg");
       $('#expand_all').attr('alt', kivi.t8('Hide all details'));
       $('#expand_all').attr('title', kivi.t8('Hide all details'));
       kivi.Order.load_all_second_rows();
@@ -568,4 +947,7 @@ $(function(){
     }
     return false;
   });
+
+  $('.reformat_number_as_null_number').change(kivi.Order.reformat_number_as_null_number);
+
 });