diff --git a/pos_return/README.rst b/pos_return/README.rst new file mode 100644 index 000000000..1313f834c --- /dev/null +++ b/pos_return/README.rst @@ -0,0 +1,37 @@ +POS Return v10 +============== + +Helps You To Manage Product Return From Frontend. + +Depends +======= +[point_of_sale] addon Odoo + +Installation +============ + +- www.odoo.com/documentation/10.0/setup/install.html +- Install our custom addon + +License +======= +GNU LESSER GENERAL PUBLIC LICENSE, Version 3 (LGPLv3) +(http://www.gnu.org/licenses/agpl.html) + +Bug Tracker +=========== + +Contact odoo@cybrosys.com + +Credits +======= +Anusha P P @ cybrosys, anusha@cybrosys.in +Linto CT @ cybrosys, linto@cybrosys.in + + +Maintainer +---------- + +This module is maintained by Cybrosys Technologies. + +For support and more information, please visit https://www.cybrosys.com. diff --git a/pos_return/__init__.py b/pos_return/__init__.py new file mode 100644 index 000000000..cde864bae --- /dev/null +++ b/pos_return/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models diff --git a/pos_return/__manifest__.py b/pos_return/__manifest__.py new file mode 100644 index 000000000..de73aab54 --- /dev/null +++ b/pos_return/__manifest__.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2017-TODAY Cybrosys Technologies(). +# Author: Anusha () +# +# This program is free software: you can modify +# it under the terms of the GNU Affero General Public License (AGPL) as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### +{ + 'name': 'Product Return In POS', + 'version': '10.0.1.0.0', + 'category': 'Point of Sale', + 'summary': 'POS Order Return', + 'author': 'Cybrosys Techno Solutions', + 'company': 'Cybrosys Techno Solutions', + 'maintainer': 'Cybrosys Techno Solutions', + 'images': ['static/description/banner.jpg'], + 'website': 'https://www.cybrosys.com', + 'depends': ['point_of_sale'], + 'data': [ + 'views/return.xml', + 'views/pos_template.xml', + ], + 'qweb': ['static/src/xml/pos_return.xml'], + 'license': 'AGPL-3', + 'installable': True, + 'auto_install': False, + 'application': False, + +} diff --git a/pos_return/models/__init__.py b/pos_return/models/__init__.py new file mode 100644 index 000000000..41ee556be --- /dev/null +++ b/pos_return/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import pos_return diff --git a/pos_return/models/pos_return.py b/pos_return/models/pos_return.py new file mode 100644 index 000000000..c96f284f0 --- /dev/null +++ b/pos_return/models/pos_return.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +from odoo import models, api, fields + + +class PosReturn(models.Model): + _inherit = 'pos.order' + + return_ref = fields.Char(string='Return Ref', readonly=True, copy=False) + return_status = fields.Selection([ + ('nothing_return', 'Nothing Returned'), + ('partialy_return', 'Partialy Returned'), + ('fully_return', 'Fully Returned') + ], string="Return Status", default='nothing_return', + help="Return status of Order") + + @api.model + def get_lines(self, ref): + result = [] + order_id = self.search([('pos_reference', '=', ref)], limit=1) + if order_id: + lines = self.env['pos.order.line'].search([('order_id', '=', order_id.id)]) + for line in lines: + if line.qty - line.returned_qty > 0: + new_vals = { + 'product_id': line.product_id.id, + 'product': line.product_id.name, + 'qty': line.qty - line.returned_qty, + 'price_unit': line.price_unit, + 'discount': line.discount, + 'line_id': line.id, + } + result.append(new_vals) + + return [result] + + @api.model + def get_client(self, ref): + order_id = self.search([('pos_reference', '=', ref)], limit=1) + client = '' + if order_id: + client = order_id.partner_id.id + return client + + def _order_fields(self, ui_order): + order = super(PosReturn, self)._order_fields(ui_order) + if ui_order['lines']: + for data in ui_order['lines']: + if data[2]['line_id']: + line = self.env['pos.order.line'].search([('id', '=', data[2]['line_id'])]) + if line: + qty = -(data[2]['qty']) + line.returned_qty += qty + if ui_order['return_ref']: + order['return_ref'] = ui_order['return_ref'] + parent_order = self.search([('pos_reference', '=', ui_order['return_ref'])], limit=1) + lines = self.env['pos.order.line'].search([('order_id', '=', parent_order.id)]) + ret = 0 + qty = 0 + for line in lines: + qty += line.qty + if line.returned_qty: + ret += 1 + if qty-ret == 0: + parent_order.return_status = 'fully_return' + elif ret: + if qty > ret: + parent_order.return_status = 'partialy_return' + + return order + + @api.model + def get_status(self, ref): + order_id = self.search([('pos_reference', '=', ref)], limit=1) + if order_id.return_status == 'fully_return': + return False + else: + return True + + +class NewPosLines(models.Model): + _inherit = "pos.order.line" + + returned_qty = fields.Integer(string='Returned', digits=0) diff --git a/pos_return/static/description/backend.png b/pos_return/static/description/backend.png new file mode 100644 index 000000000..40516e664 Binary files /dev/null and b/pos_return/static/description/backend.png differ diff --git a/pos_return/static/description/banner.jpg b/pos_return/static/description/banner.jpg new file mode 100644 index 000000000..874a6d32a Binary files /dev/null and b/pos_return/static/description/banner.jpg differ diff --git a/pos_return/static/description/button.png b/pos_return/static/description/button.png new file mode 100644 index 000000000..2e81fedf7 Binary files /dev/null and b/pos_return/static/description/button.png differ diff --git a/pos_return/static/description/fully.png b/pos_return/static/description/fully.png new file mode 100644 index 000000000..df13a4eed Binary files /dev/null and b/pos_return/static/description/fully.png differ diff --git a/pos_return/static/description/icon.png b/pos_return/static/description/icon.png new file mode 100644 index 000000000..787de69d0 Binary files /dev/null and b/pos_return/static/description/icon.png differ diff --git a/pos_return/static/description/index.html b/pos_return/static/description/index.html new file mode 100644 index 000000000..72ed693ce --- /dev/null +++ b/pos_return/static/description/index.html @@ -0,0 +1,139 @@ +
+
+

Product Return In POS

+

Manages Product Return From POS Frontend

+

Cybrosys Technologies

+
+
+

Features:

+
+ Manage Product Return From Frontend.
+
+
+
+ +
+
+
+

Overview

+

+ This app will help you to return the products in pos from the user interface. +

+
+
+
+ +
+
+
+ Create a normal order. +
+ +
+
+
+
+ +
+
+
+
+ +
+ Click on Return button In order to return orders +
+
+
+ +
+
+
+
+ +
+ Select the order and click on Return. +
+
+
+ +
+
+
+
+ +
+ Return some of the products. +
+
+
+ +
+
+
+
+ +
+
+ When we create the return for the same order we get the rest of the products to return. +
+
+ +
+
+
+
+ +
+
+ We select a order that fully returned a warning will come. +
+
+ +
+
+
+
+ +
+ We get the return reference very easily. +
+
+
+ +
+
+
+
+ +
+ We get the return reference and status of the return from backend also. +
+
+
+ +
+

Need Any Help?

+ +
+ diff --git a/pos_return/static/description/line.png b/pos_return/static/description/line.png new file mode 100644 index 000000000..53ae1211e Binary files /dev/null and b/pos_return/static/description/line.png differ diff --git a/pos_return/static/description/oreder_1.png b/pos_return/static/description/oreder_1.png new file mode 100644 index 000000000..39ed6102a Binary files /dev/null and b/pos_return/static/description/oreder_1.png differ diff --git a/pos_return/static/description/ref.png b/pos_return/static/description/ref.png new file mode 100644 index 000000000..fa2227eee Binary files /dev/null and b/pos_return/static/description/ref.png differ diff --git a/pos_return/static/description/rest.png b/pos_return/static/description/rest.png new file mode 100644 index 000000000..6ec332195 Binary files /dev/null and b/pos_return/static/description/rest.png differ diff --git a/pos_return/static/description/ret_order.png b/pos_return/static/description/ret_order.png new file mode 100644 index 000000000..b01c52ddf Binary files /dev/null and b/pos_return/static/description/ret_order.png differ diff --git a/pos_return/static/src/js/pos_return.js b/pos_return/static/src/js/pos_return.js new file mode 100644 index 000000000..ce5dfcded --- /dev/null +++ b/pos_return/static/src/js/pos_return.js @@ -0,0 +1,549 @@ +odoo.define('pos_return',function(require){ +"use strict"; + +var gui = require('point_of_sale.gui'); +var chrome = require('point_of_sale.chrome'); +var popups = require('point_of_sale.popups'); +var core = require('web.core'); +var PosBaseWidget = require('point_of_sale.BaseWidget'); +var models = require('point_of_sale.models'); +var PosModelSuper = models.PosModel; +var pos_screens = require('point_of_sale.screens'); +var Model = require('web.DataModel'); +var QWeb = core.qweb; +var _t = core._t; +var exports = {}; + +models.load_models({ + model: 'pos.order', + fields: ['id', 'name', 'session_id', 'pos_reference', 'partner_id', 'amount_total','lines', 'amount_tax','return_ref'], + loaded: function (self, pos_orders) { + var orders = []; + for (var i in pos_orders){ + orders[pos_orders[i].id] = pos_orders[i]; + } + self.pos_orders = orders; + self.order = []; + for (var i in pos_orders){ + self.order[i] = pos_orders[i]; + } + }, + }); +var _super_posmodel = models.PosModel.prototype; +models.PosModel = models.PosModel.extend({ + initialize: function (session, attributes) { + var posorder_model = _.find(this.models, function(model){ + return model.model === 'pos.order'; + }); + posorder_model.fields.push('return_status'); + return _super_posmodel.initialize.call(this, session, attributes); + }, +}); + +models.load_models({ + model: 'pos.order.line', + fields: ['product_id','qty','price_unit','price_subtotal_incl','order_id','discount'], + loaded: function(self,order_lines){ + self.order_line = []; + for (var i = 0; i < order_lines.length; i++) { + self.order_line[i] = order_lines[i]; + } + }, +}); + +var DomCache = core.Class.extend({ + init: function(options){ + options = options || {}; + this.max_size = options.max_size || 2000; + + this.cache = {}; + this.access_time = {}; + this.size = 0; + }, + cache_node: function(key,node){ + var cached = this.cache[key]; + this.cache[key] = node; + this.access_time[key] = new Date().getTime(); + if(!cached){ + this.size++; + while(this.size >= this.max_size){ + var oldest_key = null; + var oldest_time = new Date().getTime(); + for(key in this.cache){ + var time = this.access_time[key]; + if(time <= oldest_time){ + oldest_time = time; + oldest_key = key; + } + } + if(oldest_key){ + delete this.cache[oldest_key]; + delete this.access_time[oldest_key]; + } + this.size--; + } + } + return node; + }, + clear_node: function(key) { + var cached = this.cache[key]; + if (cached) { + delete this.cache[key]; + delete this.access_time[key]; + this.size --; + } + }, + get_node: function(key){ + var cached = this.cache[key]; + if(cached){ + this.access_time[key] = new Date().getTime(); + } + return cached; + }, + }); + +var ReturnButton = pos_screens.ActionButtonWidget.extend({ + template: 'ReturnButton', + button_click: function(){ + if (this.pos.get_order().get_orderlines().length === 0){ + this.gui.show_screen('ReturnOrdersWidget'); + } + else{ + this.gui.show_popup('error',{ + title :_t('Process Only one operation at a time'), + body :_t('Process the current order first'), + }); + } + }, +}); + +pos_screens.define_action_button({ + 'name': 'Return', + 'widget': ReturnButton, + 'condition': function(){ + return this.pos; + }, +}); + +models.PosModel = models.PosModel.extend({ + _save_to_server: function (orders, options) { + var result_new = PosModelSuper.prototype._save_to_server.call(this, orders, options); + var self = this; + var new_order = {}; + var orders_list = self.pos_orders; + for (var i in orders) { + var partners = self.partners; + var partner = ""; + for(var j in partners){ + if(partners[j].id == orders[i].data.partner_id){ + partner = partners[j].name; + } + } + new_order = { + 'amount_tax': orders[i].data.amount_tax, + 'amount_total': orders[i].data.amount_total, + 'pos_reference': orders[i].data.name, + 'return_ref': orders[i].data.return_ref, + 'partner_id': [orders[i].data.partner_id, partner], + 'return_status':orders[i].data.return_status, + 'session_id': [ + self.pos_session.id, self.pos_session.name + ] + }; + orders_list.push(new_order); + self.pos_orders = orders_list; + self.gui.screen_instances.ReturnOrdersWidget.render_list(orders_list); + } + return result_new; + }, +}); + +var ReturnOrdersWidget = pos_screens.ScreenWidget.extend({ + template: 'ReturnOrdersWidget', + + init: function(parent, options){ + this._super(parent, options); + this.order_cache = new DomCache(); + this.order_string = ""; + this.pos_reference = ""; + }, + + auto_back: true, + renderElement: function () { + this._super(this); + var self = this; + + }, + + show: function(){ + var self = this; + this._super(); + + this.renderElement(); + this.details_visible = false; + + this.$('.back').click(function(){ + self.gui.back(); + }); + var pos_orders = this.pos.pos_orders; + this.render_list(pos_orders); + + + var search_timeout = null; + + if(this.pos.config.iface_vkeyboard && this.chrome.widget.keyboard){ + this.chrome.widget.keyboard.connect(this.$('.searchbox input')); + } + + this.$('.searchbox input').on('keypress',function(event){ + clearTimeout(search_timeout); + + var query = this.value; + search_timeout = setTimeout(function(){ + self.perform_search(query,event.which === 13); + },70); + }); + + this.$('.searchbox .search-clear').click(function(){ + self.clear_search(); + }); + }, + hide: function () { + this._super(); + this.new_client = null; + }, + perform_search: function(query, associate_result){ + var new_orders; + if(query){ + new_orders = this.search_order(query); + + this.render_list(new_orders); + }else{ + var orders = this.pos.pos_orders; + this.render_list(orders); + } + }, + search_order: function(query){ + var self = this; + try { + query = query.replace(/[\[\]\(\)\+\*\?\.\-\!\&\^\$\|\~\_\{\}\:\,\\\/]/g,'.'); + query = query.replace(' ','.+'); + var re = RegExp("([0-9]+):.*?"+query,"gi"); + }catch(e){ + return []; + } + var results = []; + for(var i = 0; i < Math.min(self.pos.pos_orders.length,1000); i++){ + var r = re.exec(this.order_string); + if(r){ + var id = Number(r[1]); + results.push(this.get_order_by_id(id)); + }else{ + break; + } + } + return results; + }, + // returns the order with the id provided + get_order_by_id: function (id) { + return this.pos.pos_orders[id]; + }, + clear_search: function(){ + var orders = this.pos.pos_orders; + this.render_list(orders); + this.$('.searchbox input')[0].value = ''; + this.$('.searchbox input').focus(); + }, + render_list: function(orders){ + var self = this; + for(var i = 0, len = Math.min(orders.length,1000); i < len; i++) { + if (orders[i]) { + var order = orders[i]; + self.order_string += i + ':' + order.pos_reference + '\n'; + } + } + this.$('.order-list-lines').delegate('.return-button','click',function(event){ + + var pos_ref = $(this).data('id'); + var order_new = null; + for(var i = 0, len = Math.min(orders.length,1000); i < len; i++) { + if (orders[i] && orders[i].pos_reference == pos_ref) { + order_new = orders[i]; + } + } + $('span.searchbox').css('display', 'none'); + $('.button.return').css('display', 'block') + self.pos_reference = order_new.pos_reference; + if (order_new.return_ref){ + self.gui.show_popup('error',_t('This is a returned order')); + self.gui.show_popup('error',{ + title :_t('Cannot Return'), + body :_t('This order is a returned order'), + }); + } + else{ + new Model('pos.order').call('get_status',[order_new.pos_reference]).then(function(result){ + if (result){ + self.gui.show_popup('OrderReturnWidget',{ + ref: pos_ref + }); + } + else{ + self.gui.show_popup('error',{ + title :_t('Fully Returned Order'), + body :_t('This order is fully returned'), + }); + + + } + }); + + } + }); + + var contents = this.$el[0].querySelector('.order-list-lines'); + if (contents){ + contents.innerHTML = ""; + for(var i = 0, len = Math.min(orders.length,1000); i < len; i++) { + if (orders[i]) { + var order = orders[i]; + var orderline = this.order_cache.get_node(order.id); + if (!orderline) { + var clientline_html = QWeb.render('OrderLines', {widget: this, order: order}); + var orderline = document.createElement('tbody'); + orderline.innerHTML = clientline_html; + orderline = orderline.childNodes[1]; + if (order.id){ + this.order_cache.cache_node(order.id, orderline); + } + else{ + this.order_cache.cache_node(i, orderline); + } + } + contents.appendChild(orderline); + } + } + } + }, + + close: function(){ + this._super(); + }, +}); +var OrderReturnWidget = PosBaseWidget.extend({ + template: 'OrderReturnWidget', + + init: function(parent, options){ + this._super(parent, options); + this.order_cache = new DomCache(); + this.ordernes = ""; + this.pos_reference = ""; + this.client =""; + }, + show: function (options) { + var self = this; + this._super(options); + var pos_orders = this.pos.pos_orders; + this.render_list(options); + + }, + close: function(){ + if (this.pos.barcode_reader) { + this.pos.barcode_reader.restore_callbacks(); + } + }, + events: { + 'click .button.cancel': 'click_cancel', + 'click .button.confirm': 'click_confirm', + }, + + render_list:function(options){ + var order_new = null; + $("#table-body").empty(); + var lines = []; + this.pos_reference = options.ref + new Model('pos.order').call('get_lines',[options.ref]).then(function(result){ + lines = result[0]; + this.client = result[1]; + for(var j=0;j < lines.length; j++){ + var product_line = lines[j]; + var rows = ""; + var id = product_line.product_id + var price_unit = product_line.price_unit; + var name =product_line.product; + var qty = product_line.qty; + var line_id = product_line.line_id; + var discount = product_line.discount; + rows += "" + id + "" + price_unit +" " + name + "" + qty + "" + discount + "" + line_id + ""; + $(rows).appendTo("#list tbody"); + var rows = document.getElementById('list').rows; + for (var row = 0; row < rows.length; row++) { + var cols = rows[row].cells; + cols[0].style.display = 'none'; + cols[1].style.display = 'none'; + cols[5].style.display = 'none'; + + } + + } + var table = document.getElementById('list'); + var tr = table.getElementsByTagName("tr"); + for (var i = 1; i < tr.length; i++) { + var td = document.createElement('td'); + var input = document.createElement('input'); + input.setAttribute("type", "text"); + input.setAttribute("value", 0); + input.setAttribute("id", "text"+i); + td.appendChild(input); + tr[i].appendChild(td); + + } + + }); + + }, + click_confirm: function(){ + var self = this; + var myTable = document.getElementById('list').tBodies[0]; + var count = 0 + var c = 1 + for (var r=0, n = myTable.rows.length; r < n; r++) { + var row = myTable.rows[r] + var return_qty = document.getElementById("text"+c).value + if (row.cells[3].innerHTML < return_qty){ + count +=1 + } + c = c+1 + } + if (count > 0){ + alert('Please check the Returned Quantity,it is higher than purchased') + } + else{ + var c = 1 + // OrderSuper.prototype.set_client.call(this, this.client); + for (var r=0, n = myTable.rows.length; r < n; r++) { + row = myTable.rows[r] + var return_qty = document.getElementById("text"+c).value + var product = this.pos.db.get_product_by_id(row.cells[0].innerHTML); + if (!product) { + return; + } + if (return_qty > 0){ + this.pos.get_order().add_product(product, { + price: row.cells[1].innerHTML, + quantity: -(return_qty), + discount:row.cells[4].innerHTML, + merge: false, + extras: {pos_ref: this.pos_reference, + label:row.cells[5].innerHTML}, + }); + + } + c = c+1 + } + + } + this.gui.close_popup(); + + self.gui.show_screen('products'); + + }, + click_cancel: function(){ + this.gui.close_popup(); + + }, + +}); + +var OrderSuper = models.Order; +models.Order = models.Order.extend({ + initialize: function(attributes,options){ + var order = OrderSuper.prototype.initialize.call(this, attributes,options); + order.return_ref = ''; + return order; + }, + init_from_JSON: function(json) { + OrderSuper.prototype.init_from_JSON.call(this, json); + this.return_ref = json.return_ref; + + + }, + export_as_JSON: function() { + var json_new = OrderSuper.prototype.export_as_JSON.call(this); + json_new.return_ref = this.get_return_ref(); + return json_new; + }, + + get_return_ref: function(){ + return this.return_ref; + }, + + add_product: function (product, options) { + OrderSuper.prototype.add_product.call(this, product, options); + var order = this.pos.get_order(); + var last_orderline = this.get_last_orderline(); + if (options !== undefined){ + if(options.extras !== undefined){ + for (var prop in options.extras) { + if (prop ==='pos_ref'){ + this.return_ref = options.extras['pos_ref'] + this.trigger('change',this); + var self = this; + var curr_client = order.get_client(); + if (!curr_client) { + new Model('pos.order').call('get_client',[options.extras['pos_ref']]).then(function(result){ + if (result){ + var partner = self.pos.db.get_partner_by_id(result); + order.set_client(partner); + + } + }); + } + + } + else if(prop ==='label'){ + order.selected_orderline.set_order_line_id(options.extras['label']); + } + } + + } + + } + + }, + + }); +var OrderlineSuper = models.Orderline; +models.Orderline = models.Orderline.extend({ + initialize: function(attr,options){ + OrderlineSuper.prototype.initialize.call(this, attr,options); + this.line_id = ''; + + }, + init_from_JSON: function(json) { + OrderlineSuper.prototype.init_from_JSON.call(this, json); + this.line_id = json.line_id + }, + clone: function(){ + var orderline = OrderlineSuper.prototype.clone.call(this); + orderline.line_id = this.line_id; + return orderline; + }, + get_line_id: function(){ + return this.line_id; + }, + set_order_line_id:function(id){ + this.line_id = id; + this.trigger('change',this); + }, + export_as_JSON: function(){ + var json = OrderlineSuper.prototype.export_as_JSON.apply(this,arguments); + json.line_id = this.get_line_id(); + return json; + }, +}); + +gui.define_screen({name:'ReturnOrdersWidget', widget: ReturnOrdersWidget}); +gui.define_popup({name:'OrderReturnWidget', widget: OrderReturnWidget}); + +}); diff --git a/pos_return/static/src/xml/pos_return.xml b/pos_return/static/src/xml/pos_return.xml new file mode 100644 index 000000000..cc87da2a5 --- /dev/null +++ b/pos_return/static/src/xml/pos_return.xml @@ -0,0 +1,103 @@ + + + + +
+ Return +
+
+ + + + + + + + + + + + + Return + + + + +
+
+
+ + + Cancel + + + + + +
+
+
+ +
+
+
+ + + + + + + + + + + + +
Reciept Ref.Return Ref.PartnerSessionAmount Total
+
+
+
+
+
+
+
+
+ + + + +
\ No newline at end of file diff --git a/pos_return/views/pos_template.xml b/pos_return/views/pos_template.xml new file mode 100644 index 000000000..58837d926 --- /dev/null +++ b/pos_return/views/pos_template.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/pos_return/views/return.xml b/pos_return/views/return.xml new file mode 100644 index 000000000..8727bfbca --- /dev/null +++ b/pos_return/views/return.xml @@ -0,0 +1,31 @@ + + + + + pos.order.form.extend + pos.order + + + + + + + + + + + + + + pos.order.tree.extend + pos.order + + + + + + + + + + \ No newline at end of file