Skip to content

Commit

Permalink
[IMP] Adds the glue which fill in the product packaging in stock
Browse files Browse the repository at this point in the history
When a sale order is confirmed and product packaging is filled in
sale order line(s), the information is propagated through
procurement orders and stock moves.
This is conditioned by a parameter on procurement rule.
  • Loading branch information
rousseldenis committed Aug 29, 2017
1 parent 487db73 commit 01fdd86
Show file tree
Hide file tree
Showing 15 changed files with 430 additions and 68 deletions.
46 changes: 24 additions & 22 deletions packaging_uom/models/product_packaging.py
Expand Up @@ -34,42 +34,44 @@ def _default_uom_categ_domain_id(self):
readonly=True
)

@api.one
@api.multi
@api.depends('uom_id', 'product_tmpl_id.uom_id')
def _compute_qty(self):
"""
Compute the quantity by package based on uom
"""
if self.uom_id and self.product_tmpl_id:
self.qty = self.uom_id._compute_quantity(
1, to_unit=self.product_tmpl_id.uom_id)
else:
self.qty = 0
for packaging in self:
if packaging.uom_id and packaging.product_tmpl_id:
packaging.qty = packaging.uom_id._compute_quantity(
1, to_unit=packaging.product_tmpl_id.uom_id)
else:
packaging.qty = 0

@api.one
@api.multi
def _inverse_qty(self):
"""
The inverse method is defined to make the code compatible with
existing modules and to not break tests...
:return:
"""
category_id = self.product_tmpl_id.uom_id.category_id
uom_id = self.uom_id.search([
("factor", "=", 1.0 / self.qty),
('category_id', '=', category_id.id)])
if not uom_id:
uom_id = self.uom_id .create({
'name': "%s %s" % (category_id.name, self.qty),
'category_id': category_id.id,
'rounding': self.product_tmpl_id.uom_id.rounding,
'uom_type': 'bigger',
'factor_inv': self.qty,
'active': True
})
self.uom_id = uom_id
for packaging in self:
category_id = packaging.product_tmpl_id.uom_id.category_id
uom_id = packaging.uom_id.search([
("factor", "=", 1.0 / self.qty),
('category_id', '=', category_id.id)])
if not uom_id:
uom_id = packaging.uom_id.create({
'name': "%s %s" % (category_id.name, packaging.qty),
'category_id': category_id.id,
'rounding': packaging.product_tmpl_id.uom_id.rounding,
'uom_type': 'bigger',
'factor_inv': packaging.qty,
'active': True
})
packaging.uom_id = uom_id

@api.multi
@api.constrains
@api.constrains('uom_id')
def _check_uom_id(self):
""" Check uom_id is not null
Expand Down
66 changes: 54 additions & 12 deletions packaging_uom/tests/test_product_packaging.py
Expand Up @@ -2,6 +2,7 @@
# Copyright 2015-2017 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import odoo.tests.common as common
from odoo.exceptions import ValidationError


class TestProductPackaging(common.TransactionCase):
Expand All @@ -10,12 +11,13 @@ def setUp(self):
super(TestProductPackaging, self).setUp()
self.uom_unit = self.env.ref('product.product_uom_unit')
self.uom_dozen = self.env.ref('product.product_uom_dozen')
self.product_tmpl_dozen = self.env[
'product.template'].new(
{'uom_id': self.uom_dozen})
self.product_tmpl_unit = self.env[
'product.template'].new(
{'uom_id': self.uom_unit})
self.product_tmpl_dozen = self.env['product.template'].create(
{'name': 'PRODUCT DOZEN',
'uom_id': self.uom_dozen.id})
self.product_tmpl_unit = self.env['product.template'].create(
{'name': 'PRODUCT UNIT',
'uom_id': self.uom_unit.id}
)

def test_compute_quantity_by_package(self):
""" Create a packagings with uom product_uom_dozen on
Expand All @@ -41,14 +43,22 @@ def test_compute_quantity_by_package(self):
"""

packaging_obj = self.env['product.packaging']
product_packaging_dozen = packaging_obj.new(
{'product_tmpl_id': self.product_tmpl_dozen,
'uom_id': self.uom_dozen})
product_packaging_dozen = packaging_obj.create(
{'name': 'PACKAGING 1',
'product_tmpl_id': self.product_tmpl_dozen.id,
'uom_id': self.uom_dozen.id})
self.assertAlmostEqual(product_packaging_dozen.qty, 1)
product_packaging_unit = packaging_obj.new(
{'product_tmpl_id': self.product_tmpl_unit,
'uom_id': self.uom_dozen})
product_packaging_unit = packaging_obj.with_context(
get_uom_categ_from_uom=self.uom_dozen.category_id.id).create(
{'name': 'PACKAGING 2',
'product_tmpl_id': self.product_tmpl_unit.id,
'uom_id': self.uom_dozen.id})
self.assertAlmostEqual(product_packaging_unit.qty, 12)
self.assertEqual(
self.uom_dozen.category_id,
product_packaging_unit.uom_categ_domain_id,
'The UOM domain is not well set'
)
product_uom_24 = self.env['product.uom'].create(
{'category_id': self.env.ref('product.product_uom_categ_unit').id,
'name': 'Double Dozens',
Expand All @@ -69,3 +79,35 @@ def test_compute_quantity_by_package(self):
self.assertAlmostEqual(product_packaging_dozen.qty, 2)
product_packaging_unit.uom_id = product_uom_6
self.assertAlmostEqual(product_packaging_unit.qty, 6)
# Set Packaging Quantity
product_packaging_dozen.qty = 1
self.assertEquals(
self.uom_unit,
product_packaging_dozen.uom_id
)
# Try to set null on uom
with self.assertRaises(ValidationError):
product_packaging_dozen.uom_id = None

# Define a new packaging unit
uom_524 = self.env['product.uom'].search([
('category_id', '=',
product_packaging_dozen.product_tmpl_id.uom_id.category_id.id),
('name',
'=',
'%s %s' %
(product_packaging_dozen.product_tmpl_id.uom_id.category_id.name,
float(524)))
])
self.assertEqual(0, len(uom_524))
product_packaging_dozen.qty = 524
uom_524 = self.env['product.uom'].search([
('category_id', '=',
product_packaging_dozen.product_tmpl_id.uom_id.category_id.id),
('name',
'=',
'%s %s' %
(product_packaging_dozen.product_tmpl_id.uom_id.category_id.name,
float(524)))
])
self.assertEqual(1, len(uom_524))
4 changes: 2 additions & 2 deletions purchase_packaging/models/procurement_order.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright 2015-2017 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, models
from odoo import api, fields, models


class ProcurementOrder(models.Model):
Expand All @@ -17,7 +17,7 @@ def _prepare_purchase_order_line(self, po, supplier):
seller = self.product_id._select_seller(
partner_id=supplier.name,
quantity=res['product_qty'],
date=po.date_order and po.date_order[:10],
date=po.date_order and fields.Date.from_string(po.date_order),
uom_id=self.product_id.uom_po_id)
if seller.packaging_id:
res['packaging_id'] = seller.packaging_id.id
Expand Down
61 changes: 33 additions & 28 deletions purchase_packaging/models/purchase_order_line.py
Expand Up @@ -43,44 +43,49 @@ def _get_product_seller(self):
return self.product_id._select_seller(
partner_id=self.order_id.partner_id,
quantity=self.product_qty,
date=self.order_id.date_order and self.order_id.date_order[:10],
date=self.order_id.date_order and
fields.Date.from_string(self.order_id.date_order),
uom_id=self.product_uom)

@api.one
@api.multi
@api.depends('product_purchase_uom_id', 'product_purchase_qty')
def _compute_product_qty(self):
"""
Compute the total quantity
"""
uom_obj = self.env['product.uom']
to_uom = uom_obj.search(
[('category_id', '=', self.product_purchase_uom_id.category_id.id),
('uom_type', '=', 'reference')], limit=1)
if not self.product_purchase_uom_id:
return
self.product_qty = self.product_purchase_uom_id._compute_quantity(
self.product_purchase_qty,
to_uom)
for line in self:
uom_obj = self.env['product.uom']
to_uom = uom_obj.search(
[('category_id',
'=',
line.product_purchase_uom_id.category_id.id),
('uom_type', '=', 'reference')], limit=1)
if not line.product_purchase_uom_id:
return
line.product_qty = line.product_purchase_uom_id._compute_quantity(
line.product_purchase_qty,
to_uom)

@api.one
@api.multi
def _inverse_product_qty(self):
""" If product_quantity is set compute the purchase_qty
"""
if self.product_id:
supplier = self._get_product_seller()
if supplier:
product_purchase_uom = supplier.min_qty_uom_id
uom_obj = self.env['product.uom']
from_uom = uom_obj.search(
[('category_id', '=',
product_purchase_uom.category_id.id),
('uom_type', '=', 'reference')], limit=1)
self.product_purchase_qty = from_uom._compute_quantity(
self.product_qty,
product_purchase_uom)
self.product_purchase_uom_id = product_purchase_uom.id
else:
self.product_purchase_qty = self.product_qty
for line in self:
if line.product_id:
supplier = line._get_product_seller()
if supplier:
product_purchase_uom = supplier.min_qty_uom_id
uom_obj = self.env['product.uom']
from_uom = uom_obj.search(
[('category_id', '=',
product_purchase_uom.category_id.id),
('uom_type', '=', 'reference')], limit=1)
line.product_purchase_qty = from_uom._compute_quantity(
line.product_qty,
product_purchase_uom)
line.product_purchase_uom_id = product_purchase_uom.id
else:
line.product_purchase_qty = line.product_qty

@api.onchange("packaging_id")
def _onchange_packaging_id(self):
Expand Down Expand Up @@ -136,7 +141,7 @@ def onchange_product_id(self):
if res.get('domain'):
res['domain'].update(domain)
else:
res['domain'] = domain
res['domain'] = domain # pragma: no cover not aware of super
return res

@api.multi
Expand Down
56 changes: 56 additions & 0 deletions purchase_packaging/tests/test_purchase_order_line.py
Expand Up @@ -22,6 +22,11 @@ def setUp(self):
'uom_id': self.env.ref('product.product_uom_dozen').id,
'name': 'Packaging Dozen'}
)
self.product_packaging_unit = self.env['product.packaging'].create(
{'product_tmpl_id': self.product_tmpl_id.id,
'uom_id': self.env.ref('product.product_uom_unit').id,
'name': 'Packaging Unit'}
)
self.product_uom_8 = self.env['product.uom'].create(
{'category_id': self.env.ref('product.product_uom_categ_unit').id,
'name': 'COL8',
Expand Down Expand Up @@ -79,3 +84,54 @@ def test_po_line(self):
self.assertEqual(sm.product_uom.id,
self.env.ref('product.product_uom_dozen').id)
self.assertAlmostEqual(sm.product_uom_qty, 16)

def test_po_line_no_product(self):
self.product_supplier_info.min_qty_uom_id = self.product_uom_8
self.product_supplier_info.min_qty = 2
self.product_supplier_info.packaging_id = self.product_packaging_dozen

po = self.env['purchase.order'].create(
{'partner_id': self.product_supplier_info.name.id})
po_line = po.order_line.new({
'product_id': self.product_tmpl_id.product_variant_id,
'product_purchase_qty': 1.0,
'product_purchase_uom_id':
po.order_line._default_product_purchase_uom_id(),
'order_id': po
})
po_line.onchange_product_id()
po_line.product_id = None
po_line.product_qty = 2.0
self.assertEqual(
2.0,
po_line.product_purchase_qty,
'The purchase quantity is not well set'
)

def test_po_line_change_packaging(self):
self.product_supplier_info.min_qty_uom_id = self.product_uom_8
self.product_supplier_info.min_qty = 2
self.product_supplier_info.packaging_id = self.product_packaging_dozen

po = self.env['purchase.order'].create(
{'partner_id': self.product_supplier_info.name.id})
po_line = po.order_line.new({
'product_id': self.product_tmpl_id.product_variant_id,
'product_purchase_qty': 1.0,
'product_purchase_uom_id':
po.order_line._default_product_purchase_uom_id(),
'order_id': po
})
po_line.onchange_product_id()
self.assertEquals(
self.product_packaging_dozen.uom_id,
po_line.product_uom,
'The UOM Unit is not well set'
)
po_line.packaging_id = self.product_packaging_unit
po_line._onchange_packaging_id()
self.assertEquals(
self.product_packaging_unit.uom_id,
po_line.product_uom,
'The product uom is not well set'
)
9 changes: 7 additions & 2 deletions purchase_packaging/views/product_supplier_info_view.xml
Expand Up @@ -22,10 +22,15 @@
<field name="inherit_id" ref="product.product_supplierinfo_form_view"/>
<field name="arch" type="xml">
<field name="min_qty" position="after">
<field name="min_qty_uom_id" groups="product.group_uom"/>
<span> x <field name="min_qty_uom_id" groups="product.group_uom"/></span>
</field>
<field name="product_uom" position="before">
<span> of </span>
</field>
<field name="product_uom" position="after">
<field name="packaging_id" domain="[('product_tmpl_id', '=', product_tmpl_id)]" groups="product.group_stock_packaging"/>
<div>
using package <field name="packaging_id" domain="[('product_tmpl_id', '=', product_tmpl_id)]" groups="product.group_stock_packaging"/>
</div>
</field>
<field name="delay" position="after">
<field name="product_tmpl_id" invisible="1" required="False"/>
Expand Down
3 changes: 2 additions & 1 deletion sale_packaging/__manifest__.py
Expand Up @@ -13,7 +13,8 @@
"depends": ["sale_stock",
"packaging_uom",
],
"data": ["views/sale_order_line_views.xml",
"data": ["views/procurement_rule.xml",
"views/sale_order_line_views.xml",
],
"license": "AGPL-3",
"installable": True,
Expand Down
3 changes: 3 additions & 0 deletions sale_packaging/models/__init__.py
Expand Up @@ -3,3 +3,6 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import sale_order_line
from . import procurement_order
from . import procurement_rule
from . import stock_move
21 changes: 21 additions & 0 deletions sale_packaging/models/procurement_order.py
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Copyright 2017 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import fields, models


class ProcurementOrder(models.Model):

_inherit = 'procurement.order'

product_packaging = fields.Many2one(
'product.packaging',
string='Packaging',
default=False)

def _get_stock_move_values(self):
vals = super(ProcurementOrder, self)._get_stock_move_values()
if self.product_packaging:
vals.update({'product_packaging': self.product_packaging.id})
return vals

0 comments on commit 01fdd86

Please sign in to comment.