Browse Source

[ADD] new module pos_picking_load (#183)

[ADD] new module pos_picking_load
pull/365/head
Sylvain LE GAL 7 years ago
parent
commit
bf3320cda7
  1. 178
      pos_picking_load/README.rst
  2. 2
      pos_picking_load/__init__.py
  3. 32
      pos_picking_load/__openerp__.py
  4. 19
      pos_picking_load/demo/product_template.xml
  5. 7
      pos_picking_load/demo/res_groups.xml
  6. 51
      pos_picking_load/demo/sale_order.xml
  7. 7
      pos_picking_load/demo/stock_picking_type.xml
  8. 196
      pos_picking_load/i18n/fr.po
  9. 6
      pos_picking_load/models/__init__.py
  10. 12
      pos_picking_load/models/pos_config.py
  11. 55
      pos_picking_load/models/pos_order.py
  12. 14
      pos_picking_load/models/sale_order.py
  13. 72
      pos_picking_load/models/stock_picking.py
  14. 15
      pos_picking_load/models/stock_picking_type.py
  15. BIN
      pos_picking_load/static/description/load_picking_01.png
  16. BIN
      pos_picking_load/static/description/load_picking_02.png
  17. BIN
      pos_picking_load/static/description/load_picking_03.png
  18. BIN
      pos_picking_load/static/description/load_picking_04.png
  19. BIN
      pos_picking_load/static/description/load_picking_05.png
  20. BIN
      pos_picking_load/static/description/load_picking_06.png
  21. BIN
      pos_picking_load/static/description/pos_config_form.png
  22. BIN
      pos_picking_load/static/description/stock_picking_type_form.png
  23. 40
      pos_picking_load/static/src/css/pos_picking_load.css
  24. 329
      pos_picking_load/static/src/js/pos_picking_load.js
  25. 85
      pos_picking_load/static/src/xml/pos_picking_load.xml
  26. 18
      pos_picking_load/views/pos_picking_load.xml
  27. 14
      pos_picking_load/views/view_pos_config.xml
  28. 14
      pos_picking_load/views/view_sale_order.xml
  29. 14
      pos_picking_load/views/view_stock_picking.xml
  30. 14
      pos_picking_load/views/view_stock_picking_type.xml

178
pos_picking_load/README.rst

@ -0,0 +1,178 @@
.. 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
================
POS Picking Load
================
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.
Configuration
=============
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'.
.. figure:: static/description/pos_config_form.png
:width: 800 px
Note: This box is enabled by default
Usage
=====
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.png
:width: 800 px
* Point of sale will load available pickings. (About displayed pickings, see
'Technical Notes' below).
.. figure:: static/description/load_picking_02.png
:width: 800 px
* Click on a picking will display the content of the moves (as PoS Order Lines)
.. figure:: static/description/load_picking_03.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 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_04.png
:width: 800 px
**Related Picking:**
.. figure:: static/description/load_picking_05.png
:width: 800 px
* Confirm the selection, by clicking on 'Select' button.
* 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' below).
Some warning messages can appear:
* if some products are not available in the Point of Sale.
.. figure:: static/description/load_picking_06.png
:width: 800 px
.. 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
===============
* By default, the following filters are applied on the pickings displayed:
* 'State' should be 'Waiting Availability', 'Partially Available' or
'Ready to Transfer'.
* 'Invoice Control' should be 'To be invoiced'.
You can change this filter by overloading the ```_prepare_filter_for_pos```
function of the model ```stock.picking```
* By default, 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```.
Known issues / Roadmap
======================
* 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'. **The VAT settings should be consistent**.
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
=======
Images
------
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
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.

2
pos_picking_load/__init__.py

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

32
pos_picking_load/__openerp__.py

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# 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).
{
'name': 'POS Picking Load',
'version': '8.0.1.0.0',
'author': 'GRAP,Odoo Community Association (OCA)',
'category': 'Point Of Sale',
'license': 'AGPL-3',
'depends': [
'point_of_sale',
],
'website': 'https://odoo-community.org/',
'data': [
'views/view_pos_config.xml',
'views/view_sale_order.xml',
'views/view_stock_picking.xml',
'views/view_stock_picking_type.xml',
'views/pos_picking_load.xml',
],
'demo': [
'demo/res_groups.xml',
'demo/product_template.xml',
'demo/sale_order.xml',
'demo/stock_picking_type.xml',
],
'qweb': [
'static/src/xml/pos_picking_load.xml',
],
}

19
pos_picking_load/demo/product_template.xml

@ -0,0 +1,19 @@
<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>

7
pos_picking_load/demo/res_groups.xml

@ -0,0 +1,7 @@
<openerp><data>
<record id="sale.group_discount_per_so_line" model="res.groups">
<field name="users" eval="[(4, ref('base.user_root'))]"/>
</record>
</data></openerp>

51
pos_picking_load/demo/sale_order.xml

@ -0,0 +1,51 @@
<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_picking_load/demo/stock_picking_type.xml

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

196
pos_picking_load/i18n/fr.po

@ -0,0 +1,196 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * pos_picking_load
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-06-21 13:48+0000\n"
"PO-Revision-Date: 2017-06-21 13:48+0000\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_picking_load
#. openerp-web
#: code:addons/pos_picking_load/static/src/xml/pos_picking_load.xml:74
#, python-format
msgid "--------------------------------"
msgstr "--------------------------------"
#. module: pos_picking_load
#: field:stock.picking.type,available_in_pos:0
msgid "Available in Point Of Sale"
msgstr "Disponible dans le point de vente"
#. module: pos_picking_load
#. openerp-web
#: code:addons/pos_picking_load/static/src/js/pos_picking_load.js:264
#: code:addons/pos_picking_load/static/src/js/pos_picking_load.js:290
#, python-format
msgid "Can not execute this action because the POS is currently offline"
msgstr "Impossible de réaliser cette action car le point de vente est actuellement hors ligne"
#. module: pos_picking_load
#. openerp-web
#: code:addons/pos_picking_load/static/src/xml/pos_picking_load.xml:22
#, python-format
msgid "Cancel"
msgstr "Annuler"
#. module: pos_picking_load
#. openerp-web
#: code:addons/pos_picking_load/static/src/js/pos_picking_load.js:263
#: code:addons/pos_picking_load/static/src/js/pos_picking_load.js:289
#, python-format
msgid "Connection error"
msgstr "Erreur de connexion"
#. module: pos_picking_load
#. openerp-web
#: code:addons/pos_picking_load/static/src/xml/pos_picking_load.xml:54
#, python-format
msgid "Customer"
msgstr "Client"
#. module: pos_picking_load
#: field:sale.order,final_pos_order_id:0
#: field:stock.picking,final_pos_order_id:0
msgid "Final PoS Order"
msgstr "Commande Finale"
#. module: pos_picking_load
#: help:stock.picking.type,available_in_pos:0
msgid "If checked, associated pickings will be available in the point of sale, to be changed and paid in it"
msgstr "Si la case est cochée, toutes les bons de livraisons associés seront disponible dans le point de vente, pour ere changé et payé au sein de celui-ci"
#. module: pos_picking_load
#. openerp-web
#: code:addons/pos_picking_load/static/src/xml/pos_picking_load.xml:5
#, python-format
msgid "Load Picking"
msgstr "Charger un bon de livraison"
#. module: pos_picking_load
#: field:pos.config,iface_load_picking:0
msgid "Load Pickings"
msgstr "Charger des bons de livraison"
#. module: pos_picking_load
#. openerp-web
#: code:addons/pos_picking_load/static/src/xml/pos_picking_load.xml:53
#, python-format
msgid "Name"
msgstr "Nom"
#. module: pos_picking_load
#: field:pos.order,origin_picking_id:0
msgid "Origin Picking"
msgstr "Livraison d'origine"
#. module: pos_picking_load
#. openerp-web
#: code:addons/pos_picking_load/static/src/xml/pos_picking_load.xml:75
#: code:addons/pos_picking_load/static/src/xml/pos_picking_load.xml:83
#, python-format
msgid "Origin Picking:"
msgstr "BL d'origine:"
#. module: pos_picking_load
#: model:ir.model,name:pos_picking_load.model_stock_picking
msgid "Picking List"
msgstr "Bon de livraison"
#. module: pos_picking_load
#: model:ir.model,name:pos_picking_load.model_pos_order
msgid "Point of Sale"
msgstr "Point de Vente"
#. module: pos_picking_load
#: model:ir.model,name:pos_picking_load.model_sale_order
msgid "Sales Order"
msgstr "Commande de ventes"
#. module: pos_picking_load
#. openerp-web
#: code:addons/pos_picking_load/static/src/xml/pos_picking_load.xml:55
#, python-format
msgid "Scheduled Date"
msgstr "Date prévue"
#. module: pos_picking_load
#. openerp-web
#: code:addons/pos_picking_load/static/src/xml/pos_picking_load.xml:30
#, python-format
msgid "Search Picking"
msgstr "Chercher un bon de livraison"
#. module: pos_picking_load
#. openerp-web
#: code:addons/pos_picking_load/static/src/xml/pos_picking_load.xml:26
#, python-format
msgid "Select"
msgstr "Sélectionner"
#. module: pos_picking_load
#. openerp-web
#: code:addons/pos_picking_load/static/src/xml/pos_picking_load.xml:56
#, python-format
msgid "Source Document"
msgstr "Document d'origine"
#. module: pos_picking_load
#: model:ir.model,name:pos_picking_load.model_stock_picking_type
msgid "The picking type determines the picking view"
msgstr "Le type de préparation détermine la vue de préparation"
#. module: pos_picking_load
#: help:sale.order,final_pos_order_id:0
msgid "This Sale Order has beend replaced by this PoS Order"
msgstr "Ce bon de commande a été remplacé par cette commande via le point de vente"
#. module: pos_picking_load
#: help:stock.picking,final_pos_order_id:0
msgid "This picking has been canceled, because it has been replaced by this PoS Order"
msgstr "Ce bon de livraison a été annulé, car il a été remplacé par cette commande via le point de vente"
#. module: pos_picking_load
#. openerp-web
#: code:addons/pos_picking_load/static/src/js/pos_picking_load.js:228
#, python-format
msgid "Unable to load some picking lines because the products are not available in the POS cache.\n"
"\n"
"Please check that lines :\n"
"\n"
" * "
msgstr "Impossible de charger certaines lignes du bon de livraison car certains produits ne sont pas disponibles dans le point de vente.\n"
"\n"
"Merci de vérifier les lignes suivantes :\n"
"\n"
" * "
#. module: pos_picking_load
#. openerp-web
#: code:addons/pos_picking_load/static/src/js/pos_picking_load.js:239
#, python-format
msgid "Unable to load this picking because the partner is not known in the Point Of Sale as a customer"
msgstr "Impossible de charger ce bon de livraison car ce partenaire n'est pas reconnu dans le point de vente comme un client"
#. module: pos_picking_load
#. openerp-web
#: code:addons/pos_picking_load/static/src/js/pos_picking_load.js:238
#, python-format
msgid "Unknown Partner"
msgstr "Partenaire inconnu"
#. module: pos_picking_load
#. openerp-web
#: code:addons/pos_picking_load/static/src/js/pos_picking_load.js:227
#, python-format
msgid "Unknown Products"
msgstr "Produits inconnus"

6
pos_picking_load/models/__init__.py

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
from . import sale_order
from . import stock_picking
from . import stock_picking_type
from . import pos_config
from . import pos_order

12
pos_picking_load/models/pos_config.py

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# 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).
from openerp import models, fields
class PosConfig(models.Model):
_inherit = 'pos.config'
iface_load_picking = fields.Boolean(string='Load Pickings', default=True)

55
pos_picking_load/models/pos_order.py

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
# 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).
from openerp import models, api, fields
class PosOrder(models.Model):
_inherit = 'pos.order'
# Field Section
origin_picking_id = fields.Many2one(
string='Origin Picking', comodel_name='stock.picking',
readonly=True)
# Overloadable Section
@api.multi
def _handle_orders_with_original_picking(self):
"""By default, the module cancel original stock picking and sale
order. Overload / Overwrite this function if you want another
behaviour"""
sale_order_obj = self.env['sale.order']
for order in self:
# Cancel Picking
order.origin_picking_id.action_cancel()
order.origin_picking_id.write({'final_pos_order_id': order.id})
# Ignore Delivery exception of the Sale Order
sale_orders = sale_order_obj.search([
('procurement_group_id', '=',
order.origin_picking_id.group_id.id)])
# sale_orders.action_ignore_delivery_exception()
sale_orders.signal_workflow('ship_corrected')
sale_orders.write({'final_pos_order_id': order.id})
# Overload Section
@api.model
def create_from_ui(self, orders):
"""Cancel the original picking, when the pos order is done"""
res = super(PosOrder, self).create_from_ui(orders)
orders_with_original_picking = self.search([
('id', 'in', res), ('origin_picking_id', '!=', False),
('state', 'not in', [('draft')])])
orders_with_original_picking._handle_orders_with_original_picking()
return res
@api.model
def _order_fields(self, ui_order):
res = super(PosOrder, self)._order_fields(ui_order)
if 'origin_picking_id' in ui_order:
res['origin_picking_id'] = ui_order['origin_picking_id']
return res

14
pos_picking_load/models/sale_order.py

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# 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).
from openerp import models, fields
class SaleOrder(models.Model):
_inherit = 'sale.order'
final_pos_order_id = fields.Many2one(
string='Final PoS Order', comodel_name='pos.order', readonly=True,
help="This Sale Order has beend replaced by this PoS Order")

72
pos_picking_load/models/stock_picking.py

@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
# 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).
from openerp import models, api, fields
class StockPicking(models.Model):
_inherit = 'stock.picking'
# Field Section
final_pos_order_id = fields.Many2one(
string='Final PoS Order', comodel_name='pos.order', readonly=True,
help="This picking has been canceled, because it has been replaced by"
" this PoS Order")
# Custom Section
@api.model
def _prepare_filter_for_pos(self):
return [
('state', 'in', ['confirmed', 'partially_available', 'assigned']),
('invoice_state', '=', '2binvoiced'),
]
@api.model
def _prepare_fields_for_pos_list(self):
return ['name', 'partner_id', 'min_date', 'origin']
@api.model
def search_pickings_for_pos(self, query):
# Get Picking Types available for PoS
picking_type_obj = self.env['stock.picking.type']
picking_types = picking_type_obj.search(
[('available_in_pos', '=', True)])
condition = self._prepare_filter_for_pos() + [
('picking_type_id', 'in', picking_types.ids),
'|', '|',
('name', 'ilike', query),
('origin', 'ilike', query),
('partner_id', 'ilike', query)
]
fields = self._prepare_fields_for_pos_list()
return self.search_read(condition, fields, limit=10)
@api.model
def _prepare_pos_order(self, picking):
pickinglines = []
for line in picking.move_lines.filtered(lambda x: x.state != 'cancel'):
picking_line = {
'name': line.name,
'product_id': line.product_id.id,
'quantity': line.product_uom_qty,
}
sale_order_line =\
line.procurement_id and line.procurement_id.sale_line_id
if sale_order_line:
# Get price and discount of the order if available
picking_line['price_unit'] = sale_order_line.price_unit
picking_line['discount'] = sale_order_line.discount
pickinglines.append(picking_line)
return {
'id': picking.id,
'name': picking.name,
'partner_id': picking.partner_id.id,
'line_ids': pickinglines,
}
@api.model
def load_picking_for_pos(self, picking_id):
picking = self.browse(picking_id)
return self._prepare_pos_order(picking)

15
pos_picking_load/models/stock_picking_type.py

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# 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).
from openerp import models, fields
class StockPickingType(models.Model):
_inherit = 'stock.picking.type'
available_in_pos = fields.Boolean(
string='Available in Point Of Sale', help="If checked, associated"
" pickings will be available in the point of sale, to be changed and"
" paid in it")

BIN
pos_picking_load/static/description/load_picking_01.png

After

Width: 421  |  Height: 205  |  Size: 8.9 KiB

BIN
pos_picking_load/static/description/load_picking_02.png

After

Width: 855  |  Height: 222  |  Size: 26 KiB

BIN
pos_picking_load/static/description/load_picking_03.png

After

Width: 415  |  Height: 306  |  Size: 26 KiB

BIN
pos_picking_load/static/description/load_picking_04.png

After

Width: 837  |  Height: 495  |  Size: 43 KiB

BIN
pos_picking_load/static/description/load_picking_05.png

After

Width: 839  |  Height: 298  |  Size: 33 KiB

BIN
pos_picking_load/static/description/load_picking_06.png

After

Width: 511  |  Height: 426  |  Size: 14 KiB

BIN
pos_picking_load/static/description/pos_config_form.png

After

Width: 637  |  Height: 160  |  Size: 12 KiB

BIN
pos_picking_load/static/description/stock_picking_type_form.png

After

Width: 785  |  Height: 251  |  Size: 31 KiB

40
pos_picking_load/static/src/css/pos_picking_load.css

@ -0,0 +1,40 @@
.pos .pickinglist-screen .button.picking-button {
width: 150px;
}
.pos .pickinglist-screen .button.cancel {
margin-left: -180px;
}
.pos .pickinglist-screen .picking-list{
font-size: 16px;
width: 100%;
line-height: 40px;
}
.pos .pickinglist-screen .picking-list th,
.pos .pickinglist-screen .picking-list td {
padding: 0px 8px;
}
.pos .pickinglist-screen .picking-list tbody > tr{
background: rgb(230,230,230);
cursor: pointer;
}
.pos .pickinglist-screen .picking-list thead > tr,
.pos .pickinglist-screen .picking-list tr:nth-child(even) {
background: rgb(247,247,247);
}
.pos .pickinglist-screen .picking-list tbody > tr:hover {
background: rgb(220,220,220);
}
.pos .pickinglist-screen .searchbox{
margin-top:8px;
}
.pos .pickinglist-screen .searchbox input{
width: 150px;
}

329
pos_picking_load/static/src/js/pos_picking_load.js

@ -0,0 +1,329 @@
/******************************************************************************
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.qty,
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 || ''])
.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();
},
});
};

85
pos_picking_load/static/src/xml/pos_picking_load.xml

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<template>
<t t-name="LoadPickingButtonWidget">
<br/><button class="input-button">Load Picking</button>
</t>
<t t-name="LoadPickingLine">
<tr class="picking-line" t-att-data-picking-id="picking.id">
<td><t t-esc="picking.name"/></td>
<td><t t-esc="picking.partner_id[1]"/></td>
<td><t t-esc="picking.min_date"/></td>
<td><t t-esc="picking.origin"/></td>
</tr>
</t>
<t t-name="PickingListScreenWidget">
<div class="pickinglist-screen screen">
<div class="screen-content">
<section class="top-content">
<span class="button cancel picking-button">
<i class="fa fa-times"></i>
Cancel
</span>
<span class="button validate picking-button">
<i class="fa fa-arrow-circle-o-left"></i>
Select
</span>
<span class="searchbox">
<input placeholder="Search Picking" />
<span class="search-clear"></span>
</span>
<span class="searchbox"></span>
</section>
<section class="full-content">
<div class="window">
<section class="subwindow collapsed">
<div class="subwindow-container">
<div class="subwindow-container-fix">
</div>
</div>
</section>
<section class="subwindow">
<div class="subwindow-container">
<div class="subwindow-container-fix touch-scrollable scrollable-y">
<table class="picking-list">
<thead>
<tr>
<th>Name</th>
<th>Customer</th>
<th>Scheduled Date</th>
<th>Source Document</th>
</tr>
</thead>
<tbody class="picking-list-contents">
</tbody>
</table>
</div>
</div>
</section>
</div>
</section>
</div>
</div>
</t>
<t t-extend="XmlReceipt">
<t t-jquery="[t-if='receipt.cashier']" t-operation="after">
<t t-if="receipt.origin_picking_name">
<div>--------------------------------</div>
<div>Origin Picking: <t t-esc='receipt.origin_picking_name' /></div>
</t>
</t>
</t>
<t t-extend="PosTicket">
<t t-jquery="[t-esc='widget.pos.shop.name']" t-operation="after">
<t t-if="order.get('origin_picking_name')">
<br /><br />Origin Picking: <t t-esc="order.get('origin_picking_name')"/>
</t>
</t>
</t>
</template>

18
pos_picking_load/views/pos_picking_load.xml

@ -0,0 +1,18 @@
<?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>

14
pos_picking_load/views/view_pos_config.xml

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp><data>
<record id="view_pos_config_form" model="ir.ui.view">
<field name="model">pos.config</field>
<field name="inherit_id" ref="point_of_sale.view_pos_config_form"/>
<field name="arch" type="xml">
<field name="iface_invoicing" position="after">
<field name="iface_load_picking" />
</field>
</field>
</record>
</data></openerp>

14
pos_picking_load/views/view_sale_order.xml

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp><data>
<record id="view_sale_order_form" model="ir.ui.view">
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale_stock.view_order_form_inherit"/>
<field name="arch" type="xml">
<field name="picking_policy" position="before">
<field name="final_pos_order_id" />
</field>
</field>
</record>
</data></openerp>

14
pos_picking_load/views/view_stock_picking.xml

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp><data>
<record id="view_stock_picking_form" model="ir.ui.view">
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock_account.view_picking_inherit_form2"/>
<field name="arch" type="xml">
<field name="invoice_state" position="after">
<field name="final_pos_order_id" />
</field>
</field>
</record>
</data></openerp>

14
pos_picking_load/views/view_stock_picking_type.xml

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp><data>
<record id="view_stock_picking_type_form" model="ir.ui.view">
<field name="model">stock.picking.type</field>
<field name="inherit_id" ref="stock.view_picking_type_form"/>
<field name="arch" type="xml">
<field name="return_picking_type_id" position="after">
<field name="available_in_pos" />
</field>
</field>
</record>
</data></openerp>
Loading…
Cancel
Save