Add clusterize, move customer field to POSCart, get POS profile
diff --git a/erpnext/public/css/pos.css b/erpnext/public/css/pos.css
index d44b17c..4ba00ba 100644
--- a/erpnext/public/css/pos.css
+++ b/erpnext/public/css/pos.css
@@ -1,7 +1,7 @@
 .pos {
   padding: 15px;
 }
-.customer-container {
+.cart-container {
   padding: 0 15px;
   display: inline-block;
   width: 39%;
diff --git a/erpnext/public/js/pos/clusterize.js b/erpnext/public/js/pos/clusterize.js
new file mode 100644
index 0000000..6d331ba
--- /dev/null
+++ b/erpnext/public/js/pos/clusterize.js
@@ -0,0 +1,329 @@
+/*! Clusterize.js - v0.17.6 - 2017-03-05
+* http://NeXTs.github.com/Clusterize.js/
+* Copyright (c) 2015 Denis Lukov; Licensed GPLv3 */
+
+;(function(name, definition) {
+    if (typeof module != 'undefined') module.exports = definition();
+    else if (typeof define == 'function' && typeof define.amd == 'object') define(definition);
+    else this[name] = definition();
+}('Clusterize', function() {
+  "use strict"
+
+  // detect ie9 and lower
+  // https://gist.github.com/padolsey/527683#comment-786682
+  var ie = (function(){
+    for( var v = 3,
+             el = document.createElement('b'),
+             all = el.all || [];
+         el.innerHTML = '<!--[if gt IE ' + (++v) + ']><i><![endif]-->',
+         all[0];
+       ){}
+    return v > 4 ? v : document.documentMode;
+  }()),
+  is_mac = navigator.platform.toLowerCase().indexOf('mac') + 1;
+  var Clusterize = function(data) {
+    if( ! (this instanceof Clusterize))
+      return new Clusterize(data);
+    var self = this;
+
+    var defaults = {
+      rows_in_block: 50,
+      blocks_in_cluster: 4,
+      tag: null,
+      show_no_data_row: true,
+      no_data_class: 'clusterize-no-data',
+      no_data_text: 'No data',
+      keep_parity: true,
+      callbacks: {}
+    }
+
+    // public parameters
+    self.options = {};
+    var options = ['rows_in_block', 'blocks_in_cluster', 'show_no_data_row', 'no_data_class', 'no_data_text', 'keep_parity', 'tag', 'callbacks'];
+    for(var i = 0, option; option = options[i]; i++) {
+      self.options[option] = typeof data[option] != 'undefined' && data[option] != null
+        ? data[option]
+        : defaults[option];
+    }
+
+    var elems = ['scroll', 'content'];
+    for(var i = 0, elem; elem = elems[i]; i++) {
+      self[elem + '_elem'] = data[elem + 'Id']
+        ? document.getElementById(data[elem + 'Id'])
+        : data[elem + 'Elem'];
+      if( ! self[elem + '_elem'])
+        throw new Error("Error! Could not find " + elem + " element");
+    }
+
+    // tabindex forces the browser to keep focus on the scrolling list, fixes #11
+    if( ! self.content_elem.hasAttribute('tabindex'))
+      self.content_elem.setAttribute('tabindex', 0);
+
+    // private parameters
+    var rows = isArray(data.rows)
+        ? data.rows
+        : self.fetchMarkup(),
+      cache = {},
+      scroll_top = self.scroll_elem.scrollTop;
+
+    // append initial data
+    self.insertToDOM(rows, cache);
+
+    // restore the scroll position
+    self.scroll_elem.scrollTop = scroll_top;
+
+    // adding scroll handler
+    var last_cluster = false,
+    scroll_debounce = 0,
+    pointer_events_set = false,
+    scrollEv = function() {
+      // fixes scrolling issue on Mac #3
+      if (is_mac) {
+          if( ! pointer_events_set) self.content_elem.style.pointerEvents = 'none';
+          pointer_events_set = true;
+          clearTimeout(scroll_debounce);
+          scroll_debounce = setTimeout(function () {
+              self.content_elem.style.pointerEvents = 'auto';
+              pointer_events_set = false;
+          }, 50);
+      }
+      if (last_cluster != (last_cluster = self.getClusterNum()))
+        self.insertToDOM(rows, cache);
+      if (self.options.callbacks.scrollingProgress)
+        self.options.callbacks.scrollingProgress(self.getScrollProgress());
+    },
+    resize_debounce = 0,
+    resizeEv = function() {
+      clearTimeout(resize_debounce);
+      resize_debounce = setTimeout(self.refresh, 100);
+    }
+    on('scroll', self.scroll_elem, scrollEv);
+    on('resize', window, resizeEv);
+
+    // public methods
+    self.destroy = function(clean) {
+      off('scroll', self.scroll_elem, scrollEv);
+      off('resize', window, resizeEv);
+      self.html((clean ? self.generateEmptyRow() : rows).join(''));
+    }
+    self.refresh = function(force) {
+      if(self.getRowsHeight(rows) || force) self.update(rows);
+    }
+    self.update = function(new_rows) {
+      rows = isArray(new_rows)
+        ? new_rows
+        : [];
+      var scroll_top = self.scroll_elem.scrollTop;
+      // fixes #39
+      if(rows.length * self.options.item_height < scroll_top) {
+        self.scroll_elem.scrollTop = 0;
+        last_cluster = 0;
+      }
+      self.insertToDOM(rows, cache);
+      self.scroll_elem.scrollTop = scroll_top;
+    }
+    self.clear = function() {
+      self.update([]);
+    }
+    self.getRowsAmount = function() {
+      return rows.length;
+    }
+    self.getScrollProgress = function() {
+      return this.options.scroll_top / (rows.length * this.options.item_height) * 100 || 0;
+    }
+
+    var add = function(where, _new_rows) {
+      var new_rows = isArray(_new_rows)
+        ? _new_rows
+        : [];
+      if( ! new_rows.length) return;
+      rows = where == 'append'
+        ? rows.concat(new_rows)
+        : new_rows.concat(rows);
+      self.insertToDOM(rows, cache);
+    }
+    self.append = function(rows) {
+      add('append', rows);
+    }
+    self.prepend = function(rows) {
+      add('prepend', rows);
+    }
+  }
+
+  Clusterize.prototype = {
+    constructor: Clusterize,
+    // fetch existing markup
+    fetchMarkup: function() {
+      var rows = [], rows_nodes = this.getChildNodes(this.content_elem);
+      while (rows_nodes.length) {
+        rows.push(rows_nodes.shift().outerHTML);
+      }
+      return rows;
+    },
+    // get tag name, content tag name, tag height, calc cluster height
+    exploreEnvironment: function(rows, cache) {
+      var opts = this.options;
+      opts.content_tag = this.content_elem.tagName.toLowerCase();
+      if( ! rows.length) return;
+      if(ie && ie <= 9 && ! opts.tag) opts.tag = rows[0].match(/<([^>\s/]*)/)[1].toLowerCase();
+      if(this.content_elem.children.length <= 1) cache.data = this.html(rows[0] + rows[0] + rows[0]);
+      if( ! opts.tag) opts.tag = this.content_elem.children[0].tagName.toLowerCase();
+      this.getRowsHeight(rows);
+    },
+    getRowsHeight: function(rows) {
+      var opts = this.options,
+        prev_item_height = opts.item_height;
+      opts.cluster_height = 0;
+      if( ! rows.length) return;
+      var nodes = this.content_elem.children;
+      var node = nodes[Math.floor(nodes.length / 2)];
+      opts.item_height = node.offsetHeight;
+      // consider table's border-spacing
+      if(opts.tag == 'tr' && getStyle('borderCollapse', this.content_elem) != 'collapse')
+        opts.item_height += parseInt(getStyle('borderSpacing', this.content_elem), 10) || 0;
+      // consider margins (and margins collapsing)
+      if(opts.tag != 'tr') {
+        var marginTop = parseInt(getStyle('marginTop', node), 10) || 0;
+        var marginBottom = parseInt(getStyle('marginBottom', node), 10) || 0;
+        opts.item_height += Math.max(marginTop, marginBottom);
+      }
+      opts.block_height = opts.item_height * opts.rows_in_block;
+      opts.rows_in_cluster = opts.blocks_in_cluster * opts.rows_in_block;
+      opts.cluster_height = opts.blocks_in_cluster * opts.block_height;
+      return prev_item_height != opts.item_height;
+    },
+    // get current cluster number
+    getClusterNum: function () {
+      this.options.scroll_top = this.scroll_elem.scrollTop;
+      return Math.floor(this.options.scroll_top / (this.options.cluster_height - this.options.block_height)) || 0;
+    },
+    // generate empty row if no data provided
+    generateEmptyRow: function() {
+      var opts = this.options;
+      if( ! opts.tag || ! opts.show_no_data_row) return [];
+      var empty_row = document.createElement(opts.tag),
+        no_data_content = document.createTextNode(opts.no_data_text), td;
+      empty_row.className = opts.no_data_class;
+      if(opts.tag == 'tr') {
+        td = document.createElement('td');
+        // fixes #53
+        td.colSpan = 100;
+        td.appendChild(no_data_content);
+      }
+      empty_row.appendChild(td || no_data_content);
+      return [empty_row.outerHTML];
+    },
+    // generate cluster for current scroll position
+    generate: function (rows, cluster_num) {
+      var opts = this.options,
+        rows_len = rows.length;
+      if (rows_len < opts.rows_in_block) {
+        return {
+          top_offset: 0,
+          bottom_offset: 0,
+          rows_above: 0,
+          rows: rows_len ? rows : this.generateEmptyRow()
+        }
+      }
+      var items_start = Math.max((opts.rows_in_cluster - opts.rows_in_block) * cluster_num, 0),
+        items_end = items_start + opts.rows_in_cluster,
+        top_offset = Math.max(items_start * opts.item_height, 0),
+        bottom_offset = Math.max((rows_len - items_end) * opts.item_height, 0),
+        this_cluster_rows = [],
+        rows_above = items_start;
+      if(top_offset < 1) {
+        rows_above++;
+      }
+      for (var i = items_start; i < items_end; i++) {
+        rows[i] && this_cluster_rows.push(rows[i]);
+      }
+      return {
+        top_offset: top_offset,
+        bottom_offset: bottom_offset,
+        rows_above: rows_above,
+        rows: this_cluster_rows
+      }
+    },
+    renderExtraTag: function(class_name, height) {
+      var tag = document.createElement(this.options.tag),
+        clusterize_prefix = 'clusterize-';
+      tag.className = [clusterize_prefix + 'extra-row', clusterize_prefix + class_name].join(' ');
+      height && (tag.style.height = height + 'px');
+      return tag.outerHTML;
+    },
+    // if necessary verify data changed and insert to DOM
+    insertToDOM: function(rows, cache) {
+      // explore row's height
+      if( ! this.options.cluster_height) {
+        this.exploreEnvironment(rows, cache);
+      }
+      var data = this.generate(rows, this.getClusterNum()),
+        this_cluster_rows = data.rows.join(''),
+        this_cluster_content_changed = this.checkChanges('data', this_cluster_rows, cache),
+        top_offset_changed = this.checkChanges('top', data.top_offset, cache),
+        only_bottom_offset_changed = this.checkChanges('bottom', data.bottom_offset, cache),
+        callbacks = this.options.callbacks,
+        layout = [];
+
+      if(this_cluster_content_changed || top_offset_changed) {
+        if(data.top_offset) {
+          this.options.keep_parity && layout.push(this.renderExtraTag('keep-parity'));
+          layout.push(this.renderExtraTag('top-space', data.top_offset));
+        }
+        layout.push(this_cluster_rows);
+        data.bottom_offset && layout.push(this.renderExtraTag('bottom-space', data.bottom_offset));
+        callbacks.clusterWillChange && callbacks.clusterWillChange();
+        this.html(layout.join(''));
+        this.options.content_tag == 'ol' && this.content_elem.setAttribute('start', data.rows_above);
+        callbacks.clusterChanged && callbacks.clusterChanged();
+      } else if(only_bottom_offset_changed) {
+        this.content_elem.lastChild.style.height = data.bottom_offset + 'px';
+      }
+    },
+    // unfortunately ie <= 9 does not allow to use innerHTML for table elements, so make a workaround
+    html: function(data) {
+      var content_elem = this.content_elem;
+      if(ie && ie <= 9 && this.options.tag == 'tr') {
+        var div = document.createElement('div'), last;
+        div.innerHTML = '<table><tbody>' + data + '</tbody></table>';
+        while((last = content_elem.lastChild)) {
+          content_elem.removeChild(last);
+        }
+        var rows_nodes = this.getChildNodes(div.firstChild.firstChild);
+        while (rows_nodes.length) {
+          content_elem.appendChild(rows_nodes.shift());
+        }
+      } else {
+        content_elem.innerHTML = data;
+      }
+    },
+    getChildNodes: function(tag) {
+        var child_nodes = tag.children, nodes = [];
+        for (var i = 0, ii = child_nodes.length; i < ii; i++) {
+            nodes.push(child_nodes[i]);
+        }
+        return nodes;
+    },
+    checkChanges: function(type, value, cache) {
+      var changed = value != cache[type];
+      cache[type] = value;
+      return changed;
+    }
+  }
+
+  // support functions
+  function on(evt, element, fnc) {
+    return element.addEventListener ? element.addEventListener(evt, fnc, false) : element.attachEvent("on" + evt, fnc);
+  }
+  function off(evt, element, fnc) {
+    return element.removeEventListener ? element.removeEventListener(evt, fnc, false) : element.detachEvent("on" + evt, fnc);
+  }
+  function isArray(arr) {
+    return Object.prototype.toString.call(arr) === '[object Array]';
+  }
+  function getStyle(prop, elem) {
+    return window.getComputedStyle ? window.getComputedStyle(elem)[prop] : elem.currentStyle[prop];
+  }
+
+  return Clusterize;
+}));
\ No newline at end of file
diff --git a/erpnext/public/less/pos.less b/erpnext/public/less/pos.less
index c94f8b5..e627dbc 100644
--- a/erpnext/public/less/pos.less
+++ b/erpnext/public/less/pos.less
@@ -5,7 +5,7 @@
 	padding: 15px;
 }
 
-.customer-container {
+.cart-container {
 	padding: 0 15px;
 	// flex: 2;
 	display: inline-block;
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js
index c9ef30e..fb3f666 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.js
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.js
@@ -15,25 +15,26 @@
 		this.page = wrapper.page;
 
 		const assets = [
-			'assets/frappe/js/lib/clusterize.js',
+			'assets/erpnext/js/pos/clusterize.js',
 			'assets/erpnext/css/pos.css'
 		];
 
 		frappe.require(assets, () => {
-			this.prepare();
-			this.make();
-			this.bind_events();
+			this.prepare().then(() => {
+				this.make();
+				this.bind_events();
+			});
 		});
 	}
 
 	prepare() {
 		this.set_online_status();
 		this.prepare_menu();
+		return this.get_pos_profile();
 	}
 
 	make() {
 		this.make_dom();
-		this.make_customer_field();
 		this.make_cart();
 		this.make_items();
 	}
@@ -52,6 +53,66 @@
 		});
 	}
 
+	make_dom() {
+		this.wrapper.append(`
+			<div class="pos">
+				<section class="cart-container">
+
+				</section>
+				<section class="item-container">
+
+				</section>
+			</div>
+		`);
+	}
+
+	make_cart() {
+		this.cart = new erpnext.POSCart(this.wrapper.find('.cart-container'));
+	}
+
+	make_items() {
+		this.items = new erpnext.POSItems({
+			wrapper: this.wrapper.find('.item-container'),
+			pos_profile: this.pos_profile,
+			events: {
+				item_click: (item_code) => this.add_item_to_cart(item_code)
+			}
+		});
+	}
+
+	add_item_to_cart(item_code) {
+		const item = this.items.get(item_code);
+		this.cart.add_item(item);
+	}
+
+	bind_events() {
+
+	}
+
+	get_pos_profile() {
+		return frappe.call({
+			method: 'erpnext.stock.get_item_details.get_pos_profile',
+			args: {
+				company: frappe.sys_defaults.company
+			}
+		}).then(r => {
+			this.pos_profile = r.message;
+		});
+	}
+
+	make_sales_invoice_frm() {
+		const dt = 'Sales Invoice';
+		return new Promise(resolve => {
+			frappe.model.with_doctype(dt, function() {
+				const page = $('<div>');
+				const frm = new _f.Frm(dt, page, false);
+				const name = frappe.model.make_new_doc_and_get_name(dt, true);
+				frm.refresh(name);
+				resolve(frm);
+			});
+		});
+	}
+
 	prepare_menu() {
 		this.page.clear_menu();
 
@@ -76,19 +137,38 @@
 			frappe.set_route('List', 'POS Profile');
 		});
 	}
+}
+
+erpnext.POSCart = class POSCart {
+	constructor(wrapper) {
+		this.wrapper = wrapper;
+		this.items = {};
+		this.make();
+	}
+
+	make() {
+		this.make_dom();
+		this.make_customer_field();
+	}
 
 	make_dom() {
 		this.wrapper.append(`
-			<div class="pos">
-				<section class="customer-container">
-					<div class="customer-field">
+			<div class="customer-field">
+			</div>
+			<div class="cart-wrapper">
+				<div class="list-item-table">
+					<div class="list-item list-item--head">
+						<div class="list-item__content list-item__content--flex-2 text-muted">${__('Item Name')}</div>
+						<div class="list-item__content text-muted text-right">${__('Quantity')}</div>
+						<div class="list-item__content text-muted text-right">${__('Discount')}</div>
+						<div class="list-item__content text-muted text-right">${__('Rate')}</div>
 					</div>
-					<div class="cart-wrapper">
+					<div class="cart-items">
+						<div class="empty-state">
+							<span>No Items added to cart</span>
+						</div>
 					</div>
-				</section>
-				<section class="item-container">
-
-				</section>
+				</div>
 			</div>
 		`);
 	}
@@ -105,61 +185,6 @@
 		});
 	}
 
-	make_cart() {
-		this.cart = new erpnext.POSCart(this.wrapper.find('.cart-wrapper'));
-	}
-
-	make_items() {
-		this.items = new erpnext.POSItems(this.wrapper.find('.item-container'), {
-			item_click: (item_code) => this.add_item_to_cart(item_code)
-		});
-	}
-
-	add_item_to_cart(item_code) {
-		const item = this.items.get(item_code);
-		this.cart.add_item(item);
-	}
-
-	bind_events() {
-
-	}
-
-	make_sales_invoice_frm() {
-		const dt = 'Sales Invoice';
-		frappe.model.with_doctype(dt, function() {
-			const page = $('<div>');
-			const frm = new _f.Frm(dt, page, false);
-			const name = frappe.model.make_new_doc_and_get_name(dt, true);
-			frm.refresh(name);
-		});
-	}
-}
-
-erpnext.POSCart = class POSCart {
-	constructor(wrapper) {
-		this.wrapper = wrapper;
-		this.items = {};
-		this.make();
-	}
-
-	make() {
-		this.wrapper.append(`
-			<div class="list-item-table">
-				<div class="list-item list-item--head">
-					<div class="list-item__content list-item__content--flex-2 text-muted">${__('Item Name')}</div>
-					<div class="list-item__content text-muted text-right">${__('Quantity')}</div>
-					<div class="list-item__content text-muted text-right">${__('Discount')}</div>
-					<div class="list-item__content text-muted text-right">${__('Rate')}</div>
-				</div>
-				<div class="cart-items">
-					<div class="empty-state">
-						<span>No Items added to cart</span>
-					</div>
-				</div>
-			</div>
-		`);
-	}
-
 	add_item(item) {
 		const { item_code } = item;
 		const _item = this.items[item_code];
@@ -240,9 +265,11 @@
 }
 
 erpnext.POSItems = class POSItems {
-	constructor(wrapper, events) {
+	constructor({wrapper, pos_profile, events}) {
 		this.wrapper = wrapper;
+		this.pos_profile = pos_profile;
 		this.items = {};
+
 		this.make_dom();
 		this.make_fields();
 
@@ -432,7 +459,7 @@
 		return template;
 	}
 
-	get_items(start = 0, page_length = 2000) {
+	get_items(start = 0, page_length = 20) {
 		return new Promise(res => {
 			frappe.call({
 				method: "frappe.desk.reportview.get",