Rodney Elpidio Enciso Arias преди 6 години
ревизия
25e2e96360

+ 5 - 0
__init__.py

@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+
+from . import models
+from . import controllers
+

BIN
__init__.pyc


+ 21 - 0
__openerp__.py

@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+{
+    'name': 'POS Stock',
+    'version': '1.0.0',
+    'category': 'Point Of Sale',
+    'author': 'D.Jane',
+    'sequence': 10,
+    'summary': 'Display Stocks on POS Location. Update Real-Time Quantity Available.',
+    'description': "",
+    'depends': ['point_of_sale'],
+    'data': [
+        'views/header.xml',
+        'views/config.xml'
+    ],
+    'images': ['static/description/banner.png'],
+    'qweb': ['static/src/xml/pos_stock.xml'],
+    'installable': True,
+    'application': True,
+    'license': 'Other proprietary',
+    'currency': 'EUR',
+}

+ 1 - 0
controllers/__init__.py

@@ -0,0 +1 @@
+from . import bus

BIN
controllers/__init__.pyc


+ 12 - 0
controllers/bus.py

@@ -0,0 +1,12 @@
+# -*- coding: utf-8 -*-
+
+from openerp.http import request
+from openerp.addons.bus.bus import Controller
+
+
+class BusControllerInherit(Controller):
+    def _poll(self, dbname, channels, last, options):
+        if request.session.uid:
+            channels = list(channels)
+            channels.append((request.db, 'pos.stock.channel'))
+        return super(BusControllerInherit, self)._poll(dbname, channels, last, options)

BIN
controllers/bus.pyc


+ 2 - 0
models/__init__.py

@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+from . import pos_stock

BIN
models/__init__.pyc


+ 65 - 0
models/pos_stock.py

@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+from openerp import api, fields, models
+from collections import deque
+
+
+class StockQuantity(models.Model):
+    _inherit = 'stock.quant'
+
+    @api.model
+    def get_qty_available(self, location_id, location_ids=None, product_ids=None):
+        if location_id:
+            root_location = self.env['stock.location'].search([('id', '=', location_id)])
+            all_location = [root_location.id]
+            queue = deque([])
+            self.location_traversal(queue, all_location, root_location)
+            stock_quant = self.search_read([('location_id', 'in', all_location)], ['product_id', 'qty', 'location_id'])
+            return stock_quant
+        else:
+            stock_quant = self.search_read([('location_id', 'in', location_ids), ('product_id', 'in', product_ids)],
+                                           ['product_id', 'qty', 'location_id'])
+            return stock_quant
+
+    def location_traversal(self, queue, res, root):
+        for child in root.child_ids:
+            if child.usage == 'internal':
+                queue.append(child)
+                res.append(child.id)
+        while queue:
+            pick = queue.popleft()
+            res.append(pick.id)
+            self.location_traversal(queue, res, pick)
+
+    @api.model
+    def create(self, vals):
+        res = super(StockQuantity, self).create(vals)
+        if res.location_id.usage == 'internal':
+            self.env['pos.stock.channel'].broadcast(res)
+        return res
+
+    @api.multi
+    def write(self, vals):
+        res = super(StockQuantity, self).write(vals)
+        records = self.filtered(lambda x: x.location_id.usage == 'internal')
+        if len(records) > 0:
+            self.env['pos.stock.channel'].broadcast(records)
+        return res
+
+
+class PosConfig(models.Model):
+    _inherit = 'pos.config'
+
+    show_qty_available = fields.Boolean(string='Display Stock in POS')
+    location_only = fields.Boolean(string='Only in POS Location')
+    allow_out_of_stock = fields.Boolean(string='Allow Out-of-Stock')
+    limit_qty = fields.Integer(string='Deny Order when Quantity Available lower than')
+    hide_product = fields.Boolean(string='Hide Products not in POS Location')
+
+
+class PosStockChannel(models.TransientModel):
+    _name = 'pos.stock.channel'
+
+    def broadcast(self, stock_quant):
+        data = stock_quant.read(['product_id', 'location_id', 'qty'])
+        self.env['bus.bus'].sendone((self._cr.dbname, 'pos.stock.channel'), data)

BIN
models/pos_stock.pyc


BIN
static/description/banner.png


BIN
static/description/demo1.png


BIN
static/description/demo2.png


BIN
static/description/demo3.png


BIN
static/description/demo4.png


BIN
static/description/demo5.png


BIN
static/description/demo6.png


BIN
static/description/icon.png


+ 124 - 0
static/description/index.html

@@ -0,0 +1,124 @@
+<section class="oe_container oe_spaced">
+    <h2 class="oe_slogan" style="color:#875A7B;">POS Stock</h2>
+    <div class="oe_spaced" style="padding-left: 64px; padding-right: 64px; font-size: 20px;">
+        <div style="text-align: center;">
+            <p class="oe_mt_16">Keep POS work offline and real-time update quantity available when POS online.</p>
+            <p class="oe_mt_16">This module is independent, work well with
+            <a style="color: #875A7B;" href="https://apps.odoo.com/apps/modules/11.0/pos_speed_up/">POS Speed Up</a>
+                or something like.</p>
+        </div>
+    </div>
+</section>
+
+<section class="oe_container">
+    <div>
+        <h2 class="oe_slogan" style="color:#875A7B;">Main Features</h2>
+    </div>
+    <div class="oe_spaced" style="padding-left: 64px; padding-right: 64px; font-size: 18px;">
+        <div style="background: white">
+            <div class="alert alert-info">
+                <i class="fa fa-star fa-spin"></i>
+                Show Quantity Available of Stockable Product - Only in POS Location and Descendants (Max-Depth)
+            </div>
+            <img src="demo2.png" style="width: 100%; margin-bottom: 18px; border: 1px solid lightslategray"/>
+        </div>
+    </div>
+</section>
+
+<section class="oe_container oe_spaced"></section>
+
+<section class="oe_container">
+    <div class="oe_spaced" style="padding-left: 64px; padding-right: 64px; font-size: 18px;">
+        <div style="background: white">
+            <div class="alert alert-info">
+                <i class="fa fa-star fa-spin"></i>
+                Hide Products not in POS Location
+            </div>
+        </div>
+    </div>
+</section>
+
+<section class="oe_container oe_spaced"></section>
+
+<section class="oe_container">
+    <div class="oe_spaced" style="padding-left: 64px; padding-right: 64px; font-size: 18px;">
+        <div style="background: white">
+            <div class="alert alert-info">
+                <i class="fa fa-star fa-spin"></i>
+                Deny order when Out-of-Stock
+            </div>
+
+            <img src="demo5.png" style="width: 100%; margin-bottom: 18px; border: 1px solid lightslategray"/>
+            <img src="demo6.png" style="width: 100%; margin-bottom: 18px; border: 1px solid lightslategray"/>
+        </div>
+    </div>
+</section>
+
+<section class="oe_container oe_spaced"></section>
+
+<section class="oe_container">
+    <div class="oe_spaced" style="padding-left: 64px; padding-right: 64px; font-size: 18px;">
+        <div style="background: white">
+            <div class="alert alert-info">
+                <i class="fa fa-star fa-spin"></i>
+                Reminder when Sum Quantity of Orderline over Max-Quantity-Available
+            </div>
+
+            <div class="oe_mt16" style="margin-bottom: 18px;">
+                <b>Order</b> with max-available-quantity or <b>Cancel</b> to remove orderline.
+            </div>
+
+            <img src="demo1.png" style="width: 100%; margin-bottom: 18px; border: 1px solid lightslategray"/>
+        </div>
+    </div>
+</section>
+
+<section class="oe_container oe_spaced"></section>
+
+<section class="oe_container">
+    <div class="oe_spaced" style="padding-left: 64px; padding-right: 64px; font-size: 18px;">
+        <div style="background: white">
+            <div class="alert alert-info">
+                <i class="fa fa-star fa-spin"></i>
+                Real-Time update Available Quantity
+            </div>
+            <div class="oe_mt16">
+                Between difference POS sessions, when Someone confirm DO, perform Inventory Adjustments...etc.
+            </div>
+            <h3 class="oe_slogan"> AnyWhere! </h3>
+            <div class="oe_slogan">
+                <a style="background: #c53c2c;" href="https://youtu.be/I_0lrrzrfa0" class="oe_button oe_big oe_tacky">Watch
+                    the video</a>
+            </div>
+        </div>
+    </div>
+</section>
+
+
+<section class="oe_container oe_spaced"></section>
+
+<section class='oe_container oe_dark'>
+    <h2 class='oe_slogan' style="color:#875A7B;">Supports</h2>
+    <h3 class="oe_slogan">Free 24/7</h3>
+    <div class='oe_spaced' style="padding-left: 64px; padding-right: 64px; font-size: 18px; text-align: center;">
+        <p class="oe_mt16">
+            For any supports or questions, please contact me via:
+        </p>
+        <div class="oe_mt16" style="display: flex; flex-flow: row; justify-content: center">
+            <div>
+                <i class="fa fa-2x fa fa-envelope" style="color:white;background: #c53c2c;width: 60px; padding: 4px;"></i>
+                <a href="https://mail.google.com/mail/?view=cm&amp;fs=1&amp;tf=1&amp;to=jane.odoo.sp@gmail.com" target="_blank">
+                    <b style="color: #875A7B">jane.odoo.sp@gmail.com</b></a>
+            </div>
+            <div style="margin-left: 16px;">
+                <a href="https://www.linkedin.com/in/unicode2utf8/" target="_blank">
+                    <i class="fa fa-2x fa-linkedin" style="background: #0077B5; color: white; width: 60px; padding: 4px;"></i>
+                </a>
+            </div>
+        </div>
+    </div>
+</section>
+
+<section class="oe_container oe_spaced">
+    <h3 class="oe_slogan">Thank you!</h3>
+</section>

+ 29 - 0
static/src/css/stock_qty.css

@@ -0,0 +1,29 @@
+.pos .product-list .qty-tag{
+    position: absolute;
+    top: 2px;
+    left: 2px;
+    vertical-align: top;
+    color: white;
+    line-height: 13px;
+    background: #32a868;
+    padding: 2px 5px;
+    border-radius: 2px;
+    font-weight: bold;
+    display: none;
+}
+
+.pos .product-list .product{
+    transition: all 1s ease;
+}
+
+
+.pos .product-list .qty-tag.sold-out{
+    background: #EF5350;
+}
+
+
+.pos .product-list .product.disable{
+    pointer-events: none;
+    opacity: 0.5;
+}
+

+ 31 - 0
static/src/js/main.js

@@ -0,0 +1,31 @@
+/*
+* @Author: D.Jane
+* @Email: jane.odoo.sp@gmail.com
+*/
+openerp.pos_stock_quantity = function (instance) {
+    instance.pos_stock_quantity = {};
+
+    var module = instance.pos_stock_quantity;
+
+    module.load_fields = function (model_name, fields) {
+        if (!(fields instanceof Array)) {
+            fields = [fields];
+        }
+
+        var models = instance.point_of_sale.PosModel.prototype.models;
+        for (var i = 0; i < models.length; i++) {
+            var model = models[i];
+            if (model.model === model_name) {
+                // if 'fields' is empty all fields are loaded, so we do not need
+                // to modify the array
+                if ((model.fields instanceof Array) && model.fields.length > 0) {
+                    model.fields = model.fields.concat(fields || []);
+                }
+            }
+        }
+    };
+
+    // init
+    popup(instance, module);
+    pos_stock(instance, module);
+};

+ 39 - 0
static/src/js/popup.js

@@ -0,0 +1,39 @@
+/*
+* @Author: D.Jane
+* @Email: jane.odoo.sp@gmail.com
+*/
+function popup(instance, module) {
+    var ConfirmPopupWidget = instance.point_of_sale.ConfirmPopupWidget;
+    if (!ConfirmPopupWidget) {
+        return;
+    }
+    var Reminder = ConfirmPopupWidget.extend({
+        template: 'Reminder',
+        init:function(parent,options){
+            this._super(parent, options);
+            this.options = {};
+        },
+        show: function(options){
+            this.options = options;
+            options.cancel = function () {
+                options.line.set_quantity('remove');
+                this.hide();
+            };
+            options.confirm = function () {
+                options.line.set_quantity(options.max_available);
+                this.hide();
+            };
+            this._super(options);
+        }
+    });
+
+    instance.point_of_sale.PosWidget.include({
+        build_widgets: function () {
+            this.reminder_popup = new Reminder(this, {});
+            this.reminder_popup.appendTo(this.$el);
+            this.reminder_popup.hide();
+            this._super();
+            this.screen_selector.popup_set['reminder'] = this.reminder_popup;
+        }
+    })
+}

+ 319 - 0
static/src/js/pos_stock.js

@@ -0,0 +1,319 @@
+/*
+* @Author: D.Jane
+* @Email: jane.odoo.sp@gmail.com
+*/
+function pos_stock(instance, module) {
+    var Model = instance.web.Model;
+
+    if (!instance.point_of_sale.PosModel) {
+        return;
+    }
+
+    module.load_fields('product.product', ['type']);
+
+
+    var _super_pos = instance.point_of_sale.PosModel.prototype;
+    instance.point_of_sale.PosModel = instance.point_of_sale.PosModel.extend({
+        initialize: function (session, attributes) {
+            this.stock_location_modifier();
+            this.pool = [];
+            _super_pos.initialize.call(this, session, attributes);
+        },
+        on_notification: function (notification) {
+            var channel = notification[0];
+            var message = notification[1];
+            if (Array.isArray(channel) && channel[1] === 'pos.stock.channel') {
+                this.on_stock_notification(message);
+            }
+        },
+        on_stock_notification: function (stock_quant) {
+            var self = this;
+            clearTimeout(module.task);
+
+            var product_ids = stock_quant.map(function (item) {
+                return item.product_id[0];
+            });
+
+            self.pool = _.uniq(self.pool.concat(product_ids));
+            module.task = setTimeout(function () {
+                $.when(self.qty_sync(self.pool)).done(function () {
+                    self.refresh_qty();
+                });
+            }, 800);
+        },
+        qty_sync: function (product_ids) {
+            var self = this;
+            var done = new $.Deferred();
+            if (this.config.show_qty_available && this.config.location_only) {
+                new Model('stock.quant').call('get_qty_available', [false, self.stock_location_ids, product_ids])
+                    .then(function (res) {
+                        self.recompute_qty_in_pos_location(product_ids, res);
+                        done.resolve();
+                    });
+            } else if (this.config.show_qty_available) {
+                new Model('product.product').call('read', [product_ids, ['qty_available']]).then(function (res) {
+                    res.forEach(function (product) {
+                        self.db.qty_by_product_id[product.id] = product.qty_available;
+                    });
+                    done.resolve();
+                });
+            } else {
+                done.resolve();
+            }
+            return done.promise();
+        },
+        compute_qty_in_pos_location: function (res) {
+            var self = this;
+            self.db.qty_by_product_id = {};
+            res.forEach(function (item) {
+                var product_id = item.product_id[0];
+                if (!self.db.qty_by_product_id[product_id]) {
+                    self.db.qty_by_product_id[product_id] = item.qty;
+                } else {
+                    self.db.qty_by_product_id[product_id] += item.qty;
+                }
+            })
+        },
+        recompute_qty_in_pos_location: function (product_ids, res) {
+            var self = this;
+            var res_product_ids = res.map(function (item) {
+                return item.product_id[0];
+            });
+
+            var out_of_stock_ids = product_ids.filter(function (id) {
+                return res_product_ids.indexOf(id) === -1;
+            });
+
+            out_of_stock_ids.forEach(function (id) {
+                self.db.qty_by_product_id[id] = 0;
+            });
+
+            res_product_ids.forEach(function (product_id) {
+                self.db.qty_by_product_id[product_id] = false;
+            });
+
+            res.forEach(function (item) {
+                var product_id = item.product_id[0];
+
+                if (!self.db.qty_by_product_id[product_id]) {
+                    self.db.qty_by_product_id[product_id] = item.qty;
+                } else {
+                    self.db.qty_by_product_id[product_id] += item.qty;
+                }
+            });
+        },
+        refresh_qty: function () {
+            var self = this;
+            var $qty_tag = $('.product-list').find('.qty-tag');
+            $qty_tag.each(function () {
+                var $product = $(this).parents('.product');
+                var id = parseInt($product.attr('data-product-id'));
+
+                var qty = self.db.qty_by_product_id[id];
+                if (qty === false) {
+                    return;
+                }
+
+                if (qty === undefined) {
+                    if (self.config.hide_product) {
+                        $product.hide();
+                        return;
+                    } else {
+                        qty = 0;
+                    }
+                }
+
+                var product = self.db.get_product_by_id(id);
+                var unit = self.units_by_id[product.uom_id[0]] || {};
+                var unit_name = unit.name;
+                if (unit_name === 'Unit(s)') {
+                    unit_name = ''
+                }
+
+                $(this).text(qty + ' ' + unit_name).show('fast');
+
+                if (qty <= self.config.limit_qty) {
+                    $(this).addClass('sold-out');
+                    if (!self.config.allow_out_of_stock) {
+                        $product.addClass('disable');
+                    }
+                } else {
+                    $(this).removeClass('sold-out');
+                    $product.removeClass('disable');
+                }
+            });
+        },
+        get_model: function (_name) {
+            var _index = this.models.map(function (e) {
+                return e.model;
+            }).indexOf(_name);
+            if (_index > -1) {
+                return this.models[_index];
+            }
+            return false;
+        },
+        load_qty_after_load_product: function () {
+            var wait = this.get_model('account.journal');
+            var _wait_super_loaded = wait.loaded;
+            wait.loaded = function (self, journals) {
+                var done = $.Deferred();
+                _wait_super_loaded(self, journals);
+
+                var ids = Object.keys(self.db.product_by_id).map(function (item) {
+                    return parseInt(item);
+                });
+
+                new Model('product.product').call('read', [ids, ['qty_available']]).then(function (res) {
+                    self.db.qty_by_product_id = {};
+                    res.forEach(function (product) {
+                        self.db.qty_by_product_id[product.id] = product.qty_available;
+                    });
+                    self.refresh_qty();
+                    done.resolve();
+                });
+                return done;
+            }
+        },
+        stock_location_modifier: function () {
+            this.stock_location_ids = [];
+
+            var stock_location = this.get_model('stock.location');
+            var _super_loaded = stock_location.loaded;
+
+            stock_location.loaded = function (self, locations) {
+                var done = new $.Deferred();
+                _super_loaded(self, locations);
+
+                if (!self.config.show_qty_available) {
+                    return done.resolve();
+                }
+
+                if (self.config.allow_out_of_stock) {
+                    self.config.limit_qty = 0;
+                }
+
+                if (self.config.location_only) {
+                    new Model('stock.quant').call('get_qty_available', [self.shop.id]).then(function (res) {
+                        self.stock_location_ids = _.uniq(res.map(function (item) {
+                            return item.location_id[0];
+                        }));
+                        self.compute_qty_in_pos_location(res);
+                        done.resolve();
+                    });
+                } else {
+                    self.load_qty_after_load_product();
+                    done.resolve();
+                }
+                return done;
+            };
+        },
+        get_product_image_url: function (product) {
+            return window.location.origin + '/web/binary/image?model=product.product&field=image_medium&id=' + product.id;
+        },
+        push_and_invoice_order: function (order) {
+            this.sub_qty();
+            return _super_pos.push_and_invoice_order.call(this, order);
+        },
+        push_order: function (order) {
+            this.sub_qty();
+            return _super_pos.push_order.call(this, order);
+        },
+        sub_qty: function () {
+            var self = this;
+            var order = this.get('selectedOrder');
+            var orderlines = order.get('orderLines').models;
+            var sub_qty_by_product_id = {};
+            var ids = [];
+            orderlines.forEach(function (line) {
+                if (!sub_qty_by_product_id[line.product.id]) {
+                    sub_qty_by_product_id[line.product.id] = line.quantity;
+                    ids.push(line.product.id);
+                } else {
+                    sub_qty_by_product_id[line.product.id] += line.quantity;
+                }
+            });
+
+            ids.forEach(function (id) {
+                self.db.qty_by_product_id[id] -= sub_qty_by_product_id[id];
+            });
+        }
+    });
+
+    // refresh qty whenever product is re-rendered
+    instance.point_of_sale.ProductListWidget.include({
+        render_product: function (product) {
+            // for hide qty-tag
+            if (this.pos.config.show_qty_available && product.type === 'service') {
+                this.pos.db.qty_by_product_id[product.id] = false;
+            }
+            return this._super(product);
+        },
+        renderElement: function () {
+            this._super();
+            var self = this;
+            var done = $.Deferred();
+            clearInterval(module.task);
+            module.task = setTimeout(function () {
+                if (self.pos.config.show_qty_available) {
+                    self.pos.refresh_qty();
+                } else {
+                    $(self.el).find('.qty-tag').hide();
+                }
+                done.resolve();
+            }, 100);
+            return done;
+        }
+    });
+
+    // show reminder-popup when out-of-stock
+    var _super_orderline = instance.point_of_sale.Orderline.prototype;
+    instance.point_of_sale.Orderline = instance.point_of_sale.Orderline.extend({
+        set_quantity: function (quantity) {
+            _super_orderline.set_quantity.call(this, quantity);
+
+            if (!this.pos.config.show_qty_available
+                || this.pos.config.allow_out_of_stock
+                || this.product.type === 'service') {
+                return;
+            }
+
+            this.check_reminder();
+        },
+        check_reminder: function () {
+            var self = this;
+            var qty_available = this.pos.db.qty_by_product_id[this.product.id];
+
+            var orderlines = this.order.get('orderLines').models;
+            var all_product_line = orderlines.filter(function (orderline) {
+                return self.product.id === orderline.product.id;
+            });
+
+            if (all_product_line.indexOf(self) === -1) {
+                all_product_line.push(self);
+            }
+
+            var sum_qty = 0;
+            all_product_line.forEach(function (line) {
+                sum_qty += line.quantity;
+            });
+
+            if (qty_available - sum_qty < this.pos.config.limit_qty) {
+                this.pos.pos_widget.screen_selector.show_popup('reminder', {
+                    max_available: qty_available - sum_qty + self.quantity - this.pos.config.limit_qty,
+                    product_image_url: self.pos.get_product_image_url(self.product),
+                    product_name: self.product.display_name,
+                    line: self
+                });
+            }
+        }
+    });
+
+    instance.point_of_sale.PosWidget.include({
+        build_widgets: function () {
+            this._super();
+            this.bus = openerp.bus.bus;
+            this.bus.on('notification', this.pos, this.pos.on_notification);
+            this.bus.start_polling();
+        }
+    });
+}

+ 43 - 0
static/src/xml/pos_stock.xml

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="pos_stock_quantity.template" inherit_id="point_of_sale.template">
+    <t t-extend="Product">
+        <t t-jquery=".product-img" t-operation="prepend">
+            <span class="qty-tag">
+            </span>
+        </t>
+    </t>
+
+    <t t-name="Reminder">
+        <div class="modal-dialog">
+            <div class="popup popup-confirm" style="height: 40%; width: 40%; border-radius: 0; background: white">
+                <div class="modal-title" style="padding-bottom: 20px;">Warning</div>
+                <div class="body">
+                    <div style="display: flex; flex-flow: row;">
+                        <div>
+                            <img t-att-src="widget.options.product_image_url" style="max-width: 128px;"/>
+                        </div>
+                        <div style="flex-grow: 1; margin-left: 16px; text-align: center;">
+                            <p style="font-weight: bold;">
+                                <t t-esc="widget.options.product_name"/>
+                            </p>
+                            <p>
+                                Maximum quantity available is
+                                <b style="color: #32a868"><t t-esc="widget.options.max_available"/></b>
+                            </p>
+                        </div>
+                    </div>
+                </div>
+                <div class="footer">
+                    <div class="button cancel" style="width: 64px;">
+                        Cancel
+                    </div>
+                    <t t-if="widget.options.max_available">
+                        <div class="button confirm" style="color: #32a868; width: 64px;">
+                            Order
+                        </div>
+                    </t>
+                </div>
+            </div>
+        </div>
+    </t>
+</templates>

+ 30 - 0
views/config.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <record id="pos_config" model="ir.ui.view">
+            <field name="name">pos.config</field>
+            <field name="model">pos.config</field>
+            <field name="inherit_id" ref="point_of_sale.view_pos_config_form"/>
+            <field name="arch" type="xml">
+                <xpath expr='//sheet/group[1]' position='after'>
+                    <group string="POS Stock">
+                        <group>
+                            <field name="show_qty_available"/>
+                        </group>
+                        <group
+                               attrs="{'invisible':[('show_qty_available','=',False)]}">
+                            <field name="location_only"/>
+                            <field name="hide_product" attrs="{'invisible':[
+                        ('location_only','=',False)]}"/>
+                        </group>
+                        <group attrs="{'invisible':[('show_qty_available','=',False)]}">
+                            <field name="allow_out_of_stock"/>
+                            <field name="limit_qty" attrs="{'invisible':[('allow_out_of_stock','=',True)]}"
+                                   style="width: 150px;"/>
+                        </group>
+                    </group>
+                </xpath>
+            </field>
+        </record>
+    </data>
+</openerp>

+ 18 - 0
views/header.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <template id="assets_backend" inherit_id="web.assets_backend" name="pos_stock_quantity assets">
+            <xpath expr="." position="inside">
+                <script type="text/javascript" src="/pos_stock_quantity/static/src/js/pos_stock.js"></script>
+                <script type="text/javascript" src="/pos_stock_quantity/static/src/js/popup.js"></script>
+                <script type="text/javascript" src="/pos_stock_quantity/static/src/js/main.js"></script>
+            </xpath>
+        </template>
+
+        <template id="pos_css" inherit_id="point_of_sale.index">
+            <xpath expr="//head" position="inside">
+                <link rel="stylesheet" href="/pos_stock_quantity/static/src/css/stock_qty.css"/>
+            </xpath>
+        </template>
+    </data>
+</openerp>