Skip to content

Commit

Permalink
[ADD] stock_picking_variant_mgmt (OCA#49)
Browse files Browse the repository at this point in the history
=================================================
Handle easily multiple variants on Stock Pickings
=================================================

This module allows to add/modify of all the variants of a product in a direct
screen without the need of handling them one by one.

It also adds a convenient way of handling the transfer of the products in a
2D matrix with all the values of the first attribute in columns, and the
rest of the combinations in rows.

Configuration
=============

* Configure your user to have any permission from "Inventory" group.
* Create a product with 2 attributes and several values.

Usage
=====

* Go to Inventory > Dashboard.
* Create a new picking from one of the existing picking types.
* Press "Add variants" button located in the upper right corner of the
  "Initial Demand" tab.
* A new screen will appear allowing you to select the products that have
  variants.
* Once you select the product, a 2D matrix will appear with the first
  attribute values as columns and the second one (if any) as rows.
* If there are already order lines for the product variants, the current
  quantity will be pre-filled in the matrix.
* Change the quantities for the variant you want and click on "Transfer to
  picking"
* Move lines for the variants will be created/removed to comply with the
  input you have done.

As extra feature for saving steps, there's also a button on each existing line
that corresponds to a variant that opens the dialog directly with the product
selected.

You are also able to manage variants on 1 dimension in the transfer:

* Go to the "Operations" page.
* Press on "Manage Variants Transfer" button in the upper right corner of the
  tab.
* Change the quantities to transfer.
* Click on "Transfer to picking" button.

Known issues / Roadmap
======================

* The inline button for modifying quantities for an existing line won't
  work correctly until these 2 PRs are merged in Odoo:

  * odoo/odoo#13557
  * odoo/odoo#13635

  The patches are already integrated on OCB.

* Make this work with product with more than 1 attribute.
  • Loading branch information
pedrobaeza committed Oct 27, 2017
1 parent 9db955f commit 99d486e
Show file tree
Hide file tree
Showing 14 changed files with 667 additions and 0 deletions.
90 changes: 90 additions & 0 deletions stock_picking_variant_mgmt/README.rst
@@ -0,0 +1,90 @@
.. 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

=================================================
Handle easily multiple variants on Stock Pickings
=================================================

This module allows to add/modify of all the variants of a product in a direct
screen without the need of handling them one by one.

It also adds a convenient way of handling the transfer of the products in a
2D matrix with all the values of the first attribute in columns, and the
rest of the combinations in rows.

Configuration
=============

#. Configure your user to have any permission from "Inventory" group.
#. Create a product with 2 attributes and several values.

Usage
=====

#. Go to Inventory > Dashboard.
#. Create a new picking from one of the existing picking types.
#. Press "Add variants" button located in the upper right corner of the
"Initial Demand" tab.
#. A new screen will appear allowing you to select the products that have
variants.
#. Once you select the product, a 2D matrix will appear with the first
attribute values as columns and the second one (if any) as rows.
#. If there are already order lines for the product variants, the current
quantity will be pre-filled in the matrix.
#. Change the quantities for the variant you want and click on "Transfer to
picking"
#. Move lines for the variants will be created/removed to comply with the
input you have done.

As extra feature for saving steps, there's also a button on each existing line
that corresponds to a variant that opens the dialog directly with the product
selected.

You are also able to manage variants on 1 dimension in the transfer:

#. Go to the "Operations" page.
#. Press on "Manage Variants Transfer" button in the upper right corner of the
tab.
#. Change the quantities to transfer.
#. Click on "Transfer to picking" button.

.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/137/9.0

Known issues / Roadmap
======================

* The inline button for modifying quantities for an existing line won't
work correctly until these 2 PRs are merged in Odoo:

* https://github.com/odoo/odoo/pull/13557
* https://github.com/odoo/odoo/pull/13635

The patches are already integrated on OCB.

* Make this work with product with more than 1 attribute.

Credits
=======

Contributors
------------

* Pedro M. Baeza <pedro.baeza@tecnativa.com>

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.
5 changes: 5 additions & 0 deletions stock_picking_variant_mgmt/__init__.py
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import models
from . import wizard
26 changes: 26 additions & 0 deletions stock_picking_variant_mgmt/__openerp__.py
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Pedro M. Baeza <pedro.baeza@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
'name': 'Handle easily multiple variants on Stock Pickings',
'summary': 'Handle the addition/removal of multiple variants and the '
'quantities transferred in the Pickings.',
'version': '9.0.1.0.0',
'author': 'Tecnativa,'
'Odoo Community Association (OCA)',
'category': 'Inventory, Logistics, Warehousing',
'license': 'AGPL-3',
'website': 'https://www.tecnativa.com',
'depends': [
'stock',
'web_widget_x2many_2d_matrix',
],
'demo': [],
'data': [
'wizard/stock_manage_variant_view.xml',
'wizard/stock_transfer_manage_variant_view.xml',
'views/stock_picking_view.xml',
],
'installable': True,
}
4 changes: 4 additions & 0 deletions stock_picking_variant_mgmt/models/__init__.py
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import stock_move
15 changes: 15 additions & 0 deletions stock_picking_variant_mgmt/models/stock_move.py
@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Pedro M. Baeza <pedro.baeza@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from openerp import models, fields


class StockMove(models.Model):
_inherit = 'stock.move'

product_attribute_value_ids = fields.Many2many(
comodel_name='product.attribute.value',
related="product_id.attribute_value_ids",
readonly=True,
)
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions stock_picking_variant_mgmt/tests/__init__.py
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import test_stock_picking_variant_mgmt
161 changes: 161 additions & 0 deletions stock_picking_variant_mgmt/tests/test_stock_picking_variant_mgmt.py
@@ -0,0 +1,161 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Pedro M. Baeza <pedro.baeza@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from openerp.tests import common


@common.at_install(False)
@common.post_install(True)
class TestStockPickingVariantMgmt(common.SavepointCase):
@classmethod
def setUpClass(cls):
super(TestStockPickingVariantMgmt, cls).setUpClass()
cls.partner = cls.env['res.partner'].create({'name': 'Test partner'})
cls.attribute1 = cls.env['product.attribute'].create({
'name': 'Test Attribute 1',
'value_ids': [
(0, 0, {'name': 'Value 1'}),
(0, 0, {'name': 'Value 2'}),
(0, 0, {'name': 'Value 3'}),
(0, 0, {'name': 'Value 4'}),
],
})
cls.product_tmpl = cls.env['product.template'].create({
'name': 'Test template',
'attribute_line_ids': [
(0, 0, {
'attribute_id': cls.attribute1.id,
'value_ids': [(6, 0, cls.attribute1.value_ids.ids)],
}),
],
})
assert len(cls.product_tmpl.product_variant_ids) == 4
cls.warehouse = cls.env['stock.warehouse'].search([], limit=1)
cls.picking_type = cls.env['stock.picking.type'].search([
('warehouse_id', '=', cls.warehouse.id),
('code', '=', 'incoming'),
], limit=1)
cls.picking = cls.env['stock.picking'].create({
'partner_id': cls.partner.id,
'picking_type_id': cls.picking_type.id,
'location_id': cls.partner.property_stock_supplier.id,
'location_dest_id': cls.picking_type.default_location_dest_id.id,
})
cls.Move = cls.env['stock.move']
cls.product1 = cls.product_tmpl.product_variant_ids[0]
move_vals = cls.Move.onchange_product_id(
prod_id=cls.product1.id,
loc_id=cls.picking.location_id.id,
loc_dest_id=cls.picking.location_dest_id.id,
partner_id=cls.picking.partner_id.id,
).get('value', {})
move_vals.update({
'product_id': cls.product1.id,
'picking_id': cls.picking.id,
'product_uom_qty': 1,
})
cls.move1 = cls.Move.create(move_vals)
cls.product2 = cls.product_tmpl.product_variant_ids[1]
move_vals = cls.Move.onchange_product_id(
prod_id=cls.product2.id,
loc_id=cls.picking.location_id.id,
loc_dest_id=cls.picking.location_dest_id.id,
partner_id=cls.picking.partner_id.id,
).get('value', {})
move_vals.update({
'product_id': cls.product2.id,
'picking_id': cls.picking.id,
'product_uom_qty': 2,
})
cls.move2 = cls.Move.create(move_vals)
cls.Wizard = cls.env['stock.manage.variant'].with_context(
active_ids=cls.picking.ids, active_id=cls.picking.id,
active_model=cls.picking._name,
)
cls.product_single = cls.env['product.product'].create({
'name': 'Product without variants',
})
move_vals = cls.Move.onchange_product_id(
prod_id=cls.product_single.id,
loc_id=cls.picking.location_id.id,
loc_dest_id=cls.picking.location_dest_id.id,
partner_id=cls.picking.partner_id.id,
).get('value', {})
move_vals.update({
'product_id': cls.product_single.id,
'picking_id': cls.picking.id,
'product_uom_qty': 2,
})
cls.move3 = cls.Move.create(move_vals)

def test_add_variants(self):
self.move1.unlink()
self.move2.unlink()
self.move3.unlink()
wizard = self.Wizard.new({'product_tmpl_id': self.product_tmpl.id})
wizard._onchange_product_tmpl_id()
wizard = wizard.create(wizard._convert_to_write(wizard._cache))
self.assertEqual(len(wizard.variant_line_ids), 4)
wizard.variant_line_ids[0].product_uom_qty = 1
wizard.variant_line_ids[1].product_uom_qty = 2
wizard.variant_line_ids[2].product_uom_qty = 3
wizard.variant_line_ids[3].product_uom_qty = 4
wizard.button_transfer_to_picking()
self.assertEqual(len(self.picking.move_lines), 4,
"There should be 4 lines in the picking")
self.assertEqual(self.picking.move_lines[0].product_uom_qty, 1)
self.assertEqual(self.picking.move_lines[1].product_uom_qty, 2)
self.assertEqual(self.picking.move_lines[2].product_uom_qty, 3)
self.assertEqual(self.picking.move_lines[3].product_uom_qty, 4)

def test_modify_variants(self):
Wizard2 = self.Wizard.with_context(
default_product_tmpl_id=self.product_tmpl.id,
active_model='stock.move',
active_id=self.move1.id, active_ids=self.move1.ids
)
wizard = Wizard2.create({})
wizard._onchange_product_tmpl_id()
self.assertEqual(
len(wizard.variant_line_ids.filtered('product_uom_qty')), 2,
"There should be two fields with any quantity in the wizard."
)
wizard.variant_line_ids.filtered(
lambda x: x.product_id == self.product1).product_uom_qty = 0
wizard.variant_line_ids.filtered(
lambda x: x.product_id == self.product2).product_uom_qty = 10
wizard.button_transfer_to_picking()
self.assertFalse(self.move1.exists(), "Move not removed.")
self.assertEqual(
self.move2.product_uom_qty, 10, "Move not changed quantity.",
)

def test_variant_transfer(self):
self.picking.action_confirm()
self.picking.action_assign()
self.assertEqual(len(self.picking.pack_operation_ids), 3)
wizard = self.env['stock.transfer.manage.variant'].with_context(
active_model='stock.picking', active_ids=self.picking.ids,
active_id=self.picking.id,
).create({})
self.assertEqual(len(wizard.variant_line_ids), 6)
wizard.variant_line_ids.filtered(
lambda x: x.product_id == self.product1
).qty_done = 1
wizard.variant_line_ids.filtered(
lambda x: x.product_id == self.product2
).qty_done = 2
wizard.variant_line_ids.filtered(
lambda x: x.product_id == self.product_single
).qty_done = 3
wizard.button_transfer_to_picking()
self.assertEqual(self.picking.pack_operation_ids.filtered(
lambda x: x.product_id == self.product1
).qty_done, 1)
self.assertEqual(self.picking.pack_operation_ids.filtered(
lambda x: x.product_id == self.product2
).qty_done, 2)
self.assertEqual(self.picking.pack_operation_ids.filtered(
lambda x: x.product_id == self.product_single
).qty_done, 3)
51 changes: 51 additions & 0 deletions stock_picking_variant_mgmt/views/stock_picking_view.xml
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2017 Pedro M. Baeza <pedro.baeza@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>

<record id="view_picking_form" model="ir.ui.view">
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='pack_operation_product_ids']" position="before">
<div class="oe_button_box" name="button_box_transfer_variant_mgmt">
<button name="%(action_stock_transfer_manage_variant)d"
type="action"
string="Manage Variants Transfer"
/>
</div>
</xpath>
<xpath expr="//field[@name='move_lines']" position="before">
<div class="oe_button_box" name="button_box_variant_mgmt">
<button name="%(action_stock_manage_variant)d"
type="action"
string="Add or Modify Variants"
states="draft,confirmed,assigned"
/>
</div>
</xpath>
</field>
</record>

<record id="view_move_picking_tree" model="ir.ui.view">
<field name="model">stock.move</field>
<field name="inherit_id" ref="stock.view_move_picking_tree"/>
<field name="arch" type="xml">
<field name="state" position="after">
<field name="product_tmpl_id" invisible="1"/>
<field name="product_attribute_value_ids" invisible="1"/>
<!-- Not working until https://github.com/odoo/odoo/pull/13558 -->
<!-- Also https://github.com/odoo/odoo/pull/13635 is needed for correct template selection -->
<button name="%(action_stock_manage_variant)d"
type="action"
string="Modify Variants"
icon="fa-th"
class="oe_edit_only"
context="{'default_product_tmpl_id': product_tmpl_id}"
attrs="{'invisible': ['|', ('state', 'not in', ('draft', 'confirmed', 'assigned')), ('product_attribute_value_ids', '=', [])]}"
/>
</field>
</field>
</record>

</odoo>
5 changes: 5 additions & 0 deletions stock_picking_variant_mgmt/wizard/__init__.py
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import stock_manage_variant
from . import stock_transfer_manage_variant

0 comments on commit 99d486e

Please sign in to comment.