forked from OCA/product-variant
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ADD] purchase_order_variant_mgmt (OCA#288)
================================================== Handle easily multiple variants on Purchase Orders ================================================== This module allows to add/modify all the variants of a product in a direct screen without the need of handling them one by one. Configuration ============= * Configure your user to have any permission from "Purchases" group. * Create a product with 2 attributes and several values. Usage ===== * Go to Purchases > Purchase > Requests for Quotation * Create a new quotation or edit an existing one. * Press "Add variants" button located in the upper right corner of the "Order Lines" 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 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 order" * Order 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. 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#13558 * odoo/odoo#13635 The problems are already fixed in OCB.
- Loading branch information
1 parent
17f6f3b
commit 276c148
Showing
12 changed files
with
431 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
.. 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 Purchase Orders | ||
================================================== | ||
|
||
This module allows to add/modify all the variants of a product in a direct | ||
screen without the need of handling them one by one. | ||
|
||
Configuration | ||
============= | ||
|
||
#. Configure your user to have any permission from "Purchases" group. | ||
#. Create a product with 2 attributes and several values. | ||
|
||
Usage | ||
===== | ||
|
||
#. Go to Purchases > Purchase > Requests for Quotation | ||
#. Create a new quotation or edit an existing one. | ||
#. Press "Add variants" button located in the upper right corner of the | ||
"Order Lines" 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 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 | ||
order" | ||
#. Order 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. | ||
|
||
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas | ||
:alt: Try me on Runbot | ||
:target: https://runbot.odoo-community.org/runbot/142/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/13558 | ||
* https://github.com/odoo/odoo/pull/13635 | ||
|
||
The problems are already fixed in OCB. | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# -*- coding: utf-8 -*- | ||
# Copyright 2016 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 Purchase Orders', | ||
'summary': 'Handle the addition/removal of multiple variants from ' | ||
'product template into the purchase order', | ||
'version': '9.0.1.0.0', | ||
'author': 'Tecnativa,' | ||
'Odoo Community Association (OCA)', | ||
'category': 'Purchases', | ||
'license': 'AGPL-3', | ||
'website': 'https://www.tecnativa.com', | ||
'depends': [ | ||
'purchase', | ||
'web_widget_x2many_2d_matrix', | ||
], | ||
'demo': [], | ||
'data': [ | ||
'wizard/purchase_manage_variant_view.xml', | ||
'views/purchase_order_view.xml', | ||
], | ||
'installable': True, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# -*- coding: utf-8 -*- | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
from . import purchase_order |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# -*- coding: utf-8 -*- | ||
# Copyright 2016 Pedro M. Baeza <pedro.baeza@tecnativa.com> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
from openerp import fields, models | ||
|
||
|
||
class PurchaseOrderLine(models.Model): | ||
_inherit = 'purchase.order.line' | ||
|
||
# These field names are for avoiding conflicts with any other field with | ||
# the same name declared by other modules and that can be a no related one | ||
product_tmpl_id_purchase_order_variant_mgmt = fields.Many2one( | ||
comodel_name="product.template", related="product_id.product_tmpl_id") | ||
state_purchase_order_variant_mgmt = fields.Selection( | ||
related="order_id.state") | ||
product_attribute_value_ids = fields.Many2many( | ||
comodel_name='product.attribute.value', | ||
related="product_id.attribute_value_ids") |
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# -*- coding: utf-8 -*- | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
from . import test_purchase_order_variant_mgmt |
102 changes: 102 additions & 0 deletions
102
purchase_order_variant_mgmt/tests/test_purchase_order_variant_mgmt.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
# -*- coding: utf-8 -*- | ||
# Copyright 2016 Pedro M. Baeza <pedro.baeza@tecnativa.com> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
from openerp.tests import common | ||
|
||
|
||
class TestPurchaseOrderVariantMgmt(common.SavepointCase): | ||
@classmethod | ||
def setUpClass(cls): | ||
super(TestPurchaseOrderVariantMgmt, 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'}), | ||
], | ||
}) | ||
cls.attribute2 = cls.env['product.attribute'].create({ | ||
'name': 'Test Attribute 2', | ||
'value_ids': [ | ||
(0, 0, {'name': 'Value X'}), | ||
(0, 0, {'name': 'Value Y'}), | ||
], | ||
}) | ||
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)], | ||
}), | ||
(0, 0, { | ||
'attribute_id': cls.attribute2.id, | ||
'value_ids': [(6, 0, cls.attribute2.value_ids.ids)], | ||
}), | ||
], | ||
}) | ||
assert len(cls.product_tmpl.product_variant_ids) == 4 | ||
order = cls.env['purchase.order'].new({'partner_id': cls.partner.id}) | ||
order.onchange_partner_id() | ||
cls.order = order.create(order._convert_to_write(order._cache)) | ||
cls.Wizard = cls.env['purchase.manage.variant'].with_context( | ||
active_ids=cls.order.ids, active_id=cls.order.id, | ||
active_model=cls.order._name | ||
) | ||
cls.PurchaseOrderLine = cls.env['purchase.order.line'] | ||
|
||
def test_add_variants(self): | ||
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_order() | ||
self.assertEqual(len(self.order.order_line), 4, | ||
"There should be 4 lines in the sale order") | ||
|
||
def test_modify_variants(self): | ||
product1 = self.product_tmpl.product_variant_ids[0] | ||
order_line1 = self.PurchaseOrderLine.new({ | ||
'order_id': self.order.id, | ||
'product_id': product1.id, | ||
}) | ||
order_line1.onchange_product_id() | ||
order_line1.product_qty = 1 | ||
order_line1._onchange_quantity() | ||
product2 = self.product_tmpl.product_variant_ids[1] | ||
order_line1 = self.PurchaseOrderLine.create( | ||
order_line1._convert_to_write(order_line1._cache)) | ||
order_line2 = self.PurchaseOrderLine.new({ | ||
'order_id': self.order.id, | ||
'product_id': product2.id, | ||
}) | ||
order_line2.onchange_product_id() | ||
order_line1.product_qty = 2 | ||
order_line2._onchange_quantity() | ||
order_line2 = self.PurchaseOrderLine.create( | ||
order_line2._convert_to_write(order_line2._cache)) | ||
Wizard2 = self.Wizard.with_context( | ||
default_product_tmpl_id=self.product_tmpl.id, | ||
active_model='purchase.order.line', | ||
active_id=order_line1.id, active_ids=order_line1.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 == product1).product_uom_qty = 0 | ||
wizard.variant_line_ids.filtered( | ||
lambda x: x.product_id == product2).product_uom_qty = 10 | ||
wizard.button_transfer_to_order() | ||
self.assertFalse(order_line1.exists(), "Order line not removed.") | ||
self.assertEqual( | ||
order_line2.product_qty, 10, "Order line quantity not changed.") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<!-- Copyright 2016 Pedro M. Baeza <pedro.baeza@tecnativa.com> | ||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> | ||
<odoo> | ||
|
||
<record id="purchase_order_form" model="ir.ui.view"> | ||
<field name="model">purchase.order</field> | ||
<field name="inherit_id" ref="purchase.purchase_order_form"/> | ||
<field name="arch" type="xml"> | ||
<xpath expr="//field[@name='order_line']" position="before"> | ||
<div class="oe_button_box" name="button_box"> | ||
<button name="%(action_purchase_manage_variant)d" | ||
type="action" | ||
string="Add or Modify Variants" | ||
class="oe_edit_only" | ||
states="draft,sent" | ||
/> | ||
</div> | ||
</xpath> | ||
<xpath expr="//field[@name='order_line']//tree" position="inside"> | ||
<field name="product_tmpl_id_purchase_order_variant_mgmt" invisible="1"/> | ||
<field name="state_purchase_order_variant_mgmt" 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_purchase_manage_variant)d" | ||
type="action" | ||
string="Modify Variants" | ||
icon="fa-th" | ||
class="oe_edit_only" | ||
context="{'default_product_tmpl_id': product_tmpl_id_purchase_order_variant_mgmt}" | ||
attrs="{'invisible': ['|', ('state_purchase_order_variant_mgmt', 'not in', ('draft', 'sent')), ('product_attribute_value_ids', '=', [])]}" | ||
/> | ||
</xpath> | ||
<xpath expr="//field[@name='order_line']" position="attributes"> | ||
<attribute name="options">{'reload_on_button': true}</attribute> | ||
</xpath> | ||
</field> | ||
</record> | ||
|
||
</odoo> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# -*- coding: utf-8 -*- | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
from . import purchase_manage_variant |
103 changes: 103 additions & 0 deletions
103
purchase_order_variant_mgmt/wizard/purchase_manage_variant.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
# -*- coding: utf-8 -*- | ||
# Copyright 2016 Pedro M. Baeza <pedro.baeza@tecnativa.com> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
import openerp.addons.decimal_precision as dp | ||
from openerp import api, fields, models | ||
|
||
|
||
class PurchaseManageVariant(models.TransientModel): | ||
_name = 'purchase.manage.variant' | ||
|
||
product_tmpl_id = fields.Many2one( | ||
comodel_name='product.template', string="Template", required=True) | ||
# This is a many2many because Odoo fails to fill one2many in onchanges | ||
variant_line_ids = fields.Many2many( | ||
comodel_name='purchase.manage.variant.line', string="Variant Lines") | ||
|
||
# HACK: https://github.com/OCA/server-tools/pull/492#issuecomment-237594285 | ||
@api.multi | ||
def onchange(self, values, field_name, field_onchange): # pragma: no cover | ||
if "variant_line_ids" in field_onchange: | ||
for sub in ("product_id", "disabled", "value_x", "value_y", | ||
"product_uom_qty"): | ||
field_onchange.setdefault("variant_line_ids." + sub, u"") | ||
return super(PurchaseManageVariant, self).onchange( | ||
values, field_name, field_onchange) | ||
|
||
@api.onchange('product_tmpl_id') | ||
def _onchange_product_tmpl_id(self): | ||
self.variant_line_ids = [(6, 0, [])] | ||
template = self.product_tmpl_id | ||
context = self.env.context | ||
record = self.env[context['active_model']].browse( | ||
context['active_id']) | ||
if context['active_model'] == 'purchase.order.line': | ||
purchase_order = record.order_id | ||
else: | ||
purchase_order = record | ||
if template and len(template.attribute_line_ids) >= 2: | ||
line_x = template.attribute_line_ids[0] | ||
line_y = template.attribute_line_ids[1] | ||
lines = [] | ||
for value_x in line_x.value_ids: | ||
for value_y in line_y.value_ids: | ||
# Filter the corresponding product for that values | ||
product = template.product_variant_ids.filtered( | ||
lambda x: (value_x in x.attribute_value_ids and | ||
value_y in x.attribute_value_ids)) | ||
order_line = purchase_order.order_line.filtered( | ||
lambda x: x.product_id == product) | ||
lines.append((0, 0, { | ||
'product_id': product, | ||
'disabled': not bool(product), | ||
'value_x': value_x, | ||
'value_y': value_y, | ||
'product_uom_qty': order_line.product_qty, | ||
})) | ||
self.variant_line_ids = lines | ||
|
||
@api.multi | ||
def button_transfer_to_order(self): | ||
context = self.env.context | ||
record = self.env[context['active_model']].browse(context['active_id']) | ||
if context['active_model'] == 'purchase.order.line': | ||
purchase_order = record.order_id | ||
else: | ||
purchase_order = record | ||
OrderLine = self.env['purchase.order.line'] | ||
lines2unlink = OrderLine | ||
for line in self.variant_line_ids: | ||
order_line = purchase_order.order_line.filtered( | ||
lambda x: x.product_id == line.product_id) | ||
if order_line: | ||
if not line.product_uom_qty: | ||
# Done this way because there's a side effect removing here | ||
lines2unlink |= order_line | ||
else: | ||
order_line.product_qty = line.product_uom_qty | ||
elif line.product_uom_qty: | ||
order_line = OrderLine.new({ | ||
'product_id': line.product_id.id, | ||
'order_id': purchase_order.id, | ||
}) | ||
order_line.onchange_product_id() | ||
# This should be done later for handling supplier quantities | ||
order_line.product_qty = line.product_uom_qty | ||
order_line._onchange_quantity() | ||
order_line_vals = order_line._convert_to_write( | ||
order_line._cache) | ||
purchase_order.order_line.create(order_line_vals) | ||
lines2unlink.unlink() | ||
|
||
|
||
class PurchaseManageVariantLine(models.TransientModel): | ||
_name = 'purchase.manage.variant.line' | ||
|
||
product_id = fields.Many2one( | ||
comodel_name='product.product', string="Variant", readonly=True) | ||
disabled = fields.Boolean() | ||
value_x = fields.Many2one(comodel_name='product.attribute.value') | ||
value_y = fields.Many2one(comodel_name='product.attribute.value') | ||
product_uom_qty = fields.Float( | ||
string="Quantity", digits_compute=dp.get_precision('Product UoS')) |
Oops, something went wrong.