Browse Source

Merge pull request #314 from legalsylvain/8.0_FIX_pos_order_to_sale_order

[FIX][8.0] pos_order_to_sale_order
pull/371/head
David Vidal 6 years ago
committed by GitHub
parent
commit
ecd124e2a9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 146
      pos_order_to_sale_order/README.rst
  2. 4
      pos_order_to_sale_order/__openerp__.py
  3. 19
      pos_order_to_sale_order/demo/product_template.xml
  4. 51
      pos_order_to_sale_order/demo/sale_order.xml
  5. 7
      pos_order_to_sale_order/demo/stock_picking_type.xml
  6. 8
      pos_order_to_sale_order/readme/CONFIGURE.rst
  7. 1
      pos_order_to_sale_order/readme/CONTRIBUTORS.rst
  8. 52
      pos_order_to_sale_order/readme/DESCRIPTION.rst
  9. 11
      pos_order_to_sale_order/readme/ROADMAP.rst
  10. 12
      pos_order_to_sale_order/static/src/css/pos_order_to_sale_order.css
  11. 200
      pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js
  12. 5
      pos_order_to_sale_order/views/pos_order_to_sale_order.xml

146
pos_order_to_sale_order/README.rst

@ -1,135 +1,21 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
**This file is going to be generated by oca-gen-addon-readme.**
*Manual changes will be overwritten.*
=======================
POS Order To Sale Order
=======================
Please provide content in the ``readme`` directory:
* **DESCRIPTION.rst** (required)
* INSTALL.rst (optional)
* CONFIGURE.rst (optional)
* **USAGE.rst** (optional, highly recommended)
* DEVELOP.rst (optional)
* ROADMAP.rst (optional)
* HISTORY.rst (optional, recommended)
* **CONTRIBUTORS.rst** (optional, highly recommended)
* CREDITS.rst (optional)
This module extends the functionality of point of sale to allow sale orders
creation from the Point of Sale.
Content of this README will also be drawn from the addon manifest,
from keys such as name, authors, maintainers, development_status,
and license.
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
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:: static/description/pos_config_form.png
:width: 800 px
Usage
=====
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/184/8.0
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
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:: static/description/pos_create_picking_confirm.png
:width: 800 px
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/pos/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smash it by providing detailed and welcomed feedback.
Credits
=======
Contributors
------------
* Sylvain Le Gal (https://twitter.com/legalsylvain)
Funders
-------
The development of this module has been financially supported by:
* GRAP, Groupement Régional Alimentaire de Proximité (http://www.grap.coop)
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
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.
To contribute to this module, please visit https://odoo-community.org.
A good, one sentence summary in the manifest is also highly recommended.

4
pos_order_to_sale_order/__openerp__.py

@ -5,13 +5,15 @@
{ {
'name': 'PoS Order To Sale Order', 'name': 'PoS Order To Sale Order',
'version': '8.0.2.0.0',
'version': '8.0.3.0.0',
'author': 'GRAP,Odoo Community Association (OCA)', 'author': 'GRAP,Odoo Community Association (OCA)',
'category': 'Point Of Sale', 'category': 'Point Of Sale',
'license': 'AGPL-3', 'license': 'AGPL-3',
'depends': [ 'depends': [
'point_of_sale', 'point_of_sale',
], ],
'maintainers': ['legalsylvain'],
'development_status': "Production/Stable",
'website': 'https://odoo-community.org/', 'website': 'https://odoo-community.org/',
'data': [ 'data': [
'views/view_pos_config.xml', 'views/view_pos_config.xml',

19
pos_order_to_sale_order/demo/product_template.xml

@ -1,19 +0,0 @@
<openerp><data>
<record id="product.product_product_4_product_template" model="product.template">
<field name="available_in_pos" eval="True"/>
</record>
<record id="product.product_product_7_product_template" model="product.template">
<field name="available_in_pos" eval="True"/>
</record>
<record id="product.product_product_9_product_template" model="product.template">
<field name="available_in_pos" eval="True"/>
</record>
<record id="product.product_product_24_product_template" model="product.template">
<field name="available_in_pos" eval="True"/>
</record>
</data></openerp>

51
pos_order_to_sale_order/demo/sale_order.xml

@ -1,51 +0,0 @@
<openerp><data>
<!-- Sale Order 1-->
<record id="sale_order_1" model="sale.order">
<field name="partner_id" ref="base.res_partner_2"/>
<field name="order_policy">picking</field>
</record>
<record id="sale_order_line_11" model="sale.order.line">
<field name="order_id" ref="sale_order_1"/>
<field name="product_id" ref="product.product_product_7"/>
<field name="product_uom_qty">5</field>
</record>
<record id="sale_order_line_12" model="sale.order.line">
<field name="order_id" ref="sale_order_1"/>
<field name="product_id" ref="product.product_product_9"/>
<field name="product_uom_qty">3</field>
</record>
<workflow action="order_confirm" model="sale.order" ref="sale_order_1"/>
<!-- Sale Order 2-->
<record id="sale_order_2" model="sale.order">
<field name="partner_id" ref="base.res_partner_13"/>
<field name="order_policy">picking</field>
</record>
<record id="sale_order_line_21" model="sale.order.line">
<field name="order_id" ref="sale_order_2"/>
<field name="product_id" ref="product.product_product_7"/>
<field name="product_uom_qty">15</field>
<field name="discount">10</field>
</record>
<record id="sale_order_line_22" model="sale.order.line">
<field name="order_id" ref="sale_order_2"/>
<field name="product_id" ref="product.product_product_4"/>
<field name="product_uom_qty">1</field>
</record>
<record id="sale_order_line_23" model="sale.order.line">
<field name="order_id" ref="sale_order_2"/>
<field name="product_id" ref="product.product_product_24"/>
<field name="product_uom_qty">3</field>
<field name="price_unit">555</field>
</record>
<workflow action="order_confirm" model="sale.order" ref="sale_order_2"/>
</data></openerp>

7
pos_order_to_sale_order/demo/stock_picking_type.xml

@ -1,7 +0,0 @@
<openerp><data>
<record id="stock.picking_type_out" model="stock.picking.type">
<field name="available_in_pos" eval="True" />
</record>
</data></openerp>

8
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

1
pos_order_to_sale_order/readme/CONTRIBUTORS.rst

@ -0,0 +1 @@
* Sylvain LE GAL (https://www.twitter.com/legalsylvain)

52
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

11
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

12
pos_order_to_sale_order/static/src/css/pos_order_to_sale_order.css

@ -0,0 +1,12 @@
/*
Copyright (C) 2018 - Today: GRAP (http://www.grap.coop)
@author: Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
*/
/*Redefine here, the style defined in web module (base.css), because this file
is not loaded in the point of sale.*/
.blockUI.blockOverlay {
background-color: black;
opacity: 0.6;
}

200
pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js

@ -1,157 +1,207 @@
/******************************************************************************
/* ***************************************************************************
Copyright (C) 2017 - Today: GRAP (http://www.grap.coop) Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
@author: Sylvain LE GAL (https://twitter.com/legalsylvain) @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
*****************************************************************************/
**************************************************************************** */
openerp.pos_order_to_sale_order = function(instance, local) {
openerp.pos_order_to_sale_order = function (instance) {
"use strict"; "use strict";
var module = instance.point_of_sale; var module = instance.point_of_sale;
var _t = instance.web._t; var _t = instance.web._t;
/*************************************************************************
New Widget CreateSaleOrderButtonWidget:
* On click, check if there is a customer defined,
ask confirmation call server to create sale order, and delete
the current order.
*/
/* ************************************************************************
New Widget CreateSaleOrderButtonWidget
************************************************************************ */
module.CreateSaleOrderButtonWidget = module.PosBaseWidget.extend({ module.CreateSaleOrderButtonWidget = module.PosBaseWidget.extend({
template: 'CreateSaleOrderButtonWidget', template: 'CreateSaleOrderButtonWidget',
init: function(parent, options){
/**
* Define all the confirmation messages.
*/
init: function (parent, options) {
this._super(parent, options); this._super(parent, options);
this.sale_order_state = options.sale_order_state; this.sale_order_state = options.sale_order_state;
if (this.sale_order_state == 'draft') {
if (this.sale_order_state === 'draft') {
this.display_text = _t("Create Draft Order"); 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.");
console.log(this);
}
else if (options.sale_order_state == 'confirmed') {
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.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.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.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.");
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.")
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.")
} }
}, },
renderElement: function() {
/**
* 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; var self = this;
this._super(); this._super();
this.$el.click(function(){
this.$el.click(function () {
var current_order = self.pos.get('selectedOrder'); var current_order = self.pos.get('selectedOrder');
// Prevent empty delivery order // Prevent empty delivery order
if (current_order.get('orderLines').length == 0){
self.pos_widget.screen_selector.show_popup('error',{
if (current_order.get('orderLines').length === 0) {
self.pos_widget.screen_selector.show_popup('error', {
'message': _t('Empty Order'), 'message': _t('Empty Order'),
'comment': _t('There must be at least one product in your order to create Sale Order.'),
'comment': _t(
'There must be at least one product in your' +
' order to create Sale Order.'),
}); });
return; return;
} }
// Check Customer // Check Customer
if (!current_order.get('client')){
self.pos_widget.screen_selector.show_popup('error',{
if (!current_order.get('client')) {
self.pos_widget.screen_selector.show_popup('error', {
'message': _t('No customer defined'), 'message': _t('No customer defined'),
'comment': _t('You should select a customer in order to create a Sale Order. Please select one by clicking the order tab.'),
'comment': _t(
'You should select a customer in order to create' +
' a Sale Order. Please select one by clicking' +
' the order tab.'),
}); });
return; return;
} }
self.pos.pos_widget.screen_selector.show_popup('confirm', { self.pos.pos_widget.screen_selector.show_popup('confirm', {
message: self.confirmation_message, message: self.confirmation_message,
comment: self.confirmation_comment, comment: self.confirmation_comment,
confirm: function(){
var SaleOrderModel = new instance.web.Model('sale.order');
confirm: function () {
var SaleOrderModel =
new instance.web.Model('sale.order');
current_order.sale_order_state = self.sale_order_state; current_order.sale_order_state = self.sale_order_state;
SaleOrderModel.call('create_order_from_pos', [self.prepare_create_sale_order(current_order)]
instance.web.blockUI();
SaleOrderModel.call('create_order_from_pos', [
self.prepare_create_sale_order(current_order)]
).then(function (result) { ).then(function (result) {
instance.web.unblockUI();
self.hook_create_sale_order_success(result); self.hook_create_sale_order_success(result);
}).fail(function (error, event){
}).fail(function (error, event) {
instance.web.unblockUI();
self.hook_create_sale_order_error(error, event); self.hook_create_sale_order_error(error, event);
}); });
}, },
}); });
}); });
}, },
// Overload This function to send custom sale order data to server
prepare_create_sale_order: function(order) {
/**
* Overloadable function to send custom sale order data to server
*/
prepare_create_sale_order: function (order) {
var res = order.export_as_JSON(); var res = order.export_as_JSON();
res.sale_order_state = this.sale_order_state; res.sale_order_state = this.sale_order_state;
return res; return res;
}, },
// Overload this function to make custom action after Sale order
// Creation success
hook_create_sale_order_success: function(result) {
/**
* Overloadable function to make custom action after Sale order
* Creation succeeded
*/
hook_create_sale_order_success: function (result) {
this.pos.get('selectedOrder').destroy(); this.pos.get('selectedOrder').destroy();
}, },
// Overload this function to make custom action after Sale order
// Creation fail
hook_create_sale_order_error: function(error, event) {
/**
* Overloadable function to make custom action after Sale order
* Creation failed
*/
hook_create_sale_order_error: function (error, event) {
event.preventDefault(); event.preventDefault();
if(error.code === 200 ){
if (error.code === 200) {
// Business Logic Error, not a connection problem // Business Logic Error, not a connection problem
this.pos_widget.screen_selector.show_popup('error-traceback',{
this.pos_widget.screen_selector.show_popup('error-traceback', {
message: error.data.message, message: error.data.message,
comment: error.data.debug, comment: error.data.debug,
}); });
}
else{
// connexion problem
this.pos_widget.screen_selector.show_popup('error',{
} else {
// Connexion problem
this.pos_widget.screen_selector.show_popup('error', {
message: _t('The order could not be sent'), message: _t('The order could not be sent'),
comment: _t('Check your internet connection and try again.'),
comment: _t(
'Check your internet connection and try again.'),
}); });
} }
}, },
}); });
/*************************************************************************
/* ************************************************************************
Extend PosWidget: Extend PosWidget:
* Create new buttons, depending of the configuration
*/
************************************************************************ */
module.PosWidget = module.PosWidget.extend({ module.PosWidget = module.PosWidget.extend({
build_widgets: function() {
/**
* Overload build_widgets(), to create new buttons, depending of the
* configuration
*/
build_widgets: function () {
this._super(); this._super();
if (this.pos.config.iface_create_draft_sale_order){
this.create_draft_sale_order_button = new module.CreateSaleOrderButtonWidget(
this, {'sale_order_state': 'draft'});
this.create_draft_sale_order_button.appendTo(this.pos_widget.$('ul.orderlines'));
if (this.pos.config.iface_create_draft_sale_order) {
this.create_draft_sale_order_button =
new module.CreateSaleOrderButtonWidget(
this, {'sale_order_state': 'draft'});
this.create_draft_sale_order_button.appendTo(
this.pos_widget.$('ul.orderlines'));
} }
if (this.pos.config.iface_create_confirmed_sale_order){
this.create_confirmed_sale_order_button = new module.CreateSaleOrderButtonWidget(
this, {'sale_order_state': 'confirmed'});
this.create_confirmed_sale_order_button.appendTo(this.pos_widget.$('ul.orderlines'));
if (this.pos.config.iface_create_confirmed_sale_order) {
this.create_confirmed_sale_order_button =
new module.CreateSaleOrderButtonWidget(
this, {'sale_order_state': 'confirmed'});
this.create_confirmed_sale_order_button.appendTo(
this.pos_widget.$('ul.orderlines'));
} }
if (this.pos.config.iface_create_delivered_sale_order){
this.create_delivered_sale_order_button = new module.CreateSaleOrderButtonWidget(
this, {'sale_order_state': 'delivered'});
this.create_delivered_sale_order_button.appendTo(this.pos_widget.$('ul.orderlines'));
if (this.pos.config.iface_create_delivered_sale_order) {
this.create_delivered_sale_order_button =
new module.CreateSaleOrderButtonWidget(
this, {'sale_order_state': 'delivered'});
this.create_delivered_sale_order_button.appendTo(
this.pos_widget.$('ul.orderlines'));
} }
}, },
}); });
/*************************************************************************
Extend OrderWidget:
Overload renderElement, to display buttons when the order change.
*/
/* ************************************************************************
Extend OrderWidget
************************************************************************ */
module.OrderWidget = module.OrderWidget.extend({ module.OrderWidget = module.OrderWidget.extend({
renderElement: function(scrollbottom){
/**
* Overload renderElement(), to display buttons when the order change.
*/
renderElement: function (scrollbottom) {
this._super(scrollbottom); this._super(scrollbottom);
if (this.pos_widget.create_draft_sale_order_button) { if (this.pos_widget.create_draft_sale_order_button) {
this.pos_widget.create_draft_sale_order_button.appendTo( this.pos_widget.create_draft_sale_order_button.appendTo(
@ -169,7 +219,7 @@ openerp.pos_order_to_sale_order = function(instance, local) {
); );
} }
}
},
}); });
}; };

5
pos_order_to_sale_order/views/pos_order_to_sale_order.xml

@ -2,10 +2,13 @@
<openerp><data> <openerp><data>
<template id="assets_backend" name="pos_order_to_sale_order assets" inherit_id="web.assets_backend">
<template id="assets_backend" name="pos_order_to_sale_order assets" inherit_id="point_of_sale.index">
<xpath expr="." position="inside"> <xpath expr="." position="inside">
<script type="text/javascript" src="/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js"></script> <script type="text/javascript" src="/pos_order_to_sale_order/static/src/js/pos_order_to_sale_order.js"></script>
</xpath> </xpath>
<xpath expr="//link[@id='pos-stylesheet']" position="after">
<link rel="stylesheet" href="/pos_order_to_sale_order/static/src/css/pos_order_to_sale_order.css"/>
</xpath>
</template> </template>
</data></openerp> </data></openerp>
Loading…
Cancel
Save