diff --git a/pos_order_to_sale_order/README.rst b/pos_order_to_sale_order/README.rst new file mode 100644 index 0000000000..219b02160d --- /dev/null +++ b/pos_order_to_sale_order/README.rst @@ -0,0 +1,159 @@ +======================= +PoS Order To Sale Order +======================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png + :target: https://odoo-community.org/page/development-status + :alt: Production/Stable +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpos-lightgray.png?logo=github + :target: https://github.com/OCA/pos/tree/8.0/pos_order_to_sale_order + :alt: OCA/pos +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/pos-8-0/pos-8-0-pos_order_to_sale_order + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/184/8.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the functionality of point of sale to allow sale orders +creation from the Point of Sale. + + +In the POS UI, buttons has been added to create a sale order and discard +the current POS order. + +This module is usefull in many cases, for exemple : + +* take orders with a very simple interface + +* if you have some customers that come every day in your shop, but want to + have a unique invoice at the end of the month. With that module, you can + create a sale order and deliver products every time to keep your stock value + correct, and to create a unique invoice, when you want. + + +Three options are available: + +* **Create a draft Order** + A new sale order in a draft mode will be created that can be changed later. + +.. figure:: https://raw.githubusercontent.com/OCA/pos/8.0/pos_order_to_sale_order/static/description/pos_create_picking_option_1.png + :width: 800 px + +* **Create a Confirmed Order** + A new sale order will be created and confirmed. + +.. figure:: https://raw.githubusercontent.com/OCA/pos/8.0/pos_order_to_sale_order/static/description/pos_create_picking_option_2.png + :width: 800 px + +* **Create Delivered Picking** (by default) + A new sale order will be created and confirmed. the associated picking + will be marked as delivered. + +.. figure:: https://raw.githubusercontent.com/OCA/pos/8.0/pos_order_to_sale_order/static/description/pos_create_picking_option_3.png + :width: 800 px + + +**Technical Notes** + +* Some hooks are defined in the JS file, to define custom behaviour after + having created the sale order (and the stock picking). + +* Some prepare functions are available in the sale.order model, to overload + the creation of the sale order. + +* You could be interested by another module, pos_sale_order, that completely + alter Point of Sale module, avoiding creating Pos Orders, and creating + allways Sale Orders. + This module is a WIP state, and is available here: + https://github.com/OCA/pos/pull/35 + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure this module, you need to: + +* Go to Point Of Sale / Configuration / Point of Sale +* Check the box 'Create Sale Orders' +* Select the desired default behaviour + +.. figure:: https://raw.githubusercontent.com/OCA/pos/8.0/pos_order_to_sale_order/static/description/pos_config_form.png + :width: 800 px + +Known issues / Roadmap +====================== + +* Because of the poor design of the Odoo Point of Sale, some basic features + are not available by default, like pricelist, fiscal position, etc ... + For that reason, unit price will be recomputed by default, when creating the + sale order, and the unit price of the current bill will not be used. + +Note that this problem is fixed if ``pos_pricelist`` is installed. +(same repository) In that cases, the pricelist, the unit prices and the taxes +will be the same in the order, as in the displayed bill. + +.. figure:: https://raw.githubusercontent.com/OCA/pos/8.0/pos_order_to_sale_order/static/description/pos_create_picking_confirm.png + :width: 800 px + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* GRAP + +Contributors +~~~~~~~~~~~~ + +* Sylvain LE GAL (https://www.twitter.com/legalsylvain) + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-legalsylvain| image:: https://github.com/legalsylvain.png?size=40px + :target: https://github.com/legalsylvain + :alt: legalsylvain + +Current `maintainer `__: + +|maintainer-legalsylvain| + +This module is part of the `OCA/pos `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/pos_order_to_sale_order/__init__.py b/pos_order_to_sale_order/__init__.py new file mode 100644 index 0000000000..a0fdc10fe1 --- /dev/null +++ b/pos_order_to_sale_order/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import models diff --git a/pos_order_to_sale_order/__manifest__.py b/pos_order_to_sale_order/__manifest__.py new file mode 100644 index 0000000000..f1874c9641 --- /dev/null +++ b/pos_order_to_sale_order/__manifest__.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2017 - Today: GRAP (http://www.grap.coop) +# Copyright (C) 2019 - Today: Guadaltech S.L (http://www.guadaltech.es) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + 'name': 'PoS Order To Sale Order', + 'version': '12.0.1', + 'author': 'GRAP,Odoo Community Association (OCA),Guadaltech S.L', + 'category': 'Point Of Sale', + 'license': 'AGPL-3', + 'depends': [ + 'point_of_sale', + ], + 'maintainers': ['legalsylvain'], + 'development_status': "Production/Stable", + 'website': 'https://odoo-community.org/', + 'data': [ + 'views/view_pos_config.xml', + 'views/pos_order_to_sale_order.xml', + ], + 'demo': [ + 'demo/res_groups.xml', + ], + 'qweb': [ + 'static/src/xml/pos_order_to_sale_order.xml', + ], +} diff --git a/pos_order_to_sale_order/demo/res_groups.xml b/pos_order_to_sale_order/demo/res_groups.xml new file mode 100644 index 0000000000..158b505f78 --- /dev/null +++ b/pos_order_to_sale_order/demo/res_groups.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pos_order_to_sale_order/i18n/fr.po b/pos_order_to_sale_order/i18n/fr.po new file mode 100644 index 0000000000..aeae042eb4 --- /dev/null +++ b/pos_order_to_sale_order/i18n/fr.po @@ -0,0 +1,213 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_order_to_sale_order +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-03-23 15:48+0000\n" +"PO-Revision-Date: 2018-03-23 15:48+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:56 +#, python-format +msgid "" +"\n" +"Note if you have manually changed unit prices for some products, this " +"changes will not been taken into account in the sale order." +msgstr "" +"\n" +"Remarque : si vous aviez changé le prix unitaire de certains produits, ces " +"changements ne seront pas pris en compte dans la vente." + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:151 +#, python-format +msgid "Check your internet connection and try again." +msgstr "Veuillez vérifier votre connexion internet et essayer de nouveau." + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:36 +#, python-format +msgid "Create Confirmed Order" +msgstr "Créer une vente confirmée" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:37 +#, python-format +msgid "Create Confirmed Sale Order and discard the current PoS Order?" +msgstr "Créer une vente confirmée, et supprimer le ticket de caisse en cours ?" + +#. module: pos_order_to_sale_order +#: field:pos.config,iface_create_confirmed_sale_order:0 +msgid "Create Confirmed Sale Orders" +msgstr "Créer des ventes confirmées" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:45 +#, python-format +msgid "Create Delivered Order" +msgstr "Créer une vente livrée (BL)" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:46 +#, python-format +msgid "Create Delivered Sale Order and discard the current PoS Order?" +msgstr "" +"Créer une vente livrée (BL), et supprimer le ticket de caisse en cours ?" + +#. module: pos_order_to_sale_order +#: field:pos.config,iface_create_delivered_sale_order:0 +msgid "Create Delivered Sale Orders" +msgstr "Créer des ventes livrées (BL)" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:27 +#, python-format +msgid "Create Draft Order" +msgstr "Créer un devis en brouillon" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:28 +#, python-format +msgid "Create Draft Sale Order and discard the current PoS Order?" +msgstr "" +"Créer une vente en brouillon, et supprimer le ticket de caisse en cours ?" + +#. module: pos_order_to_sale_order +#: field:pos.config,iface_create_draft_sale_order:0 +msgid "Create Draft Sale Orders" +msgstr "Créer des ventes en brouillon" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:78 +#, python-format +msgid "Empty Order" +msgstr "Commande vide" + +#. module: pos_order_to_sale_order +#: help:pos.config,iface_create_confirmed_sale_order:0 +msgid "" +"If checked, the cashier will have the possibility to create a confirmed Sale " +"Order, based on the current draft PoS Order." +msgstr "" +"En cochant la case, le caissier aura la possibilité de créer une vente " +"confirmée, à partir du ticket de caisse en cours." + +#. module: pos_order_to_sale_order +#: help:pos.config,iface_create_delivered_sale_order:0 +msgid "" +"If checked, the cashier will have the possibility to create a confirmed sale " +"Order, based on the current draft PoS Order.\n" +" the according picking will be marked as delivered. Only invoices process " +"will be possible." +msgstr "" +"En cochant la case, le caissier aura la possibilité de créer une vente " +"confirmée, à partir du ticket de caisse en cours.\n" +" Le bon de livraison correspondant sera marqué comme livré. Seul le " +"processus de facturation sera possible." + +#. module: pos_order_to_sale_order +#: help:pos.config,iface_create_draft_sale_order:0 +msgid "" +"If checked, the cashier will have the possibility to create a draft Sale " +"Order, based on the current draft PoS Order." +msgstr "" +"En cochant la case, le caissier aura la possibilité de créer une vente en " +"brouillon, à partir du ticket de caisse en cours." + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:88 +#, python-format +msgid "No customer defined" +msgstr "Pas de client défini" + +#. module: pos_order_to_sale_order +#: code:addons/pos_order_to_sale_order/models/sale_order.py:19 +#, python-format +msgid "Point of Sale %s" +msgstr "Point de vente %s" + +#. module: pos_order_to_sale_order +#: model:ir.model,name:pos_order_to_sale_order.model_sale_order +msgid "Sales Order" +msgstr "Commande de ventes" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:150 +#, python-format +msgid "The order could not be sent" +msgstr "La commande n'a pas pu être envoyée" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:79 +#, python-format +msgid "There must be at least one product in your order to create Sale Order." +msgstr "" +"Il doit y avoir au moins un produit dans votre commande pour créer une vente" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:40 +#, python-format +msgid "" +"This operation will permanently discard the current PoS Order and create a " +"confirmed Sale Order, based on the current order lines." +msgstr "" +"Cette opération va supprimer définitivement le ticket de caisse en cours, et " +"créer une vente confirmée, en se basant sur les lignes du ticket en cours." + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:49 +#, python-format +msgid "" +"This operation will permanently discard the current PoS Order and create a " +"confirmed Sale Order, based on the current order lines. The according " +"picking will be marked as delivered." +msgstr "" +"Cette opération va supprimer définitivement le ticket de caisse en cours, et " +"créer une vente confirmée, en se basant sur les lignes du ticket en cours. " +"Le bon de livraison correspondant sera marqué comme livré." + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:31 +#, python-format +msgid "" +"This operation will permanently discard the current PoS Order and create a " +"draft Sale Order, based on the current order lines." +msgstr "" +"Cette opération va supprimer définitivement le ticket de caisse en cours, et " +"créer une vente en brouillon, en se basant sur les lignes du ticket en cours." + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:89 +#, python-format +msgid "" +"You should select a customer in order to create a Sale Order. Please select " +"one by clicking the order tab." +msgstr "" +"Vous devez sélectionner un client, afin de créer une vente. Veuillez en " +"sélectionner un en cliquant sur l'onglet de la commande." diff --git a/pos_order_to_sale_order/i18n/pos_order_to_sale_order.pot b/pos_order_to_sale_order/i18n/pos_order_to_sale_order.pot new file mode 100644 index 0000000000..d0ec24faf0 --- /dev/null +++ b/pos_order_to_sale_order/i18n/pos_order_to_sale_order.pot @@ -0,0 +1,170 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_order_to_sale_order +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:56 +#, python-format +msgid "\n" +"Note if you have manually changed unit prices for some products, this changes will not been taken into account in the sale order." +msgstr "" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:151 +#, python-format +msgid "Check your internet connection and try again." +msgstr "" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:36 +#, python-format +msgid "Create Confirmed Order" +msgstr "" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:37 +#, python-format +msgid "Create Confirmed Sale Order and discard the current PoS Order?" +msgstr "" + +#. module: pos_order_to_sale_order +#: field:pos.config,iface_create_confirmed_sale_order:0 +msgid "Create Confirmed Sale Orders" +msgstr "" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:45 +#, python-format +msgid "Create Delivered Order" +msgstr "" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:46 +#, python-format +msgid "Create Delivered Sale Order and discard the current PoS Order?" +msgstr "" + +#. module: pos_order_to_sale_order +#: field:pos.config,iface_create_delivered_sale_order:0 +msgid "Create Delivered Sale Orders" +msgstr "" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:27 +#, python-format +msgid "Create Draft Order" +msgstr "" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:28 +#, python-format +msgid "Create Draft Sale Order and discard the current PoS Order?" +msgstr "" + +#. module: pos_order_to_sale_order +#: field:pos.config,iface_create_draft_sale_order:0 +msgid "Create Draft Sale Orders" +msgstr "" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:78 +#, python-format +msgid "Empty Order" +msgstr "" + +#. module: pos_order_to_sale_order +#: help:pos.config,iface_create_confirmed_sale_order:0 +msgid "If checked, the cashier will have the possibility to create a confirmed Sale Order, based on the current draft PoS Order." +msgstr "" + +#. module: pos_order_to_sale_order +#: help:pos.config,iface_create_delivered_sale_order:0 +msgid "If checked, the cashier will have the possibility to create a confirmed sale Order, based on the current draft PoS Order.\n" +" the according picking will be marked as delivered. Only invoices process will be possible." +msgstr "" + +#. module: pos_order_to_sale_order +#: help:pos.config,iface_create_draft_sale_order:0 +msgid "If checked, the cashier will have the possibility to create a draft Sale Order, based on the current draft PoS Order." +msgstr "" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:88 +#, python-format +msgid "No customer defined" +msgstr "" + +#. module: pos_order_to_sale_order +#: code:addons/pos_order_to_sale_order/models/sale_order.py:19 +#, python-format +msgid "Point of Sale %s" +msgstr "" + +#. module: pos_order_to_sale_order +#: model:ir.model,name:pos_order_to_sale_order.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:150 +#, python-format +msgid "The order could not be sent" +msgstr "" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:79 +#, python-format +msgid "There must be at least one product in your order to create Sale Order." +msgstr "" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:40 +#, python-format +msgid "This operation will permanently discard the current PoS Order and create a confirmed Sale Order, based on the current order lines." +msgstr "" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:49 +#, python-format +msgid "This operation will permanently discard the current PoS Order and create a confirmed Sale Order, based on the current order lines. The according picking will be marked as delivered." +msgstr "" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:31 +#, python-format +msgid "This operation will permanently discard the current PoS Order and create a draft Sale Order, based on the current order lines." +msgstr "" + +#. module: pos_order_to_sale_order +#. openerp-web +#: code:addons/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js:89 +#, python-format +msgid "You should select a customer in order to create a Sale Order. Please select one by clicking the order tab." +msgstr "" + diff --git a/pos_order_to_sale_order/models/__init__.py b/pos_order_to_sale_order/models/__init__.py new file mode 100644 index 0000000000..b0ad68cfdb --- /dev/null +++ b/pos_order_to_sale_order/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +from . import sale_order +from . import pos_config diff --git a/pos_order_to_sale_order/models/pos_config.py b/pos_order_to_sale_order/models/pos_config.py new file mode 100644 index 0000000000..d4d0280e08 --- /dev/null +++ b/pos_order_to_sale_order/models/pos_config.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2017 - Today: GRAP (http://www.grap.coop) +# Copyright (C) 2019 - Today: Guadaltech S.L (http://www.guadaltech.es) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import models, fields + + +class PosConfig(models.Model): + _inherit = 'pos.config' + + iface_create_draft_sale_order = fields.Boolean( + string='Create Draft Sale Orders', default=True, + help="If checked, the cashier will have the possibility to create" + " a draft Sale Order, based on the current draft PoS Order.") + + iface_create_confirmed_sale_order = fields.Boolean( + string='Create Confirmed Sale Orders', default=True, + help="If checked, the cashier will have the possibility to create" + " a confirmed Sale Order, based on the current draft PoS Order.") + + iface_create_delivered_sale_order = fields.Boolean( + string='Create Delivered Sale Orders', default=True, + help="If checked, the cashier will have the possibility to create" + " a confirmed sale Order, based on the current draft PoS Order.\n" + " the according picking will be marked as delivered. Only invoices" + " process will be possible.") diff --git a/pos_order_to_sale_order/models/sale_order.py b/pos_order_to_sale_order/models/sale_order.py new file mode 100644 index 0000000000..c09cb11572 --- /dev/null +++ b/pos_order_to_sale_order/models/sale_order.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2017 - Today: GRAP (http://www.grap.coop) +# Copyright (C) 2019 - Today: Guadaltech S.L (http://www.guadaltech.es) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import models, api, _ + + +class SaleOrder(models.Model): + _inherit = 'sale.order' + + @api.model + def _prepare_order_field_from_pos(self, order_data): + session_obj = self.env['pos.session'] + session = session_obj.browse(order_data['pos_session_id']) + res = {} + res.update({ + 'partner_id': order_data['partner_id'] or False, + 'origin': _("Point of Sale %s") % (session.name), + 'client_order_ref': order_data['name'], + 'user_id': order_data['user_id'] or False, + 'order_line': [], + }) + if order_data.get('pricelist_id'): + res.update({ + 'pricelist_id': order_data['pricelist_id'], + }) + else: + res.update({ + 'pricelist_id': 1, + }) + for line_data in order_data['lines']: + res['order_line'].append([ + 0, False, self._prepare_order_line_field_from_pos( + line_data[2], res)]) + return res + + @api.model + def _prepare_order_line_field_from_pos(self, line_data, sale_order_data): + res = {} + res.update({ + 'product_id': line_data['product_id'], + 'product_uom_qty': line_data['qty'], + 'price_unit': line_data['price_unit'], + 'discount': line_data['discount'], + 'tax_id': line_data['tax_ids'], + 'partner_id' : sale_order_data['partner_id'], + 'quantity' : line_data['qty'] + }) + return res + + @api.model + def create_order_from_pos(self, order_data): + is_pos_pricelist = len(self.env['ir.module.module'].search( + [('name', '=', 'pos_pricelist'), ('state', '=', 'installed')])) + # Create Draft Sale order + sale_order = self.create( + self.with_context(is_pos_pricelist=is_pos_pricelist). + _prepare_order_field_from_pos(order_data)) + + # Confirm Sale Order + if order_data['sale_order_state'] in ['confirmed', 'delivered']: + sale_order.action_confirm() + + # mark picking as delivered + if order_data['sale_order_state'] == 'delivered': + for picking in sale_order.picking_ids: + picking.action_confirm() + picking.action_assign() + for move in picking.move_ids_without_package: + move.quantity_done = move.product_uom_qty + picking.action_done() + + return { + 'sale_order_id': sale_order.id, + } diff --git a/pos_order_to_sale_order/readme/CONFIGURE.rst b/pos_order_to_sale_order/readme/CONFIGURE.rst new file mode 100644 index 0000000000..f77b481517 --- /dev/null +++ b/pos_order_to_sale_order/readme/CONFIGURE.rst @@ -0,0 +1,8 @@ +To configure this module, you need to: + +* Go to Point Of Sale / Configuration / Point of Sale +* Check the box 'Create Sale Orders' +* Select the desired default behaviour + +.. figure:: ../static/description/pos_config_form.png + :width: 800 px diff --git a/pos_order_to_sale_order/readme/CONTRIBUTORS.rst b/pos_order_to_sale_order/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..e1525ce042 --- /dev/null +++ b/pos_order_to_sale_order/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Sylvain LE GAL (https://www.twitter.com/legalsylvain) diff --git a/pos_order_to_sale_order/readme/DESCRIPTION.rst b/pos_order_to_sale_order/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..993e779e2a --- /dev/null +++ b/pos_order_to_sale_order/readme/DESCRIPTION.rst @@ -0,0 +1,52 @@ +This module extends the functionality of point of sale to allow sale orders +creation from the Point of Sale. + + +In the POS UI, buttons has been added to create a sale order and discard +the current POS order. + +This module is usefull in many cases, for exemple : + +* take orders with a very simple interface + +* if you have some customers that come every day in your shop, but want to + have a unique invoice at the end of the month. With that module, you can + create a sale order and deliver products every time to keep your stock value + correct, and to create a unique invoice, when you want. + + +Three options are available: + +* **Create a draft Order** + A new sale order in a draft mode will be created that can be changed later. + +.. figure:: ../static/description/pos_create_picking_option_1.png + :width: 800 px + +* **Create a Confirmed Order** + A new sale order will be created and confirmed. + +.. figure:: ../static/description/pos_create_picking_option_2.png + :width: 800 px + +* **Create Delivered Picking** (by default) + A new sale order will be created and confirmed. the associated picking + will be marked as delivered. + +.. figure:: ../static/description/pos_create_picking_option_3.png + :width: 800 px + + +**Technical Notes** + +* Some hooks are defined in the JS file, to define custom behaviour after + having created the sale order (and the stock picking). + +* Some prepare functions are available in the sale.order model, to overload + the creation of the sale order. + +* You could be interested by another module, pos_sale_order, that completely + alter Point of Sale module, avoiding creating Pos Orders, and creating + allways Sale Orders. + This module is a WIP state, and is available here: + https://github.com/OCA/pos/pull/35 diff --git a/pos_order_to_sale_order/readme/ROADMAP.rst b/pos_order_to_sale_order/readme/ROADMAP.rst new file mode 100644 index 0000000000..ed6dd416ea --- /dev/null +++ b/pos_order_to_sale_order/readme/ROADMAP.rst @@ -0,0 +1,11 @@ +* Because of the poor design of the Odoo Point of Sale, some basic features + are not available by default, like pricelist, fiscal position, etc ... + For that reason, unit price will be recomputed by default, when creating the + sale order, and the unit price of the current bill will not be used. + +Note that this problem is fixed if ``pos_pricelist`` is installed. +(same repository) In that cases, the pricelist, the unit prices and the taxes +will be the same in the order, as in the displayed bill. + +.. figure:: ../static/description/pos_create_picking_confirm.png + :width: 800 px diff --git a/pos_order_to_sale_order/static/description/icon.png b/pos_order_to_sale_order/static/description/icon.png new file mode 100644 index 0000000000..d547d9cb33 Binary files /dev/null and b/pos_order_to_sale_order/static/description/icon.png differ diff --git a/pos_order_to_sale_order/static/description/index.html b/pos_order_to_sale_order/static/description/index.html new file mode 100644 index 0000000000..738fc8ef25 --- /dev/null +++ b/pos_order_to_sale_order/static/description/index.html @@ -0,0 +1,496 @@ + + + + + + +PoS Order To Sale Order + + + +
+

PoS Order To Sale Order

+ + +

Production/Stable License: AGPL-3 OCA/pos Translate me on Weblate Try me on Runbot

+

This module extends the functionality of point of sale to allow sale orders +creation from the Point of Sale.

+

In the POS UI, buttons has been added to create a sale order and discard +the current POS order.

+

This module is usefull in many cases, for exemple :

+
    +
  • take orders with a very simple interface
  • +
  • if you have some customers that come every day in your shop, but want to +have a unique invoice at the end of the month. With that module, you can +create a sale order and deliver products every time to keep your stock value +correct, and to create a unique invoice, when you want.
  • +
+

Three options are available:

+
    +
  • Create a draft Order +A new sale order in a draft mode will be created that can be changed later.
  • +
+
+https://raw.githubusercontent.com/OCA/pos/8.0/pos_order_to_sale_order/static/description/pos_create_picking_option_1.png +
+
    +
  • Create a Confirmed Order +A new sale order will be created and confirmed.
  • +
+
+https://raw.githubusercontent.com/OCA/pos/8.0/pos_order_to_sale_order/static/description/pos_create_picking_option_2.png +
+
    +
  • Create Delivered Picking (by default) +A new sale order will be created and confirmed. the associated picking +will be marked as delivered.
  • +
+
+https://raw.githubusercontent.com/OCA/pos/8.0/pos_order_to_sale_order/static/description/pos_create_picking_option_3.png +
+

Technical Notes

+
    +
  • Some hooks are defined in the JS file, to define custom behaviour after +having created the sale order (and the stock picking).
  • +
  • Some prepare functions are available in the sale.order model, to overload +the creation of the sale order.
  • +
  • You could be interested by another module, pos_sale_order, that completely +alter Point of Sale module, avoiding creating Pos Orders, and creating +allways Sale Orders. +This module is a WIP state, and is available here: +https://github.com/OCA/pos/pull/35
  • +
+

Table of contents

+ +
+

Configuration

+

To configure this module, you need to:

+
    +
  • Go to Point Of Sale / Configuration / Point of Sale
  • +
  • Check the box ‘Create Sale Orders’
  • +
  • Select the desired default behaviour
  • +
+
+https://raw.githubusercontent.com/OCA/pos/8.0/pos_order_to_sale_order/static/description/pos_config_form.png +
+
+
+

Known issues / Roadmap

+
    +
  • Because of the poor design of the Odoo Point of Sale, some basic features +are not available by default, like pricelist, fiscal position, etc … +For that reason, unit price will be recomputed by default, when creating the +sale order, and the unit price of the current bill will not be used.
  • +
+

Note that this problem is fixed if pos_pricelist is installed. +(same repository) In that cases, the pricelist, the unit prices and the taxes +will be the same in the order, as in the displayed bill.

+
+https://raw.githubusercontent.com/OCA/pos/8.0/pos_order_to_sale_order/static/description/pos_create_picking_confirm.png +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • GRAP
  • +
+
+ +
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

legalsylvain

+

This module is part of the OCA/pos project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/pos_order_to_sale_order/static/description/pos_config_form.png b/pos_order_to_sale_order/static/description/pos_config_form.png new file mode 100644 index 0000000000..54076c8272 Binary files /dev/null and b/pos_order_to_sale_order/static/description/pos_config_form.png differ diff --git a/pos_order_to_sale_order/static/description/pos_create_picking_confirm.png b/pos_order_to_sale_order/static/description/pos_create_picking_confirm.png new file mode 100644 index 0000000000..436e20a77c Binary files /dev/null and b/pos_order_to_sale_order/static/description/pos_create_picking_confirm.png differ diff --git a/pos_order_to_sale_order/static/description/pos_create_picking_option_1.png b/pos_order_to_sale_order/static/description/pos_create_picking_option_1.png new file mode 100644 index 0000000000..b69fa6f106 Binary files /dev/null and b/pos_order_to_sale_order/static/description/pos_create_picking_option_1.png differ diff --git a/pos_order_to_sale_order/static/description/pos_create_picking_option_2.png b/pos_order_to_sale_order/static/description/pos_create_picking_option_2.png new file mode 100644 index 0000000000..46739f6cf4 Binary files /dev/null and b/pos_order_to_sale_order/static/description/pos_create_picking_option_2.png differ diff --git a/pos_order_to_sale_order/static/description/pos_create_picking_option_3.png b/pos_order_to_sale_order/static/description/pos_create_picking_option_3.png new file mode 100644 index 0000000000..28c3d02ce8 Binary files /dev/null and b/pos_order_to_sale_order/static/description/pos_create_picking_option_3.png differ diff --git a/pos_order_to_sale_order/static/src/css/pos_order_to_sale_order.css b/pos_order_to_sale_order/static/src/css/pos_order_to_sale_order.css new file mode 100644 index 0000000000..6915cf9f1e --- /dev/null +++ b/pos_order_to_sale_order/static/src/css/pos_order_to_sale_order.css @@ -0,0 +1,4 @@ +.blockUI.blockOverlay { + background-color: black; + opacity: 0.6; +} diff --git a/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js b/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js new file mode 100644 index 0000000000..5a7840dae1 --- /dev/null +++ b/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js @@ -0,0 +1,221 @@ +/* *************************************************************************** + Copyright (C) 2017 - Today: GRAP (http://www.grap.coop) + Copyright (C) 2019 - Today: Guadaltech (http://www.guadaltech.es) + @author: Sylvain LE GAL (https://twitter.com/legalsylvain) + License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +**************************************************************************** */ +odoo.define('pos_order_to_sale_order.pos_order_to_sale_order',function(require){ + + "use strict"; + + var core = require('web.core'); + var models = require('point_of_sale.models'); + var Chrome = require('point_of_sale.chrome'); + var screens = require('point_of_sale.screens'); + var PosBaseWidget = require('point_of_sale.BaseWidget'); + var rpc = require('web.rpc'); + var _t = core._t; + + + /* ************************************************************************ + New Widget CreateSaleOrderButtonWidget + ************************************************************************ */ + + var CreateSaleOrderButtonWidget = PosBaseWidget.extend({ + template: 'CreateSaleOrderButtonWidget', + + /** + * Define all the confirmation messages. + */ + init: function (parent, options) { + //var self = this + this._super(parent, options); + this.sale_order_state = options.sale_order_state; + if (this.sale_order_state === 'draft') { + this.display_text = _t("Create Draft Order"); + this.confirmation_message = _t( + 'Create Draft Sale Order and discard the current' + + ' PoS Order?'); + this.confirmation_comment = _t( + "This operation will permanently discard the current PoS" + + " Order and create a draft Sale Order, based on the" + + " current order lines."); + } else if (options.sale_order_state === 'confirmed') { + this.display_text = _t("Create Confirmed Order"); + this.confirmation_message = _t( + 'Create Confirmed Sale Order and discard the current' + + ' PoS Order?'); + this.confirmation_comment = _t( + "This operation will permanently discard the current PoS" + + " Order and create a confirmed Sale Order, based on the" + + " current order lines."); + } else if (options.sale_order_state === 'delivered') { + this.display_text = _t("Create Delivered Order"); + this.confirmation_message = _t( + 'Create Delivered Sale Order and discard the current' + + ' PoS Order?'); + this.confirmation_comment = _t( + "This operation will permanently discard the current PoS" + + " Order and create a confirmed Sale Order, based on the" + + " current order lines. The according picking will be" + + " marked as delivered."); + } + if (! this.pos.pricelist_engine) { + this.confirmation_comment += _t( + "\nNote if you have manually changed unit prices for" + + " some products, this changes will not been taken into" + + " account in the sale order.") + } + }, + + /** + * Define onclick function when the button to create sale order is + * clicked. + * - On click, check if there is a customer defined, + * - ask confirmation call server to create sale order, and delete + * the current order. + */ + renderElement: function () { + var self = this; + this._super(); + this.$el.click(function () { + var current_order = self.pos.get('selectedOrder'); + // Prevent empty delivery order + if (!current_order.orderlines) { + self.gui.show_popup('error', { + title: _t('Empty Order'), + body: _t( + 'There must be at least one product in your' + + ' order to create Sale Order.'), + }); + return; + } + // Check Customer + if (!current_order.get_client()) { + self.gui.show_popup('error', { + title: _t('No customer defined'), + body: _t( + 'You should select a customer in order to create' + + ' a Sale Order. Please select one by clicking' + + ' the order tab.'), + }); + return; + } + self.gui.show_popup('confirm', { + message: self.confirmation_message, + comment: self.confirmation_comment, + confirm: function () { + + rpc.query({model: 'sale.order', method: 'create_order_from_pos',args: [self.prepare_create_sale_order(current_order)]}) + .then(function (result) { + $.unblockUI(); + self.hook_create_sale_order_success(result); + + },function(err,ev){ + $.unblockUI(); + self.hook_create_sale_order_error(err, ev); + } + ); + }, + }); + }); + }, + + /** + * Overloadable function to send custom sale order data to server + */ + prepare_create_sale_order: function (order) { + var res = order.export_as_JSON(); + res.sale_order_state = this.sale_order_state; + return res; + }, + + /** + * Overloadable function to make custom action after Sale order + * Creation succeeded + */ + hook_create_sale_order_success: function (result) { + this.pos.get('selectedOrder').destroy(); + }, + + /** + * Overloadable function to make custom action after Sale order + * Creation failed + */ + hook_create_sale_order_error: function (error, event) { + event.preventDefault(); + var self = this; + if (error.code === 200) { + // Business Logic Error, not a connection problem + self.gui.show_popup('error-traceback', { + title: error.data.message, + body: error.data.debug, + }); + } else { + // Connexion problem + self.gui.show_popup('error', { + title: _t('The order could not be sent'), + body: _t( + 'Check your internet connection and try again.'), + }); + } + }, + + }); + + + /* ************************************************************************ + Extend PosWidget: + ************************************************************************ */ + Chrome.Chrome.include({ + + build_widgets: function () { + this._super(); + if (this.pos.config.iface_create_draft_sale_order) { + this.create_draft_sale_order_button = new CreateSaleOrderButtonWidget(this, {'sale_order_state': 'draft'}); + this.create_draft_sale_order_button.appendTo(this.$('ul.orderlines')); + } + if (this.pos.config.iface_create_confirmed_sale_order) { + this.create_confirmed_sale_order_button = new CreateSaleOrderButtonWidget(this, {'sale_order_state': 'confirmed'}); + this.create_confirmed_sale_order_button.appendTo( + this.$('ul.orderlines')); + } + if (this.pos.config.iface_create_delivered_sale_order) { + this.create_delivered_sale_order_button = new CreateSaleOrderButtonWidget(this, {'sale_order_state': 'delivered'}); + this.create_delivered_sale_order_button.appendTo( + this.$('ul.orderlines')); + } + }, + }); + + + /* ************************************************************************ + Extend OrderWidget + ************************************************************************ */ + screens.OrderWidget.include({ + + /** + * Overload renderElement(), to display buttons when the order change. + */ + renderElement: function (scrollbottom) { + this._super(scrollbottom); + if (this.create_draft_sale_order_button) { + this.create_draft_sale_order_button.appendTo( + this.$('ul.orderlines') + ); + } + if (this.create_confirmed_sale_order_button) { + this.create_confirmed_sale_order_button.appendTo( + this.$('ul.orderlines') + ); + } + if (this.create_delivered_sale_order_button) { + this.create_delivered_sale_order_button.appendTo( + this.$('ul.orderlines') + ); + } + + }, + }); + +}); diff --git a/pos_order_to_sale_order/static/src/xml/pos_order_to_sale_order.xml b/pos_order_to_sale_order/static/src/xml/pos_order_to_sale_order.xml new file mode 100644 index 0000000000..b6bfd57908 --- /dev/null +++ b/pos_order_to_sale_order/static/src/xml/pos_order_to_sale_order.xml @@ -0,0 +1,13 @@ + + diff --git a/pos_order_to_sale_order/views/pos_order_to_sale_order.xml b/pos_order_to_sale_order/views/pos_order_to_sale_order.xml new file mode 100644 index 0000000000..2868d11c0b --- /dev/null +++ b/pos_order_to_sale_order/views/pos_order_to_sale_order.xml @@ -0,0 +1,9 @@ + + + + diff --git a/pos_order_to_sale_order/views/view_pos_config.xml b/pos_order_to_sale_order/views/view_pos_config.xml new file mode 100644 index 0000000000..0a69ea32e6 --- /dev/null +++ b/pos_order_to_sale_order/views/view_pos_config.xml @@ -0,0 +1,40 @@ + + + + + pos.config + + + +

Create Sale From POS

+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +