[MIG][10.0] pos_picking_load.
[PORT] Apply new 10.0 syntax ; [REF] Apply recent OCA convention (new readme, etc.) ; [PORT] make the module depend on 'sale' module [REF] remove obsolete file [IMP] point of sale UI [ADD] prepare function for picking_linepull/365/head
-
176pos_picking_load/README.rst
-
2pos_picking_load/__init__.py
-
11pos_picking_load/__manifest__.py
-
12pos_picking_load/demo/product_template.xml
-
12pos_picking_load/demo/sale_order.xml
-
181pos_picking_load/i18n/fr.po
-
2pos_picking_load/models/__init__.py
-
4pos_picking_load/models/pos_config.py
-
4pos_picking_load/models/pos_order.py
-
4pos_picking_load/models/sale_order.py
-
54pos_picking_load/models/stock_picking.py
-
4pos_picking_load/models/stock_picking_type.py
-
41pos_picking_load/readme/CONFIGURE.rst
-
2pos_picking_load/readme/CONTRIBUTORS.rst
-
16pos_picking_load/readme/DESCRIPTION.rst
-
8pos_picking_load/readme/ROADMAP.rst
-
69pos_picking_load/readme/USAGE.rst
-
562pos_picking_load/static/description/index.html
-
BINpos_picking_load/static/description/load_picking_01.png
-
BINpos_picking_load/static/description/load_picking_01_load_button.png
-
BINpos_picking_load/static/description/load_picking_02.png
-
BINpos_picking_load/static/description/load_picking_02_picking_list.png
-
BINpos_picking_load/static/description/load_picking_03.png
-
BINpos_picking_load/static/description/load_picking_03_confirm.png
-
BINpos_picking_load/static/description/load_picking_04.png
-
BINpos_picking_load/static/description/load_picking_04_pos_order.png
-
BINpos_picking_load/static/description/load_picking_05.png
-
BINpos_picking_load/static/description/load_picking_06.png
-
BINpos_picking_load/static/description/load_picking_sale_order.png
-
BINpos_picking_load/static/description/load_picking_stock_picking.png
-
BINpos_picking_load/static/description/load_picking_warning_partner.png
-
BINpos_picking_load/static/description/load_picking_warning_picking_still_loaded.png
-
BINpos_picking_load/static/description/load_picking_warning_product.png
-
BINpos_picking_load/static/description/pos_config_form.png
-
30pos_picking_load/static/src/css/pos_picking_load.css
-
77pos_picking_load/static/src/js/model.js
-
329pos_picking_load/static/src/js/pos_picking_load.js
-
289pos_picking_load/static/src/js/widget.js
-
23pos_picking_load/static/src/xml/pos_picking_load.xml
-
17pos_picking_load/tests/test_pos_picking_load.py
-
32pos_picking_load/views/pos_picking_load.xml
-
5pos_picking_load/views/view_pos_config.xml
-
9pos_picking_load/views/view_sale_order.xml
-
11pos_picking_load/views/view_stock_picking.xml
-
5pos_picking_load/views/view_stock_picking_type.xml
@ -1,2 +1,2 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
|
# coding: utf-8 |
||||
from . import models |
from . import models |
@ -0,0 +1,41 @@ |
|||||
|
To configure this module, you need to: |
||||
|
|
||||
|
#. Go to Warehouse / Configuration / Types of Operation |
||||
|
#. Select the picking type(s) you want to see in the point of sale |
||||
|
#. Check the box 'Available in Point of Sale' |
||||
|
|
||||
|
.. figure:: ../static/description/stock_picking_type_form.png |
||||
|
:width: 800 px |
||||
|
|
||||
|
Note: This box is NOT enabled by default except in demo data for the type |
||||
|
'Delivery Orders' of the demo company 'YourCompany'. |
||||
|
|
||||
|
#. Go to Point of Sale / Configuration / Point of Sales |
||||
|
#. Select the Point(s) of Sales witch those you want to enable the feature |
||||
|
#. Check the box 'Load Pickings' |
||||
|
#. Set the max quantity of pickings you want to load |
||||
|
|
||||
|
.. figure:: ../static/description/pos_config_form.png |
||||
|
:width: 800 px |
||||
|
|
||||
|
Note: This box is enabled by default |
||||
|
|
||||
|
**Technical Notes** |
||||
|
|
||||
|
* By default, the Point of Sale will display only the pickings if the state is |
||||
|
in 'Waiting Availability', 'Partially Available' or 'Ready to Transfer'. |
||||
|
|
||||
|
You can change this filter by overloading the ``_prepare_filter_for_pos`` |
||||
|
function of the model ``stock.picking``. |
||||
|
|
||||
|
* By default, the search of pickings will be done on the fields ``name``, |
||||
|
``origin`` and ``partner_id`` of the picking. |
||||
|
|
||||
|
You can change this feature by overloading the |
||||
|
``_prepare_filter_query_for_pos`` function of the model ``stock.picking``. |
||||
|
|
||||
|
* By default, when the PoS order is confirmed, the original picking is |
||||
|
cancelled and the sale order is set to the state 'Done'. |
||||
|
|
||||
|
You can change this behaviour by overloading |
||||
|
``_handle_orders_with_original_picking`` function of the model ``pos.order``. |
@ -0,0 +1,2 @@ |
|||||
|
* Sylvain LE GAL (https://twitter.com/legalsylvain) |
||||
|
* Stefan Rijnhart <stefan@opener.am> |
@ -0,0 +1,16 @@ |
|||||
|
This module extends the functionality of point of sale to allow you to |
||||
|
load your pickings in the Point of Sale, in order to add / remove products |
||||
|
and so create a PoS Order and mark it as paid. |
||||
|
|
||||
|
**Detailled Use Case** |
||||
|
|
||||
|
This module is usefull for the following use case |
||||
|
|
||||
|
* You have many Sale Orders that have generated pickings. Typically if you have |
||||
|
connected your Odoo instance to an online store like Shop Invader, |
||||
|
Prestashop, Magento, or if you use light Odoo shop (``website_sale`` |
||||
|
module). |
||||
|
* Once the order validated, you prepare your pickings |
||||
|
* The customer come in your shop to recover his order |
||||
|
* the customer add (or remove) some products |
||||
|
* the customer pay his order, based on the real delivered products list |
@ -0,0 +1,8 @@ |
|||||
|
* This module will try to get original unit price from the sale order and not |
||||
|
use the Current unit price of the product. |
||||
|
(The price at which you pledged to sell the product). |
||||
|
Some VAT troubles will occure if a product is set with VAT marked as |
||||
|
'VAT included' and if in the sale order line, there are some VAT marked as |
||||
|
'VAT excluded' for exemple. |
||||
|
|
||||
|
**The VAT settings should be consistent.** |
@ -0,0 +1,69 @@ |
|||||
|
To use this module, you need to: |
||||
|
|
||||
|
* Launch the point of sale |
||||
|
* On a new order (without lines), click on the 'Load Picking' button. |
||||
|
|
||||
|
.. figure:: ../static/description/load_picking_01_load_button.png |
||||
|
:width: 800 px |
||||
|
|
||||
|
* Point of sale will load available pickings. (About displayed pickings, see |
||||
|
'Technical Notes' section). |
||||
|
|
||||
|
.. figure:: ../static/description/load_picking_02_picking_list.png |
||||
|
:width: 800 px |
||||
|
|
||||
|
* Click on a picking will check if the picking is loadable and if yes, will |
||||
|
display a 'Select' button. (See 'Possible Warnings' Section) |
||||
|
|
||||
|
.. figure:: ../static/description/load_picking_03_confirm.png |
||||
|
:width: 800 px |
||||
|
|
||||
|
* Confirm the selection, by clicking on 'Select' button. It will display |
||||
|
the content of the moves (as PoS Order Lines) |
||||
|
|
||||
|
.. figure:: ../static/description/load_picking_04_pos_order.png |
||||
|
:width: 800 px |
||||
|
|
||||
|
The price and the discount will be the sale price and the discount set in |
||||
|
the according Sale Order Line, if it was found. Otherwise, discount will be |
||||
|
set to 0, and unit price will be the unit price of the product when it has been |
||||
|
loaded in the Point of Sale. |
||||
|
|
||||
|
**Related Sale Order:** |
||||
|
|
||||
|
.. figure:: ../static/description/load_picking_sale_order.png |
||||
|
:width: 800 px |
||||
|
|
||||
|
**Related Picking:** |
||||
|
|
||||
|
.. figure:: ../static/description/load_picking_stock_picking.png |
||||
|
:width: 800 px |
||||
|
|
||||
|
|
||||
|
* Finally, you can add / remove products or change quantity and collect the |
||||
|
payment. |
||||
|
|
||||
|
When, the order is marked as paid, the original picking will be cancelled, |
||||
|
because Point Of Sale generates a new picking related to the real delivered |
||||
|
products and the original Sale Order will pass to the state 'Done'. (Delivery |
||||
|
exception is ignored). |
||||
|
(See 'Technical Notes' section). |
||||
|
|
||||
|
**Possible Warnings** |
||||
|
|
||||
|
Some warning messages can appear: |
||||
|
|
||||
|
* if some products are not available in the Point of Sale |
||||
|
|
||||
|
.. figure:: ../static/description/load_picking_warning_product.png |
||||
|
:width: 800 px |
||||
|
|
||||
|
* if the partner is not available in the Point of Sale |
||||
|
|
||||
|
.. figure:: ../static/description/load_picking_warning_partner.png |
||||
|
:width: 800 px |
||||
|
|
||||
|
* if the picking has been still loaded in another PoS order |
||||
|
|
||||
|
.. figure:: ../static/description/load_picking_warning_picking_still_loaded.png |
||||
|
:width: 800 px |
@ -0,0 +1,562 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8" ?> |
||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> |
||||
|
<head> |
||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
||||
|
<meta name="generator" content="Docutils 0.14: http://docutils.sourceforge.net/" /> |
||||
|
<title>Point Of Sale - Picking Load</title> |
||||
|
<style type="text/css"> |
||||
|
|
||||
|
/* |
||||
|
:Author: David Goodger (goodger@python.org) |
||||
|
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $ |
||||
|
:Copyright: This stylesheet has been placed in the public domain. |
||||
|
|
||||
|
Default cascading style sheet for the HTML output of Docutils. |
||||
|
|
||||
|
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to |
||||
|
customize this style sheet. |
||||
|
*/ |
||||
|
|
||||
|
/* used to remove borders from tables and images */ |
||||
|
.borderless, table.borderless td, table.borderless th { |
||||
|
border: 0 } |
||||
|
|
||||
|
table.borderless td, table.borderless th { |
||||
|
/* Override padding for "table.docutils td" with "! important". |
||||
|
The right padding separates the table cells. */ |
||||
|
padding: 0 0.5em 0 0 ! important } |
||||
|
|
||||
|
.first { |
||||
|
/* Override more specific margin styles with "! important". */ |
||||
|
margin-top: 0 ! important } |
||||
|
|
||||
|
.last, .with-subtitle { |
||||
|
margin-bottom: 0 ! important } |
||||
|
|
||||
|
.hidden { |
||||
|
display: none } |
||||
|
|
||||
|
.subscript { |
||||
|
vertical-align: sub; |
||||
|
font-size: smaller } |
||||
|
|
||||
|
.superscript { |
||||
|
vertical-align: super; |
||||
|
font-size: smaller } |
||||
|
|
||||
|
a.toc-backref { |
||||
|
text-decoration: none ; |
||||
|
color: black } |
||||
|
|
||||
|
blockquote.epigraph { |
||||
|
margin: 2em 5em ; } |
||||
|
|
||||
|
dl.docutils dd { |
||||
|
margin-bottom: 0.5em } |
||||
|
|
||||
|
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
/* Uncomment (and remove this text!) to get bold-faced definition list terms |
||||
|
dl.docutils dt { |
||||
|
font-weight: bold } |
||||
|
*/ |
||||
|
|
||||
|
div.abstract { |
||||
|
margin: 2em 5em } |
||||
|
|
||||
|
div.abstract p.topic-title { |
||||
|
font-weight: bold ; |
||||
|
text-align: center } |
||||
|
|
||||
|
div.admonition, div.attention, div.caution, div.danger, div.error, |
||||
|
div.hint, div.important, div.note, div.tip, div.warning { |
||||
|
margin: 2em ; |
||||
|
border: medium outset ; |
||||
|
padding: 1em } |
||||
|
|
||||
|
div.admonition p.admonition-title, div.hint p.admonition-title, |
||||
|
div.important p.admonition-title, div.note p.admonition-title, |
||||
|
div.tip p.admonition-title { |
||||
|
font-weight: bold ; |
||||
|
font-family: sans-serif } |
||||
|
|
||||
|
div.attention p.admonition-title, div.caution p.admonition-title, |
||||
|
div.danger p.admonition-title, div.error p.admonition-title, |
||||
|
div.warning p.admonition-title, .code .error { |
||||
|
color: red ; |
||||
|
font-weight: bold ; |
||||
|
font-family: sans-serif } |
||||
|
|
||||
|
/* Uncomment (and remove this text!) to get reduced vertical space in |
||||
|
compound paragraphs. |
||||
|
div.compound .compound-first, div.compound .compound-middle { |
||||
|
margin-bottom: 0.5em } |
||||
|
|
||||
|
div.compound .compound-last, div.compound .compound-middle { |
||||
|
margin-top: 0.5em } |
||||
|
*/ |
||||
|
|
||||
|
div.dedication { |
||||
|
margin: 2em 5em ; |
||||
|
text-align: center ; |
||||
|
font-style: italic } |
||||
|
|
||||
|
div.dedication p.topic-title { |
||||
|
font-weight: bold ; |
||||
|
font-style: normal } |
||||
|
|
||||
|
div.figure { |
||||
|
margin-left: 2em ; |
||||
|
margin-right: 2em } |
||||
|
|
||||
|
div.footer, div.header { |
||||
|
clear: both; |
||||
|
font-size: smaller } |
||||
|
|
||||
|
div.line-block { |
||||
|
display: block ; |
||||
|
margin-top: 1em ; |
||||
|
margin-bottom: 1em } |
||||
|
|
||||
|
div.line-block div.line-block { |
||||
|
margin-top: 0 ; |
||||
|
margin-bottom: 0 ; |
||||
|
margin-left: 1.5em } |
||||
|
|
||||
|
div.sidebar { |
||||
|
margin: 0 0 0.5em 1em ; |
||||
|
border: medium outset ; |
||||
|
padding: 1em ; |
||||
|
background-color: #ffffee ; |
||||
|
width: 40% ; |
||||
|
float: right ; |
||||
|
clear: right } |
||||
|
|
||||
|
div.sidebar p.rubric { |
||||
|
font-family: sans-serif ; |
||||
|
font-size: medium } |
||||
|
|
||||
|
div.system-messages { |
||||
|
margin: 5em } |
||||
|
|
||||
|
div.system-messages h1 { |
||||
|
color: red } |
||||
|
|
||||
|
div.system-message { |
||||
|
border: medium outset ; |
||||
|
padding: 1em } |
||||
|
|
||||
|
div.system-message p.system-message-title { |
||||
|
color: red ; |
||||
|
font-weight: bold } |
||||
|
|
||||
|
div.topic { |
||||
|
margin: 2em } |
||||
|
|
||||
|
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, |
||||
|
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { |
||||
|
margin-top: 0.4em } |
||||
|
|
||||
|
h1.title { |
||||
|
text-align: center } |
||||
|
|
||||
|
h2.subtitle { |
||||
|
text-align: center } |
||||
|
|
||||
|
hr.docutils { |
||||
|
width: 75% } |
||||
|
|
||||
|
img.align-left, .figure.align-left, object.align-left, table.align-left { |
||||
|
clear: left ; |
||||
|
float: left ; |
||||
|
margin-right: 1em } |
||||
|
|
||||
|
img.align-right, .figure.align-right, object.align-right, table.align-right { |
||||
|
clear: right ; |
||||
|
float: right ; |
||||
|
margin-left: 1em } |
||||
|
|
||||
|
img.align-center, .figure.align-center, object.align-center { |
||||
|
display: block; |
||||
|
margin-left: auto; |
||||
|
margin-right: auto; |
||||
|
} |
||||
|
|
||||
|
table.align-center { |
||||
|
margin-left: auto; |
||||
|
margin-right: auto; |
||||
|
} |
||||
|
|
||||
|
.align-left { |
||||
|
text-align: left } |
||||
|
|
||||
|
.align-center { |
||||
|
clear: both ; |
||||
|
text-align: center } |
||||
|
|
||||
|
.align-right { |
||||
|
text-align: right } |
||||
|
|
||||
|
/* reset inner alignment in figures */ |
||||
|
div.align-right { |
||||
|
text-align: inherit } |
||||
|
|
||||
|
/* div.align-center * { */ |
||||
|
/* text-align: left } */ |
||||
|
|
||||
|
.align-top { |
||||
|
vertical-align: top } |
||||
|
|
||||
|
.align-middle { |
||||
|
vertical-align: middle } |
||||
|
|
||||
|
.align-bottom { |
||||
|
vertical-align: bottom } |
||||
|
|
||||
|
ol.simple, ul.simple { |
||||
|
margin-bottom: 1em } |
||||
|
|
||||
|
ol.arabic { |
||||
|
list-style: decimal } |
||||
|
|
||||
|
ol.loweralpha { |
||||
|
list-style: lower-alpha } |
||||
|
|
||||
|
ol.upperalpha { |
||||
|
list-style: upper-alpha } |
||||
|
|
||||
|
ol.lowerroman { |
||||
|
list-style: lower-roman } |
||||
|
|
||||
|
ol.upperroman { |
||||
|
list-style: upper-roman } |
||||
|
|
||||
|
p.attribution { |
||||
|
text-align: right ; |
||||
|
margin-left: 50% } |
||||
|
|
||||
|
p.caption { |
||||
|
font-style: italic } |
||||
|
|
||||
|
p.credits { |
||||
|
font-style: italic ; |
||||
|
font-size: smaller } |
||||
|
|
||||
|
p.label { |
||||
|
white-space: nowrap } |
||||
|
|
||||
|
p.rubric { |
||||
|
font-weight: bold ; |
||||
|
font-size: larger ; |
||||
|
color: maroon ; |
||||
|
text-align: center } |
||||
|
|
||||
|
p.sidebar-title { |
||||
|
font-family: sans-serif ; |
||||
|
font-weight: bold ; |
||||
|
font-size: larger } |
||||
|
|
||||
|
p.sidebar-subtitle { |
||||
|
font-family: sans-serif ; |
||||
|
font-weight: bold } |
||||
|
|
||||
|
p.topic-title { |
||||
|
font-weight: bold } |
||||
|
|
||||
|
pre.address { |
||||
|
margin-bottom: 0 ; |
||||
|
margin-top: 0 ; |
||||
|
font: inherit } |
||||
|
|
||||
|
pre.literal-block, pre.doctest-block, pre.math, pre.code { |
||||
|
margin-left: 2em ; |
||||
|
margin-right: 2em } |
||||
|
|
||||
|
pre.code .ln { color: grey; } /* line numbers */ |
||||
|
pre.code, code { background-color: #eeeeee } |
||||
|
pre.code .comment, code .comment { color: #5C6576 } |
||||
|
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } |
||||
|
pre.code .literal.string, code .literal.string { color: #0C5404 } |
||||
|
pre.code .name.builtin, code .name.builtin { color: #352B84 } |
||||
|
pre.code .deleted, code .deleted { background-color: #DEB0A1} |
||||
|
pre.code .inserted, code .inserted { background-color: #A3D289} |
||||
|
|
||||
|
span.classifier { |
||||
|
font-family: sans-serif ; |
||||
|
font-style: oblique } |
||||
|
|
||||
|
span.classifier-delimiter { |
||||
|
font-family: sans-serif ; |
||||
|
font-weight: bold } |
||||
|
|
||||
|
span.interpreted { |
||||
|
font-family: sans-serif } |
||||
|
|
||||
|
span.option { |
||||
|
white-space: nowrap } |
||||
|
|
||||
|
span.pre { |
||||
|
white-space: pre } |
||||
|
|
||||
|
span.problematic { |
||||
|
color: red } |
||||
|
|
||||
|
span.section-subtitle { |
||||
|
/* font-size relative to parent (h1..h6 element) */ |
||||
|
font-size: 80% } |
||||
|
|
||||
|
table.citation { |
||||
|
border-left: solid 1px gray; |
||||
|
margin-left: 1px } |
||||
|
|
||||
|
table.docinfo { |
||||
|
margin: 2em 4em } |
||||
|
|
||||
|
table.docutils { |
||||
|
margin-top: 0.5em ; |
||||
|
margin-bottom: 0.5em } |
||||
|
|
||||
|
table.footnote { |
||||
|
border-left: solid 1px black; |
||||
|
margin-left: 1px } |
||||
|
|
||||
|
table.docutils td, table.docutils th, |
||||
|
table.docinfo td, table.docinfo th { |
||||
|
padding-left: 0.5em ; |
||||
|
padding-right: 0.5em ; |
||||
|
vertical-align: top } |
||||
|
|
||||
|
table.docutils th.field-name, table.docinfo th.docinfo-name { |
||||
|
font-weight: bold ; |
||||
|
text-align: left ; |
||||
|
white-space: nowrap ; |
||||
|
padding-left: 0 } |
||||
|
|
||||
|
/* "booktabs" style (no vertical lines) */ |
||||
|
table.docutils.booktabs { |
||||
|
border: 0px; |
||||
|
border-top: 2px solid; |
||||
|
border-bottom: 2px solid; |
||||
|
border-collapse: collapse; |
||||
|
} |
||||
|
table.docutils.booktabs * { |
||||
|
border: 0px; |
||||
|
} |
||||
|
table.docutils.booktabs th { |
||||
|
border-bottom: thin solid; |
||||
|
text-align: left; |
||||
|
} |
||||
|
|
||||
|
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, |
||||
|
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { |
||||
|
font-size: 100% } |
||||
|
|
||||
|
ul.auto-toc { |
||||
|
list-style-type: none } |
||||
|
|
||||
|
</style> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div class="document" id="point-of-sale-picking-load"> |
||||
|
<h1 class="title">Point Of Sale - Picking Load</h1> |
||||
|
|
||||
|
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
||||
|
!! This file is generated by oca-gen-addon-readme !! |
||||
|
!! changes will be overwritten. !! |
||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> |
||||
|
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/legalsylvain/pos/tree/10.0-mig-pos_picking_load/pos_picking_load"><img alt="legalsylvain/pos" src="https://img.shields.io/badge/github-legalsylvain%2Fpos-lightgray.png?logo=github" /></a></p> |
||||
|
<p>This module extends the functionality of point of sale to allow you to |
||||
|
load your pickings in the Point of Sale, in order to add / remove products |
||||
|
and so create a PoS Order and mark it as paid.</p> |
||||
|
<p><strong>Detailled Use Case</strong></p> |
||||
|
<p>This module is usefull for the following use case</p> |
||||
|
<ul class="simple"> |
||||
|
<li>You have many Sale Orders that have generated pickings. Typically if you have |
||||
|
connected your Odoo instance to an online store like Shop Invader, |
||||
|
Prestashop, Magento, or if you use light Odoo shop (<tt class="docutils literal">website_sale</tt> |
||||
|
module).</li> |
||||
|
<li>Once the order validated, you prepare your pickings</li> |
||||
|
<li>The customer come in your shop to recover his order</li> |
||||
|
<li>the customer add (or remove) some products</li> |
||||
|
<li>the customer pay his order, based on the real delivered products list</li> |
||||
|
</ul> |
||||
|
<p><strong>Table of contents</strong></p> |
||||
|
<div class="contents local topic" id="contents"> |
||||
|
<ul class="simple"> |
||||
|
<li><a class="reference internal" href="#configuration" id="id1">Configuration</a></li> |
||||
|
<li><a class="reference internal" href="#usage" id="id2">Usage</a></li> |
||||
|
<li><a class="reference internal" href="#known-issues-roadmap" id="id3">Known issues / Roadmap</a></li> |
||||
|
<li><a class="reference internal" href="#bug-tracker" id="id4">Bug Tracker</a></li> |
||||
|
<li><a class="reference internal" href="#credits" id="id5">Credits</a><ul> |
||||
|
<li><a class="reference internal" href="#authors" id="id6">Authors</a></li> |
||||
|
<li><a class="reference internal" href="#contributors" id="id7">Contributors</a></li> |
||||
|
<li><a class="reference internal" href="#maintainers" id="id8">Maintainers</a></li> |
||||
|
</ul> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
<div class="section" id="configuration"> |
||||
|
<h1><a class="toc-backref" href="#id1">Configuration</a></h1> |
||||
|
<p>To configure this module, you need to:</p> |
||||
|
<ol class="arabic simple"> |
||||
|
<li>Go to Warehouse / Configuration / Types of Operation</li> |
||||
|
<li>Select the picking type(s) you want to see in the point of sale</li> |
||||
|
<li>Check the box ‘Available in Point of Sale’</li> |
||||
|
</ol> |
||||
|
<div class="figure"> |
||||
|
<img alt="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/stock_picking_type_form.png" src="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/stock_picking_type_form.png" style="width: 800px;" /> |
||||
|
</div> |
||||
|
<p>Note: This box is NOT enabled by default except in demo data for the type |
||||
|
‘Delivery Orders’ of the demo company ‘YourCompany’.</p> |
||||
|
<ol class="arabic simple"> |
||||
|
<li>Go to Point of Sale / Configuration / Point of Sales</li> |
||||
|
<li>Select the Point(s) of Sales witch those you want to enable the feature</li> |
||||
|
<li>Check the box ‘Load Pickings’</li> |
||||
|
<li>Set the max quantity of pickings you want to load</li> |
||||
|
</ol> |
||||
|
<div class="figure"> |
||||
|
<img alt="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/pos_config_form.png" src="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/pos_config_form.png" style="width: 800px;" /> |
||||
|
</div> |
||||
|
<p>Note: This box is enabled by default</p> |
||||
|
<p><strong>Technical Notes</strong></p> |
||||
|
<ul class="simple"> |
||||
|
<li>By default, the Point of Sale will display only the pickings if the state is |
||||
|
in ‘Waiting Availability’, ‘Partially Available’ or ‘Ready to Transfer’.</li> |
||||
|
</ul> |
||||
|
<p>You can change this filter by overloading the <tt class="docutils literal">_prepare_filter_for_pos</tt> |
||||
|
function of the model <tt class="docutils literal">stock.picking</tt>.</p> |
||||
|
<ul class="simple"> |
||||
|
<li>By default, the search of pickings will be done on the fields <tt class="docutils literal">name</tt>, |
||||
|
<tt class="docutils literal">origin</tt> and <tt class="docutils literal">partner_id</tt> of the picking.</li> |
||||
|
</ul> |
||||
|
<p>You can change this feature by overloading the |
||||
|
<tt class="docutils literal">_prepare_filter_query_for_pos</tt> function of the model <tt class="docutils literal">stock.picking</tt>.</p> |
||||
|
<ul class="simple"> |
||||
|
<li>By default, when the PoS order is confirmed, the original picking is |
||||
|
cancelled and the sale order is set to the state ‘Done’.</li> |
||||
|
</ul> |
||||
|
<p>You can change this behaviour by overloading |
||||
|
<tt class="docutils literal">_handle_orders_with_original_picking</tt> function of the model <tt class="docutils literal">pos.order</tt>.</p> |
||||
|
</div> |
||||
|
<div class="section" id="usage"> |
||||
|
<h1><a class="toc-backref" href="#id2">Usage</a></h1> |
||||
|
<p>To use this module, you need to:</p> |
||||
|
<ul class="simple"> |
||||
|
<li>Launch the point of sale</li> |
||||
|
<li>On a new order (without lines), click on the ‘Load Picking’ button.</li> |
||||
|
</ul> |
||||
|
<div class="figure"> |
||||
|
<img alt="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/load_picking_01_load_button.png" src="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/load_picking_01_load_button.png" style="width: 800px;" /> |
||||
|
</div> |
||||
|
<ul class="simple"> |
||||
|
<li>Point of sale will load available pickings. (About displayed pickings, see |
||||
|
‘Technical Notes’ section).</li> |
||||
|
</ul> |
||||
|
<div class="figure"> |
||||
|
<img alt="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/load_picking_02_picking_list.png" src="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/load_picking_02_picking_list.png" style="width: 800px;" /> |
||||
|
</div> |
||||
|
<ul class="simple"> |
||||
|
<li>Click on a picking will check if the picking is loadable and if yes, will |
||||
|
display a ‘Select’ button. (See ‘Possible Warnings’ Section)</li> |
||||
|
</ul> |
||||
|
<div class="figure"> |
||||
|
<img alt="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/load_picking_03_confirm.png" src="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/load_picking_03_confirm.png" style="width: 800px;" /> |
||||
|
</div> |
||||
|
<ul class="simple"> |
||||
|
<li>Confirm the selection, by clicking on ‘Select’ button. It will display |
||||
|
the content of the moves (as PoS Order Lines)</li> |
||||
|
</ul> |
||||
|
<div class="figure"> |
||||
|
<img alt="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/load_picking_04_pos_order.png" src="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/load_picking_04_pos_order.png" style="width: 800px;" /> |
||||
|
</div> |
||||
|
<p>The price and the discount will be the sale price and the discount set in |
||||
|
the according Sale Order Line, if it was found. Otherwise, discount will be |
||||
|
set to 0, and unit price will be the unit price of the product when it has been |
||||
|
loaded in the Point of Sale.</p> |
||||
|
<p><strong>Related Sale Order:</strong></p> |
||||
|
<div class="figure"> |
||||
|
<img alt="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/load_picking_sale_order.png" src="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/load_picking_sale_order.png" style="width: 800px;" /> |
||||
|
</div> |
||||
|
<p><strong>Related Picking:</strong></p> |
||||
|
<div class="figure"> |
||||
|
<img alt="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/load_picking_stock_picking.png" src="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/load_picking_stock_picking.png" style="width: 800px;" /> |
||||
|
</div> |
||||
|
<ul class="simple"> |
||||
|
<li>Finally, you can add / remove products or change quantity and collect the |
||||
|
payment.</li> |
||||
|
</ul> |
||||
|
<p>When, the order is marked as paid, the original picking will be cancelled, |
||||
|
because Point Of Sale generates a new picking related to the real delivered |
||||
|
products and the original Sale Order will pass to the state ‘Done’. (Delivery |
||||
|
exception is ignored). |
||||
|
(See ‘Technical Notes’ section).</p> |
||||
|
<p><strong>Possible Warnings</strong></p> |
||||
|
<p>Some warning messages can appear:</p> |
||||
|
<ul class="simple"> |
||||
|
<li>if some products are not available in the Point of Sale</li> |
||||
|
</ul> |
||||
|
<div class="figure"> |
||||
|
<img alt="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/load_picking_warning_product.png" src="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/load_picking_warning_product.png" style="width: 800px;" /> |
||||
|
</div> |
||||
|
<ul class="simple"> |
||||
|
<li>if the partner is not available in the Point of Sale</li> |
||||
|
</ul> |
||||
|
<div class="figure"> |
||||
|
<img alt="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/load_picking_warning_partner.png" src="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/load_picking_warning_partner.png" style="width: 800px;" /> |
||||
|
</div> |
||||
|
<ul class="simple"> |
||||
|
<li>if the picking has been still loaded in another PoS order</li> |
||||
|
</ul> |
||||
|
<div class="figure"> |
||||
|
<img alt="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/load_picking_warning_picking_still_loaded.png" src="https://raw.githubusercontent.com/legalsylvain/pos/10.0-mig-pos_picking_load/pos_picking_load/static/description/load_picking_warning_picking_still_loaded.png" style="width: 800px;" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="section" id="known-issues-roadmap"> |
||||
|
<h1><a class="toc-backref" href="#id3">Known issues / Roadmap</a></h1> |
||||
|
<ul class="simple"> |
||||
|
<li>This module will try to get original unit price from the sale order and not |
||||
|
use the Current unit price of the product. |
||||
|
(The price at which you pledged to sell the product). |
||||
|
Some VAT troubles will occure if a product is set with VAT marked as |
||||
|
‘VAT included’ and if in the sale order line, there are some VAT marked as |
||||
|
‘VAT excluded’ for exemple.</li> |
||||
|
</ul> |
||||
|
<p><strong>The VAT settings should be consistent.</strong></p> |
||||
|
</div> |
||||
|
<div class="section" id="bug-tracker"> |
||||
|
<h1><a class="toc-backref" href="#id4">Bug Tracker</a></h1> |
||||
|
<p>Bugs are tracked on <a class="reference external" href="https://github.com/legalsylvain/pos/issues">GitHub Issues</a>. |
||||
|
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 |
||||
|
<a class="reference external" href="https://github.com/legalsylvain/pos/issues/new?body=module:%20pos_picking_load%0Aversion:%2010.0-mig-pos_picking_load%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p> |
||||
|
<p>Do not contact contributors directly about support or help with technical issues.</p> |
||||
|
</div> |
||||
|
<div class="section" id="credits"> |
||||
|
<h1><a class="toc-backref" href="#id5">Credits</a></h1> |
||||
|
<div class="section" id="authors"> |
||||
|
<h2><a class="toc-backref" href="#id6">Authors</a></h2> |
||||
|
<ul class="simple"> |
||||
|
<li>GRAP</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
<div class="section" id="contributors"> |
||||
|
<h2><a class="toc-backref" href="#id7">Contributors</a></h2> |
||||
|
<ul class="simple"> |
||||
|
<li>Sylvain LE GAL (<a class="reference external" href="https://twitter.com/legalsylvain">https://twitter.com/legalsylvain</a>)</li> |
||||
|
<li>Stefan Rijnhart <<a class="reference external" href="mailto:stefan@opener.am">stefan@opener.am</a>></li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
<div class="section" id="maintainers"> |
||||
|
<h2><a class="toc-backref" href="#id8">Maintainers</a></h2> |
||||
|
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p> |
||||
|
<p><a class="reference external" href="https://github.com/legalsylvain"><img alt="legalsylvain" src="https://github.com/legalsylvain.png?size=40px" /></a></p> |
||||
|
<p>This module is part of the <a class="reference external" href="https://github.com/legalsylvain/pos/tree/10.0-mig-pos_picking_load/pos_picking_load">legalsylvain/pos</a> project on GitHub.</p> |
||||
|
<p>You are welcome to contribute.</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
Before Width: 421 | Height: 205 | Size: 8.9 KiB |
After Width: 428 | Height: 494 | Size: 22 KiB |
Before Width: 855 | Height: 222 | Size: 26 KiB |
After Width: 976 | Height: 285 | Size: 33 KiB |
Before Width: 415 | Height: 306 | Size: 26 KiB |
After Width: 1025 | Height: 267 | Size: 35 KiB |
Before Width: 837 | Height: 495 | Size: 43 KiB |
After Width: 430 | Height: 637 | Size: 38 KiB |
Before Width: 839 | Height: 298 | Size: 33 KiB |
Before Width: 511 | Height: 426 | Size: 14 KiB |
After Width: 844 | Height: 384 | Size: 35 KiB |
After Width: 854 | Height: 360 | Size: 26 KiB |
After Width: 492 | Height: 392 | Size: 10 KiB |
After Width: 492 | Height: 394 | Size: 12 KiB |
After Width: 496 | Height: 394 | Size: 19 KiB |
Before Width: 637 | Height: 160 | Size: 12 KiB After Width: 634 | Height: 178 | Size: 17 KiB |
@ -0,0 +1,77 @@ |
|||||
|
/** *************************************************************************** |
||||
|
Copyright (C) 2017 - 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).
|
||||
|
******************************************************************************/ |
||||
|
|
||||
|
odoo.define('pos_picking_load.model', function (require) { |
||||
|
"use strict"; |
||||
|
|
||||
|
var models = require('point_of_sale.models'); |
||||
|
|
||||
|
/** ********************************************************************** |
||||
|
Extend Model Order: |
||||
|
* Add getter and setter function for field 'origin_picking_id'; |
||||
|
*/ |
||||
|
|
||||
|
var moduleOrderParent = models.Order; |
||||
|
models.Order = models.Order.extend({ |
||||
|
|
||||
|
load_from_picking_data: function (picking_data) { |
||||
|
var self = this; |
||||
|
|
||||
|
var partner = this.pos.db.get_partner_by_id( |
||||
|
picking_data.partner_id); |
||||
|
|
||||
|
this.set({ |
||||
|
'origin_picking_id': picking_data.id, |
||||
|
'origin_picking_name': picking_data.name, |
||||
|
}); |
||||
|
this.set_client(partner); |
||||
|
|
||||
|
picking_data.line_ids.forEach(function (picking_line_data) { |
||||
|
// Create new line and add it to the current order
|
||||
|
var product = self.pos.db.get_product_by_id( |
||||
|
picking_line_data.product_id); |
||||
|
var order_line_data = |
||||
|
self.prepare_order_line_from_picking_line_data( |
||||
|
product, picking_line_data); |
||||
|
self.add_product(product, order_line_data); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
prepare_order_line_from_picking_line_data: function ( |
||||
|
product, picking_line_data) { |
||||
|
return { |
||||
|
quantity: picking_line_data.quantity, |
||||
|
price: picking_line_data.price_unit || product.price, |
||||
|
discount: picking_line_data.discount || 0.0, |
||||
|
}; |
||||
|
}, |
||||
|
|
||||
|
export_for_printing: function () { |
||||
|
var order = moduleOrderParent.prototype.export_for_printing.apply( |
||||
|
this, arguments); |
||||
|
order.origin_picking_name = this.get('origin_picking_name'); |
||||
|
return order; |
||||
|
}, |
||||
|
|
||||
|
export_as_JSON: function () { |
||||
|
var order = moduleOrderParent.prototype.export_as_JSON.apply( |
||||
|
this, arguments); |
||||
|
order.origin_picking_id = this.get('origin_picking_id'); |
||||
|
order.origin_picking_name = this.get('origin_picking_name'); |
||||
|
return order; |
||||
|
}, |
||||
|
|
||||
|
init_from_JSON: function (json) { |
||||
|
moduleOrderParent.prototype.init_from_JSON.apply(this, arguments); |
||||
|
this.set({ |
||||
|
'origin_picking_id': json.origin_picking_id, |
||||
|
'origin_picking_name': json.origin_picking_name, |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
}); |
@ -1,329 +0,0 @@ |
|||||
/****************************************************************************** |
|
||||
Copyright (C) 2017 - 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).
|
|
||||
*****************************************************************************/ |
|
||||
|
|
||||
openerp.pos_picking_load = function(instance, local) { |
|
||||
|
|
||||
|
|
||||
module = instance.point_of_sale; |
|
||||
var QWeb = instance.web.qweb; |
|
||||
var _t = instance.web._t; |
|
||||
var round_pr = instance.web.round_precision; |
|
||||
|
|
||||
/************************************************************************* |
|
||||
Extend Model Order: |
|
||||
* Add getter and setter function for field 'origin_picking_id'; |
|
||||
*/ |
|
||||
var moduleOrderParent = module.Order; |
|
||||
module.Order = module.Order.extend({ |
|
||||
|
|
||||
set_origin_picking_id: function(id) { |
|
||||
this.set({ |
|
||||
origin_picking_id: id, |
|
||||
}); |
|
||||
}, |
|
||||
|
|
||||
set_origin_picking_name: function(name) { |
|
||||
this.set({ |
|
||||
origin_picking_name: name, |
|
||||
}); |
|
||||
}, |
|
||||
|
|
||||
export_for_printing: function(attributes){ |
|
||||
var order = moduleOrderParent.prototype.export_for_printing.apply(this, arguments); |
|
||||
order['origin_picking_name'] = this.get('origin_picking_name'); |
|
||||
return order; |
|
||||
}, |
|
||||
|
|
||||
export_as_JSON: function() { |
|
||||
var order = moduleOrderParent.prototype.export_as_JSON.apply(this, arguments); |
|
||||
order['origin_picking_id'] = this.get('origin_picking_id'); |
|
||||
return order; |
|
||||
}, |
|
||||
|
|
||||
}); |
|
||||
|
|
||||
|
|
||||
/************************************************************************* |
|
||||
New Widget LoadPickingButtonWidget: |
|
||||
* On click, display a new screen to select a picking; |
|
||||
*/ |
|
||||
module.LoadPickingButtonWidget = module.PosBaseWidget.extend({ |
|
||||
template: 'LoadPickingButtonWidget', |
|
||||
|
|
||||
renderElement: function() { |
|
||||
var self = this; |
|
||||
this._super(); |
|
||||
this.$el.click(function(){ |
|
||||
var ss = self.pos.pos_widget.screen_selector; |
|
||||
ss.set_current_screen('pickinglist'); |
|
||||
}); |
|
||||
}, |
|
||||
}); |
|
||||
|
|
||||
|
|
||||
/************************************************************************* |
|
||||
Extend PosWidget: |
|
||||
* Create new screen; |
|
||||
* Add load and save button; |
|
||||
*/ |
|
||||
module.PosWidget = module.PosWidget.extend({ |
|
||||
build_widgets: function() { |
|
||||
this._super(); |
|
||||
|
|
||||
if (this.pos.config.iface_load_picking){ |
|
||||
// New Screen to select a picking
|
|
||||
this.pickinglist_screen = new module.PickingListScreenWidget(this, {}); |
|
||||
this.pickinglist_screen.appendTo(this.$('.screens')); |
|
||||
this.pickinglist_screen.hide(); |
|
||||
this.screen_selector.screen_set.pickinglist = this.pickinglist_screen; |
|
||||
|
|
||||
// Add button
|
|
||||
this.search_picking_button = new module.LoadPickingButtonWidget(this,{}); |
|
||||
this.search_picking_button.appendTo(this.pos_widget.$('li.orderline.empty')); |
|
||||
} |
|
||||
}, |
|
||||
}); |
|
||||
|
|
||||
|
|
||||
/************************************************************************* |
|
||||
Extend OrderWidget: |
|
||||
*/ |
|
||||
module.OrderWidget = module.OrderWidget.extend({ |
|
||||
renderElement: function(scrollbottom){ |
|
||||
this._super(scrollbottom); |
|
||||
if (this.pos_widget.search_picking_button) { |
|
||||
this.pos_widget.search_picking_button.appendTo( |
|
||||
this.pos_widget.$('li.orderline.empty') |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
|
|
||||
/************************************************************************* |
|
||||
New ScreenWidget PickingListScreenWidget: |
|
||||
* On show, display all pickings; |
|
||||
* on click on a picking, display the content; |
|
||||
* on click on 'validate', allow to use this picking; |
|
||||
* on click on 'cancel', display the preview screen; |
|
||||
*/ |
|
||||
module.PickingListScreenWidget = module.ScreenWidget.extend({ |
|
||||
template: 'PickingListScreenWidget', |
|
||||
show_leftpane: true, |
|
||||
model: 'stock.picking', |
|
||||
current_picking_id: false, |
|
||||
current_picking_name: false, |
|
||||
|
|
||||
// Base functions
|
|
||||
init: function(parent, options){ |
|
||||
this._super(parent, options); |
|
||||
}, |
|
||||
|
|
||||
start: function() { |
|
||||
var self = this; |
|
||||
this._super(); |
|
||||
|
|
||||
// Bind click buttons
|
|
||||
this.$el.find('span.button.cancel').click(_.bind(this.clickCancelButton, this)); |
|
||||
this.$el.find('span.button.validate').click(_.bind(this.clickValidateButton, this)); |
|
||||
|
|
||||
// manage Search Box
|
|
||||
var search_timeout = null; |
|
||||
this.$('.searchbox input').on('keyup',function(event){ |
|
||||
clearTimeout(search_timeout); |
|
||||
var query = this.value; |
|
||||
search_timeout = setTimeout(function(){ |
|
||||
self.perform_search(query); |
|
||||
},70); |
|
||||
}); |
|
||||
|
|
||||
this.$('.searchbox .search-clear').click(function(){ |
|
||||
self.clear_search(); |
|
||||
}); |
|
||||
}, |
|
||||
|
|
||||
show: function() { |
|
||||
this._super(); |
|
||||
var ss = this.pos.pos_widget.screen_selector; |
|
||||
this.pos_widget.numpad.hide(); |
|
||||
this.pos_widget.paypad.hide(); |
|
||||
this.search_pickings(); |
|
||||
this.$el.find('span.button.validate').hide(); |
|
||||
}, |
|
||||
|
|
||||
prepare_order: function(order, picking) { |
|
||||
var partner = this.pos.db.get_partner_by_id(picking.partner_id); |
|
||||
order.set_client(partner || undefined); |
|
||||
return order; |
|
||||
}, |
|
||||
|
|
||||
prepare_orderline: function(product, pickingline) { |
|
||||
return { |
|
||||
quantity: pickingline.quantity, |
|
||||
price: pickingline.price_unit || product.price, |
|
||||
discount: pickingline.discount || 0.0, |
|
||||
}; |
|
||||
}, |
|
||||
|
|
||||
// User Event
|
|
||||
clickCancelButton: function(event) { |
|
||||
order = this.pos.get('selectedOrder'); |
|
||||
order.set_client(undefined); |
|
||||
order.set_origin_picking_id(undefined); |
|
||||
order.set_origin_picking_name(undefined); |
|
||||
order.get('orderLines').reset(); |
|
||||
this.pos_widget.order_widget.change_selected_order(); |
|
||||
var ss = this.pos.pos_widget.screen_selector; |
|
||||
ss.set_current_screen('products'); |
|
||||
this.pos_widget.numpad.show(); |
|
||||
this.pos_widget.paypad.show(); |
|
||||
}, |
|
||||
|
|
||||
clickValidateButton: function(event) { |
|
||||
order = this.pos.get('selectedOrder'); |
|
||||
order.set_origin_picking_id(this.current_picking_id); |
|
||||
order.set_origin_picking_name(this.current_picking_name); |
|
||||
var ss = this.pos.pos_widget.screen_selector; |
|
||||
ss.set_current_screen('products'); |
|
||||
this.pos_widget.numpad.show(); |
|
||||
this.pos_widget.paypad.show(); |
|
||||
}, |
|
||||
|
|
||||
load_picking: function(origin_picking_id) { |
|
||||
var self = this; |
|
||||
var pickingModel = new instance.web.Model(this.model); |
|
||||
return pickingModel.call('load_picking_for_pos', [[origin_picking_id]]) |
|
||||
.then(function (picking) { |
|
||||
self.current_picking_id = origin_picking_id; |
|
||||
self.current_picking_name = picking.name; |
|
||||
var picking_selectable = true; |
|
||||
var order = self.pos.get('selectedOrder'); |
|
||||
order = self.prepare_order(order, picking); |
|
||||
order.get('orderLines').reset(); |
|
||||
var pickinglines = picking.line_ids || []; |
|
||||
var unknown_products = []; |
|
||||
for (var i=0, len=pickinglines.length; i<len; i++) { |
|
||||
// check if product are available in pos
|
|
||||
var pickingline = pickinglines[i]; |
|
||||
var line_name = pickingline.name; |
|
||||
var product = self.pos.db.get_product_by_id(pickingline.product_id); |
|
||||
if (_.isUndefined(product)) { |
|
||||
unknown_products.push(line_name); |
|
||||
continue; |
|
||||
} |
|
||||
// Create new line and add it to the current order
|
|
||||
orderline = self.prepare_orderline(product, pickingline); |
|
||||
order.addProduct(product, orderline); |
|
||||
last_orderline = order.getLastOrderline(); |
|
||||
last_orderline = jQuery.extend(last_orderline, orderline); |
|
||||
} |
|
||||
// Forbid POS Order loading if some products are unknown
|
|
||||
if (unknown_products.length > 0){ |
|
||||
self.pos_widget.screen_selector.show_popup( |
|
||||
'error-traceback', { |
|
||||
message: _t('Unknown Products'), |
|
||||
comment: _t('Unable to load some picking lines because the ' + |
|
||||
'products are not available in the POS cache.\n\n' + |
|
||||
'Please check that lines :\n\n * ') + unknown_products.join("; \n *") |
|
||||
}); |
|
||||
picking_selectable = false; |
|
||||
} |
|
||||
// Check if the partner is unknown
|
|
||||
if (_.isUndefined(order.get_client)) { |
|
||||
self.pos_widget.screen_selector.show_popup( |
|
||||
'error-traceback', { |
|
||||
message: _t('Unknown Partner'), |
|
||||
comment: _t('Unable to load this picking because the partner' + |
|
||||
' is not known in the Point Of Sale as a customer') |
|
||||
}); |
|
||||
picking_selectable = false; |
|
||||
} |
|
||||
|
|
||||
if (picking_selectable){ |
|
||||
self.$el.find('span.button.validate').show(); |
|
||||
} |
|
||||
else{ |
|
||||
self.$el.find('span.button.validate').hide(); |
|
||||
} |
|
||||
|
|
||||
}).fail(function (error, event){ |
|
||||
if (parseInt(error.code) === 200) { |
|
||||
// Business Logic Error, not a connection problem
|
|
||||
self.pos_widget.screen_selector.show_popup( |
|
||||
'error-traceback', { |
|
||||
message: error.data.message, |
|
||||
comment: error.data.debug |
|
||||
}); |
|
||||
} |
|
||||
else{ |
|
||||
self.pos_widget.screen_selector.show_popup('error',{ |
|
||||
message: _t('Connection error'), |
|
||||
comment: _t('Can not execute this action because the POS is currently offline'), |
|
||||
}); |
|
||||
} |
|
||||
event.preventDefault(); |
|
||||
}); |
|
||||
}, |
|
||||
|
|
||||
search_pickings: function(query) { |
|
||||
var self = this; |
|
||||
var pickingModel = new instance.web.Model(this.model); |
|
||||
return pickingModel.call('search_pickings_for_pos', [query || '', this.pos.pos_session.id]) |
|
||||
.then(function (result) { |
|
||||
self.render_list(result); |
|
||||
}).fail(function (error, event){ |
|
||||
if (parseInt(error.code) === 200) { |
|
||||
// Business Logic Error, not a connection problem
|
|
||||
self.pos_widget.screen_selector.show_popup( |
|
||||
'error-traceback', { |
|
||||
message: error.data.message, |
|
||||
comment: error.data.debug |
|
||||
} |
|
||||
); |
|
||||
} |
|
||||
else{ |
|
||||
self.pos_widget.screen_selector.show_popup('error',{ |
|
||||
message: _t('Connection error'), |
|
||||
comment: _t('Can not execute this action because the POS is currently offline'), |
|
||||
}); |
|
||||
} |
|
||||
event.preventDefault(); |
|
||||
}); |
|
||||
}, |
|
||||
|
|
||||
on_click_picking: function(event){ |
|
||||
this.load_picking(parseInt(event.target.parentNode.dataset.pickingId, 10)); |
|
||||
}, |
|
||||
|
|
||||
render_list: function(pickings){ |
|
||||
var self = this; |
|
||||
var contents = this.$el[0].querySelector('.picking-list-contents'); |
|
||||
contents.innerHTML = ""; |
|
||||
var line_list = document.createDocumentFragment(); |
|
||||
_.each(pickings, function(picking){ |
|
||||
var picking_line_html = QWeb.render('LoadPickingLine',{widget: this, picking:picking}); |
|
||||
var picking_line = document.createElement('tbody'); |
|
||||
picking_line.innerHTML = picking_line_html; |
|
||||
picking_line = picking_line.childNodes[1]; |
|
||||
picking_line.addEventListener('click', self.on_click_picking); |
|
||||
line_list.appendChild(picking_line); |
|
||||
}); |
|
||||
contents.appendChild(line_list); |
|
||||
}, |
|
||||
|
|
||||
perform_search: function(query){ |
|
||||
this.search_pickings(query); |
|
||||
}, |
|
||||
|
|
||||
clear_search: function(){ |
|
||||
this.search_pickings(); |
|
||||
this.$('.searchbox input')[0].value = ''; |
|
||||
this.$('.searchbox input').focus(); |
|
||||
}, |
|
||||
|
|
||||
}); |
|
||||
|
|
||||
}; |
|
@ -0,0 +1,289 @@ |
|||||
|
/** *************************************************************************** |
||||
|
Copyright (C) 2017 - 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).
|
||||
|
******************************************************************************/ |
||||
|
|
||||
|
odoo.define('pos_picking_load.widget', function (require) { |
||||
|
"use strict"; |
||||
|
|
||||
|
var core = require('web.core'); |
||||
|
var framework = require('web.framework'); |
||||
|
var Model = require('web.DataModel'); |
||||
|
|
||||
|
var gui = require('point_of_sale.gui'); |
||||
|
var screens = require('point_of_sale.screens'); |
||||
|
|
||||
|
var QWeb = core.qweb; |
||||
|
var _t = core._t; |
||||
|
|
||||
|
|
||||
|
/** ********************************************************************** |
||||
|
New ScreenWidget LoadPickingScreenWidget: |
||||
|
* On show, display all pickings; |
||||
|
* on click on a picking, display the content; |
||||
|
* on click on 'validate', allow to use this picking; |
||||
|
* on click on 'cancel', display the preview screen; |
||||
|
*/ |
||||
|
var LoadPickingScreenWidget = screens.ScreenWidget.extend({ |
||||
|
template: 'LoadPickingScreenWidget', |
||||
|
auto_back: true, |
||||
|
|
||||
|
current_picking_id: false, |
||||
|
current_picking_name: false, |
||||
|
|
||||
|
show: function () { |
||||
|
var self = this; |
||||
|
this._super(); |
||||
|
|
||||
|
this.renderElement(); |
||||
|
|
||||
|
// Bind functions
|
||||
|
this.$('.back').click(_.bind(this.clickBackButton, this)); |
||||
|
this.$('.validate').click(_.bind(this.clickValidateButton, this)); |
||||
|
|
||||
|
// Initialize display
|
||||
|
this.$('.validate').hide(); |
||||
|
|
||||
|
this.search_pickings(); |
||||
|
|
||||
|
this.$('.picking-list-contents').delegate( |
||||
|
'.picking-line', 'click', function (event) { |
||||
|
self.select_picking(event); |
||||
|
}); |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
select_picking: function (event) { |
||||
|
var origin_picking_id = parseInt( |
||||
|
event.target.parentNode.dataset.pickingId, 10); |
||||
|
var self = this; |
||||
|
this.current_picking_data = false; |
||||
|
this.$('span.button.validate').hide(); |
||||
|
this.$('.picking-list .highlight').removeClass('highlight'); |
||||
|
|
||||
|
framework.blockUI(); |
||||
|
new Model('stock.picking').call( |
||||
|
'load_picking_for_pos', [origin_picking_id]) |
||||
|
.then(function (picking_data) { |
||||
|
framework.unblockUI(); |
||||
|
if (self.check_picking(picking_data)) { |
||||
|
self.current_picking_data = picking_data; |
||||
|
$(event.target.parentNode).addClass('highlight'); |
||||
|
self.$('span.button.validate').show(); |
||||
|
} |
||||
|
}).fail(function (error, event) { |
||||
|
framework.unblockUI(); |
||||
|
self.handle_errors(error, event); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
check_picking: function (picking_data) { |
||||
|
var self = this; |
||||
|
|
||||
|
var picking_selectable = true; |
||||
|
|
||||
|
// Forbid POS Order loading if some products are unknown
|
||||
|
var unknown_products = []; |
||||
|
|
||||
|
picking_data.line_ids.forEach(function (picking_line_data) { |
||||
|
var line_name = picking_line_data.name.replace('\n', ' '); |
||||
|
var product = self.pos.db.get_product_by_id( |
||||
|
picking_line_data.product_id); |
||||
|
if (_.isUndefined(product)) { |
||||
|
unknown_products.push(line_name); |
||||
|
} |
||||
|
}); |
||||
|
if (unknown_products.length > 0) { |
||||
|
self.gui.show_popup( |
||||
|
'error-traceback', { |
||||
|
'title': _t('Unknown Products'), |
||||
|
'body': _t( |
||||
|
"Unable to load some picking lines because the" + |
||||
|
" products are not available in the POS" + |
||||
|
" cache.\n\n" + |
||||
|
"Please check that lines :\n\n * ") + |
||||
|
unknown_products.join("; \n *"), |
||||
|
}); |
||||
|
picking_selectable = false; |
||||
|
} |
||||
|
|
||||
|
// Check if the partner is unknown
|
||||
|
var partner = self.pos.db.get_partner_by_id( |
||||
|
picking_data.partner_id); |
||||
|
|
||||
|
if (_.isUndefined(partner)) { |
||||
|
self.gui.show_popup( |
||||
|
'error-traceback', { |
||||
|
'title': _t('Unknown Partner'), |
||||
|
'body': _t( |
||||
|
"Unable to load this picking because the partner" + |
||||
|
" is not known in the Point Of Sale" + |
||||
|
" as a customer"), |
||||
|
}); |
||||
|
picking_selectable = false; |
||||
|
} |
||||
|
|
||||
|
// Check if the picking is still loaded in another PoS tab
|
||||
|
self.pos.db.get_unpaid_orders().forEach(function (order) { |
||||
|
if (order.origin_picking_id === picking_data.id) { |
||||
|
self.gui.show_popup( |
||||
|
'error-traceback', { |
||||
|
'title': _t('Picking Still Loaded'), |
||||
|
'body': _t( |
||||
|
"Unable to load this picking because it has" + |
||||
|
" been loaded in another draft" + |
||||
|
" PoS Order :\n\n") + |
||||
|
order.name, |
||||
|
}); |
||||
|
picking_selectable = false; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// Check if the picking has still been handled in another PoS Order
|
||||
|
self.pos.db.get_orders().forEach(function (order) { |
||||
|
if (order.origin_picking_id === picking_data.id) { |
||||
|
self.gui.show_popup( |
||||
|
'error-traceback', { |
||||
|
'title': _t('Picking Still Loaded'), |
||||
|
'body': _t( |
||||
|
"Unable to load this picking because it has" + |
||||
|
" been loaded in another confirmed" + |
||||
|
" PoS Order :\n\n") + |
||||
|
order.name, |
||||
|
}); |
||||
|
picking_selectable = false; |
||||
|
} |
||||
|
}); |
||||
|
return picking_selectable; |
||||
|
}, |
||||
|
|
||||
|
search_pickings: function (query) { |
||||
|
var self = this; |
||||
|
return new Model('stock.picking').call( |
||||
|
'search_pickings_for_pos', |
||||
|
[query || '', this.pos.pos_session.id]) |
||||
|
.then(function (result) { |
||||
|
self.render_list(result); |
||||
|
}).fail(function (error, event) { |
||||
|
self.handle_errors(error, event); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
render_list: function (pickings) { |
||||
|
var contents = this.$el[0].querySelector('.picking-list-contents'); |
||||
|
contents.innerHTML = ""; |
||||
|
var line_list = document.createDocumentFragment(); |
||||
|
_.each(pickings, function (picking) { |
||||
|
var picking_line_html = QWeb.render( |
||||
|
'LoadPickingLine', {widget: this, picking:picking}); |
||||
|
var picking_line = document.createElement('tbody'); |
||||
|
picking_line.innerHTML = picking_line_html; |
||||
|
picking_line = picking_line.childNodes[1]; |
||||
|
line_list.appendChild(picking_line); |
||||
|
}); |
||||
|
contents.appendChild(line_list); |
||||
|
}, |
||||
|
|
||||
|
// User Event
|
||||
|
clickBackButton: function () { |
||||
|
this.gui.back(); |
||||
|
}, |
||||
|
|
||||
|
clickValidateButton: function () { |
||||
|
var order = this.pos.get_order(); |
||||
|
order.load_from_picking_data(this.current_picking_data); |
||||
|
this.gui.show_screen('products'); |
||||
|
}, |
||||
|
|
||||
|
handle_errors: function (error, event) { |
||||
|
var self = this; |
||||
|
if (parseInt(error.code, 10) === 200) { |
||||
|
// Business Logic Error, not a connection problem
|
||||
|
self.gui.show_popup('error-traceback', { |
||||
|
'title': error.data.message || _t("Server Error"), |
||||
|
'body': error.data.debug || _t( |
||||
|
"The server encountered an error while" + |
||||
|
" receiving your order."), |
||||
|
}); |
||||
|
} else { |
||||
|
self.gui.show_popup('error', { |
||||
|
'title': _t('Connection error'), |
||||
|
'body': _t( |
||||
|
"Can not execute this action because the POS" + |
||||
|
" is currently offline"), |
||||
|
}); |
||||
|
} |
||||
|
event.preventDefault(); |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
gui.define_screen({ |
||||
|
'name': 'load_picking', |
||||
|
'widget': LoadPickingScreenWidget, |
||||
|
'condition': function () { |
||||
|
return this.pos.config.iface_load_picking; |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
/** ********************************************************************** |
||||
|
New Widget LoadPickingButtonWidget: |
||||
|
* On click, display a new screen to select a picking; |
||||
|
*/ |
||||
|
var LoadPickingButtonWidget = screens.ActionButtonWidget.extend({ |
||||
|
template: 'LoadPickingButtonWidget', |
||||
|
|
||||
|
button_click: function () { |
||||
|
if (_.isUndefined(this.pos.get_order().get('origin_picking_id'))) { |
||||
|
this.gui.show_screen('load_picking'); |
||||
|
} else { |
||||
|
this.gui.show_popup('error', { |
||||
|
'title': _t('Pending Picking'), |
||||
|
'body': _t( |
||||
|
"A picking is still loaded. You can not load" + |
||||
|
" another picking. Please create a new order."), |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
button_text: function () { |
||||
|
if (! this.pos.get_order() || |
||||
|
_.isUndefined( |
||||
|
this.pos.get_order().get('origin_picking_id'))) { |
||||
|
return _t("Load Picking"); |
||||
|
} |
||||
|
return this.pos.get_order().get('origin_picking_name'); |
||||
|
}, |
||||
|
|
||||
|
is_visible: function () { |
||||
|
if (this.pos.get_order()) { |
||||
|
return ( |
||||
|
this.pos.get_order().get_orderlines().length === 0 || |
||||
|
! _.isUndefined( |
||||
|
this.pos.get_order().get('origin_picking_id'))); |
||||
|
} |
||||
|
return false; |
||||
|
}, |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
screens.define_action_button({ |
||||
|
'name': 'load_picking', |
||||
|
'widget': LoadPickingButtonWidget, |
||||
|
'condition': function () { |
||||
|
return this.pos.config.iface_load_picking; |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
screens.OrderWidget.include({ |
||||
|
update_summary: function () { |
||||
|
this._super(); |
||||
|
if (this.getParent().action_buttons && |
||||
|
this.getParent().action_buttons.load_picking) { |
||||
|
this.getParent().action_buttons.load_picking.renderElement(); |
||||
|
} |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
}); |
@ -1,18 +1,18 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<!-- vim:fdn=3: |
|
||||
--> |
|
||||
<openerp> |
|
||||
<data> |
|
||||
<template id="assets_backend" name="pos_picking_load assets" inherit_id="web.assets_backend"> |
|
||||
<xpath expr="." position="inside"> |
|
||||
<script type="text/javascript" src="/pos_picking_load/static/src/js/pos_picking_load.js"></script> |
|
||||
</xpath> |
|
||||
</template> |
|
||||
<template id="index_pos_picking_load" name="POS Index Picking Load" inherit_id="point_of_sale.index"> |
|
||||
<xpath expr="//head" position="inside"> |
|
||||
<link rel="stylesheet" href="/pos_picking_load/static/src/css/pos_picking_load.css"/> |
|
||||
</xpath> |
|
||||
</template> |
|
||||
</data> |
|
||||
</openerp> |
|
||||
|
|
||||
|
<odoo> |
||||
|
|
||||
|
<template id="assets" name="pos_picking_load assets" inherit_id="point_of_sale.assets"> |
||||
|
<xpath expr="." position="inside"> |
||||
|
<script type="text/javascript" src="/pos_picking_load/static/src/js/widget.js"></script> |
||||
|
<script type="text/javascript" src="/pos_picking_load/static/src/js/model.js"></script> |
||||
|
</xpath> |
||||
|
</template> |
||||
|
|
||||
|
<template id="index_pos_picking_load" name="POS Index Picking Load" inherit_id="point_of_sale.index"> |
||||
|
<xpath expr="//head" position="inside"> |
||||
|
<link rel="stylesheet" href="/pos_picking_load/static/src/css/pos_picking_load.css"/> |
||||
|
</xpath> |
||||
|
</template> |
||||
|
|
||||
|
</odoo> |
@ -1,14 +1,15 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<openerp><data> |
|
||||
|
|
||||
|
<odoo> |
||||
|
|
||||
<record id="view_sale_order_form" model="ir.ui.view"> |
<record id="view_sale_order_form" model="ir.ui.view"> |
||||
<field name="model">sale.order</field> |
<field name="model">sale.order</field> |
||||
<field name="inherit_id" ref="sale_stock.view_order_form_inherit"/> |
|
||||
|
<field name="inherit_id" ref="sale_stock.view_order_form_inherit_sale_stock"/> |
||||
<field name="arch" type="xml"> |
<field name="arch" type="xml"> |
||||
<field name="picking_policy" position="before"> |
<field name="picking_policy" position="before"> |
||||
<field name="final_pos_order_id" /> |
|
||||
|
<field name="final_pos_order_id" attrs="{'invisible': [('final_pos_order_id', '=', False)]}"/> |
||||
</field> |
</field> |
||||
</field> |
</field> |
||||
</record> |
</record> |
||||
|
|
||||
</data></openerp> |
|
||||
|
</odoo> |
@ -1,14 +1,15 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<openerp><data> |
|
||||
|
|
||||
|
<odoo> |
||||
|
|
||||
<record id="view_stock_picking_form" model="ir.ui.view"> |
<record id="view_stock_picking_form" model="ir.ui.view"> |
||||
<field name="model">stock.picking</field> |
<field name="model">stock.picking</field> |
||||
<field name="inherit_id" ref="stock_account.view_picking_inherit_form2"/> |
|
||||
|
<field name="inherit_id" ref="stock.view_picking_form"/> |
||||
<field name="arch" type="xml"> |
<field name="arch" type="xml"> |
||||
<field name="invoice_state" position="after"> |
|
||||
<field name="final_pos_order_id" /> |
|
||||
|
<field name="picking_type_id" position="after"> |
||||
|
<field name="final_pos_order_id" attrs="{'invisible': [('final_pos_order_id', '=', False)]}"/> |
||||
</field> |
</field> |
||||
</field> |
</field> |
||||
</record> |
</record> |
||||
|
|
||||
</data></openerp> |
|
||||
|
</odoo> |