mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
Initial commit of product_catch_weight for 11.0
This commit is contained in:
1
product_catch_weight/__init__.py
Normal file
1
product_catch_weight/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import models
|
||||||
19
product_catch_weight/__manifest__.py
Normal file
19
product_catch_weight/__manifest__.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
'name': 'Product Catch Weight',
|
||||||
|
'version': '11.0.1.0.0',
|
||||||
|
'category': 'Warehouse',
|
||||||
|
'depends': [
|
||||||
|
'sale_stock',
|
||||||
|
'purchase',
|
||||||
|
],
|
||||||
|
'description': """
|
||||||
|
""",
|
||||||
|
'author': 'Hibou Corp.',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'website': 'https://hibou.io/',
|
||||||
|
'data': [
|
||||||
|
'views/stock_views.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
'application': False,
|
||||||
|
}
|
||||||
3
product_catch_weight/models/__init__.py
Normal file
3
product_catch_weight/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from . import account_invoice
|
||||||
|
from . import stock_patch
|
||||||
|
from . import stock
|
||||||
42
product_catch_weight/models/account_invoice.py
Normal file
42
product_catch_weight/models/account_invoice.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
from odoo import api, fields, models
|
||||||
|
import logging
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AccountInvoiceLine(models.Model):
|
||||||
|
_inherit = 'account.invoice.line'
|
||||||
|
|
||||||
|
@api.one
|
||||||
|
@api.depends('price_unit', 'discount', 'invoice_line_tax_ids', 'quantity',
|
||||||
|
'product_id', 'invoice_id.partner_id', 'invoice_id.currency_id', 'invoice_id.company_id',
|
||||||
|
'invoice_id.date_invoice', 'invoice_id.date')
|
||||||
|
def _compute_price(self):
|
||||||
|
currency = self.invoice_id and self.invoice_id.currency_id or None
|
||||||
|
price = self.price_unit * (1 - (self.discount or 0.0) / 100.0)
|
||||||
|
|
||||||
|
ratio = 1.0
|
||||||
|
qty_done_total = 0.0
|
||||||
|
if self.invoice_id.type in ('out_invoice', 'out_refund'):
|
||||||
|
move_lines = self.sale_line_ids.mapped('move_ids.move_line_ids')
|
||||||
|
else:
|
||||||
|
move_lines = self.purchase_line_id.mapped('move_ids.move_line_ids')
|
||||||
|
for move_line in move_lines:
|
||||||
|
qty_done = move_line.qty_done
|
||||||
|
r = move_line.lot_id.catch_weight_ratio
|
||||||
|
ratio = ((ratio * qty_done_total) + (qty_done * r)) / (qty_done + qty_done_total)
|
||||||
|
qty_done_total += qty_done
|
||||||
|
price = price * ratio
|
||||||
|
|
||||||
|
taxes = False
|
||||||
|
if self.invoice_line_tax_ids:
|
||||||
|
taxes = self.invoice_line_tax_ids.compute_all(price, currency, self.quantity, product=self.product_id,
|
||||||
|
partner=self.invoice_id.partner_id)
|
||||||
|
self.price_subtotal = price_subtotal_signed = taxes['total_excluded'] if taxes else self.quantity * price
|
||||||
|
self.price_total = taxes['total_included'] if taxes else self.price_subtotal
|
||||||
|
if self.invoice_id.currency_id and self.invoice_id.currency_id != self.invoice_id.company_id.currency_id:
|
||||||
|
price_subtotal_signed = self.invoice_id.currency_id.with_context(
|
||||||
|
date=self.invoice_id._get_currency_rate_date()).compute(price_subtotal_signed,
|
||||||
|
self.invoice_id.company_id.currency_id)
|
||||||
|
sign = self.invoice_id.type in ['in_refund', 'out_refund'] and -1 or 1
|
||||||
|
self.price_subtotal_signed = price_subtotal_signed * sign
|
||||||
20
product_catch_weight/models/stock.py
Normal file
20
product_catch_weight/models/stock.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class StockProductionLot(models.Model):
|
||||||
|
_inherit = 'stock.production.lot'
|
||||||
|
|
||||||
|
catch_weight_ratio = fields.Float(string='Catch Weight Ratio', digits=(10, 6), default=1.0)
|
||||||
|
|
||||||
|
|
||||||
|
class StockMoveLine(models.Model):
|
||||||
|
_inherit = 'stock.move.line'
|
||||||
|
|
||||||
|
lot_catch_weight_ratio = fields.Float(string='Catch Weight Ratio', digits=(10, 6), default=1.0)
|
||||||
|
lot_catch_weight_ratio_related = fields.Float(related='lot_id.catch_weight_ratio')
|
||||||
|
#lot_catch_weight_ratio = fields.Float(related='lot_id.catch_weight_ratio')
|
||||||
|
|
||||||
|
# def _action_done(self):
|
||||||
|
# super(StockMoveLine, self)._action_done()
|
||||||
|
# for ml in self.filtered(lambda l: l.product_id.tracking == 'serial' and l.lot_id):
|
||||||
|
# ml.lot_id.catch_weight_ratio = ml.lot_catch_weight_ratio
|
||||||
114
product_catch_weight/models/stock_patch.py
Normal file
114
product_catch_weight/models/stock_patch.py
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
from odoo import fields
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
from odoo.tools.float_utils import float_round, float_compare, float_is_zero
|
||||||
|
from odoo.addons.stock.models.stock_move_line import StockMoveLine
|
||||||
|
|
||||||
|
|
||||||
|
def _action_done(self):
|
||||||
|
""" This method is called during a move's `action_done`. It'll actually move a quant from
|
||||||
|
the source location to the destination location, and unreserve if needed in the source
|
||||||
|
location.
|
||||||
|
|
||||||
|
This method is intended to be called on all the move lines of a move. This method is not
|
||||||
|
intended to be called when editing a `done` move (that's what the override of `write` here
|
||||||
|
is done.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# First, we loop over all the move lines to do a preliminary check: `qty_done` should not
|
||||||
|
# be negative and, according to the presence of a picking type or a linked inventory
|
||||||
|
# adjustment, enforce some rules on the `lot_id` field. If `qty_done` is null, we unlink
|
||||||
|
# the line. It is mandatory in order to free the reservation and correctly apply
|
||||||
|
# `action_done` on the next move lines.
|
||||||
|
ml_to_delete = self.env['stock.move.line']
|
||||||
|
for ml in self:
|
||||||
|
# Check here if `ml.qty_done` respects the rounding of `ml.product_uom_id`.
|
||||||
|
uom_qty = float_round(ml.qty_done, precision_rounding=ml.product_uom_id.rounding, rounding_method='HALF-UP')
|
||||||
|
precision_digits = self.env['decimal.precision'].precision_get('Product Unit of Measure')
|
||||||
|
qty_done = float_round(ml.qty_done, precision_digits=precision_digits, rounding_method='HALF-UP')
|
||||||
|
if float_compare(uom_qty, qty_done, precision_digits=precision_digits) != 0:
|
||||||
|
raise UserError(_('The quantity done for the product "%s" doesn\'t respect the rounding precision \
|
||||||
|
defined on the unit of measure "%s". Please change the quantity done or the \
|
||||||
|
rounding precision of your unit of measure.') % (
|
||||||
|
ml.product_id.display_name, ml.product_uom_id.name))
|
||||||
|
|
||||||
|
qty_done_float_compared = float_compare(ml.qty_done, 0, precision_rounding=ml.product_uom_id.rounding)
|
||||||
|
if qty_done_float_compared > 0:
|
||||||
|
if ml.product_id.tracking != 'none':
|
||||||
|
picking_type_id = ml.move_id.picking_type_id
|
||||||
|
if picking_type_id:
|
||||||
|
if picking_type_id.use_create_lots:
|
||||||
|
# If a picking type is linked, we may have to create a production lot on
|
||||||
|
# the fly before assigning it to the move line if the user checked both
|
||||||
|
# `use_create_lots` and `use_existing_lots`.
|
||||||
|
if ml.lot_name and not ml.lot_id:
|
||||||
|
lot = self.env['stock.production.lot'].create(
|
||||||
|
{'name': ml.lot_name, 'product_id': ml.product_id.id, 'catch_weight_ratio': ml.lot_catch_weight_ratio}
|
||||||
|
)
|
||||||
|
ml.write({'lot_id': lot.id})
|
||||||
|
elif not picking_type_id.use_create_lots and not picking_type_id.use_existing_lots:
|
||||||
|
# If the user disabled both `use_create_lots` and `use_existing_lots`
|
||||||
|
# checkboxes on the picking type, he's allowed to enter tracked
|
||||||
|
# products without a `lot_id`.
|
||||||
|
continue
|
||||||
|
elif ml.move_id.inventory_id:
|
||||||
|
# If an inventory adjustment is linked, the user is allowed to enter
|
||||||
|
# tracked products without a `lot_id`.
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not ml.lot_id:
|
||||||
|
raise UserError(_('You need to supply a lot/serial number for %s.') % ml.product_id.name)
|
||||||
|
elif qty_done_float_compared < 0:
|
||||||
|
raise UserError(_('No negative quantities allowed'))
|
||||||
|
else:
|
||||||
|
ml_to_delete |= ml
|
||||||
|
ml_to_delete.unlink()
|
||||||
|
|
||||||
|
# Now, we can actually move the quant.
|
||||||
|
done_ml = self.env['stock.move.line']
|
||||||
|
for ml in self - ml_to_delete:
|
||||||
|
if ml.product_id.type == 'product':
|
||||||
|
Quant = self.env['stock.quant']
|
||||||
|
rounding = ml.product_uom_id.rounding
|
||||||
|
|
||||||
|
# if this move line is force assigned, unreserve elsewhere if needed
|
||||||
|
if not ml.location_id.should_bypass_reservation() and float_compare(ml.qty_done, ml.product_qty,
|
||||||
|
precision_rounding=rounding) > 0:
|
||||||
|
extra_qty = ml.qty_done - ml.product_qty
|
||||||
|
ml._free_reservation(ml.product_id, ml.location_id, extra_qty, lot_id=ml.lot_id,
|
||||||
|
package_id=ml.package_id, owner_id=ml.owner_id, ml_to_ignore=done_ml)
|
||||||
|
# unreserve what's been reserved
|
||||||
|
if not ml.location_id.should_bypass_reservation() and ml.product_id.type == 'product' and ml.product_qty:
|
||||||
|
try:
|
||||||
|
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id,
|
||||||
|
package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
|
||||||
|
except UserError:
|
||||||
|
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=False,
|
||||||
|
package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
|
||||||
|
|
||||||
|
# move what's been actually done
|
||||||
|
quantity = ml.product_uom_id._compute_quantity(ml.qty_done, ml.move_id.product_id.uom_id,
|
||||||
|
rounding_method='HALF-UP')
|
||||||
|
available_qty, in_date = Quant._update_available_quantity(ml.product_id, ml.location_id, -quantity,
|
||||||
|
lot_id=ml.lot_id, package_id=ml.package_id,
|
||||||
|
owner_id=ml.owner_id)
|
||||||
|
if available_qty < 0 and ml.lot_id:
|
||||||
|
# see if we can compensate the negative quants with some untracked quants
|
||||||
|
untracked_qty = Quant._get_available_quantity(ml.product_id, ml.location_id, lot_id=False,
|
||||||
|
package_id=ml.package_id, owner_id=ml.owner_id,
|
||||||
|
strict=True)
|
||||||
|
if untracked_qty:
|
||||||
|
taken_from_untracked_qty = min(untracked_qty, abs(quantity))
|
||||||
|
Quant._update_available_quantity(ml.product_id, ml.location_id, -taken_from_untracked_qty,
|
||||||
|
lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id)
|
||||||
|
Quant._update_available_quantity(ml.product_id, ml.location_id, taken_from_untracked_qty,
|
||||||
|
lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
|
||||||
|
Quant._update_available_quantity(ml.product_id, ml.location_dest_id, quantity, lot_id=ml.lot_id,
|
||||||
|
package_id=ml.result_package_id, owner_id=ml.owner_id, in_date=in_date)
|
||||||
|
done_ml |= ml
|
||||||
|
# Reset the reserved quantity as we just moved it to the destination location.
|
||||||
|
(self - ml_to_delete).with_context(bypass_reservation_update=True).write({
|
||||||
|
'product_uom_qty': 0.00,
|
||||||
|
'date': fields.Datetime.now(),
|
||||||
|
})
|
||||||
|
|
||||||
|
StockMoveLine._action_done = _action_done
|
||||||
1
product_catch_weight/tests/__init__.py
Normal file
1
product_catch_weight/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import test_catch_weight
|
||||||
152
product_catch_weight/tests/test_catch_weight.py
Normal file
152
product_catch_weight/tests/test_catch_weight.py
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import logging
|
||||||
|
# from odoo.addons.stock.tests.test_move2 import TestPickShip
|
||||||
|
from odoo import fields
|
||||||
|
from odoo.tests.common import TransactionCase
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPicking(TransactionCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestPicking, self).setUp()
|
||||||
|
self.partner1 = self.env.ref('base.res_partner_2')
|
||||||
|
self.product1 = self.env['product.product'].create({
|
||||||
|
'name': 'Product 1',
|
||||||
|
'type': 'product',
|
||||||
|
'tracking': 'serial',
|
||||||
|
'list_price': 100.0,
|
||||||
|
'standard_price': 50.0,
|
||||||
|
'taxes_id': [(5, 0, 0)],
|
||||||
|
})
|
||||||
|
#self.product1 = self.env.ref('product.product_order_01')
|
||||||
|
self.product1.write({
|
||||||
|
'type': 'product',
|
||||||
|
'tracking': 'serial',
|
||||||
|
})
|
||||||
|
self.stock_location = self.env.ref('stock.stock_location_stock')
|
||||||
|
|
||||||
|
|
||||||
|
# def test_creation(self):
|
||||||
|
# self.productA.tracking = 'serial'
|
||||||
|
# lot = self.env['stock.production.lot'].create({
|
||||||
|
# 'product_id': self.productA.id,
|
||||||
|
# 'name': '123456789',
|
||||||
|
# })
|
||||||
|
#
|
||||||
|
# lot.catch_weight_ratio = 0.8
|
||||||
|
# _logger.warn(lot.xxxcatch_weight_ratio)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# def test_delivery(self):
|
||||||
|
# self.productA.tracking = 'serial'
|
||||||
|
# picking_pick, picking_pack, picking_ship = self.create_pick_pack_ship()
|
||||||
|
# stock_location = self.env['stock.location'].browse(self.stock_location)
|
||||||
|
# lot = self.env['stock.production.lot'].create({
|
||||||
|
# 'product_id': self.productA.id,
|
||||||
|
# 'name': '123456789',
|
||||||
|
# 'catch_weight_ratio': 0.8,
|
||||||
|
# })
|
||||||
|
# self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 1.0, lot_id=lot)
|
||||||
|
|
||||||
|
def test_so_invoice(self):
|
||||||
|
ratio = 0.8
|
||||||
|
lot = self.env['stock.production.lot'].create({
|
||||||
|
'product_id': self.product1.id,
|
||||||
|
'name': '123456789',
|
||||||
|
'catch_weight_ratio': ratio,
|
||||||
|
})
|
||||||
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot)
|
||||||
|
so = self.env['sale.order'].create({
|
||||||
|
'partner_id': self.partner1.id,
|
||||||
|
'partner_invoice_id': self.partner1.id,
|
||||||
|
'partner_shipping_id': self.partner1.id,
|
||||||
|
'order_line': [(0, 0, {'product_id': self.product1.id})],
|
||||||
|
})
|
||||||
|
so.action_confirm()
|
||||||
|
self.assertTrue(so.state in ('sale', 'done'))
|
||||||
|
self.assertEqual(len(so.picking_ids), 1)
|
||||||
|
picking = so.picking_ids
|
||||||
|
self.assertEqual(picking.state, 'assigned')
|
||||||
|
self.assertEqual(picking.move_lines.move_line_ids.lot_id, lot)
|
||||||
|
picking.move_lines.move_line_ids.qty_done = 1.0
|
||||||
|
picking.button_validate()
|
||||||
|
self.assertEqual(picking.state, 'done')
|
||||||
|
|
||||||
|
inv_id = so.action_invoice_create()
|
||||||
|
inv = self.env['account.invoice'].browse(inv_id)
|
||||||
|
self.assertEqual(inv.amount_total, ratio * self.product1.list_price)
|
||||||
|
|
||||||
|
def test_so_invoice2(self):
|
||||||
|
ratio1 = 0.8
|
||||||
|
ratio2 = 1.1
|
||||||
|
lot1 = self.env['stock.production.lot'].create({
|
||||||
|
'product_id': self.product1.id,
|
||||||
|
'name': '1-low',
|
||||||
|
'catch_weight_ratio': ratio1,
|
||||||
|
})
|
||||||
|
lot2 = self.env['stock.production.lot'].create({
|
||||||
|
'product_id': self.product1.id,
|
||||||
|
'name': '1-high',
|
||||||
|
'catch_weight_ratio': ratio2,
|
||||||
|
})
|
||||||
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot1)
|
||||||
|
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot2)
|
||||||
|
so = self.env['sale.order'].create({
|
||||||
|
'partner_id': self.partner1.id,
|
||||||
|
'partner_invoice_id': self.partner1.id,
|
||||||
|
'partner_shipping_id': self.partner1.id,
|
||||||
|
'order_line': [(0, 0, {'product_id': self.product1.id, 'product_uom_qty': 2.0})],
|
||||||
|
})
|
||||||
|
so.action_confirm()
|
||||||
|
self.assertTrue(so.state in ('sale', 'done'))
|
||||||
|
self.assertEqual(len(so.picking_ids), 1)
|
||||||
|
picking = so.picking_ids
|
||||||
|
self.assertEqual(picking.state, 'assigned')
|
||||||
|
self.assertEqual(picking.move_lines.move_line_ids.mapped('lot_id'), lot1 + lot2)
|
||||||
|
for line in picking.move_lines.move_line_ids:
|
||||||
|
line.qty_done = 1.0
|
||||||
|
picking.button_validate()
|
||||||
|
self.assertEqual(picking.state, 'done')
|
||||||
|
|
||||||
|
inv_id = so.action_invoice_create()
|
||||||
|
inv = self.env['account.invoice'].browse(inv_id)
|
||||||
|
self.assertEqual(inv.amount_total, (ratio1 * self.product1.list_price) + (ratio2 * self.product1.list_price))
|
||||||
|
|
||||||
|
def test_po_invoice(self):
|
||||||
|
ratio1 = 0.8
|
||||||
|
ratio2 = 1.1
|
||||||
|
ratios = (ratio1, ratio2)
|
||||||
|
price = self.product1.standard_price
|
||||||
|
po = self.env['purchase.order'].create({
|
||||||
|
'partner_id': self.partner1.id,
|
||||||
|
'order_line': [(0, 0, {
|
||||||
|
'product_id': self.product1.id,
|
||||||
|
'product_qty': 2.0,
|
||||||
|
'name': 'Test',
|
||||||
|
'date_planned': fields.Datetime.now(),
|
||||||
|
'product_uom': self.product1.uom_po_id.id,
|
||||||
|
'price_unit': price,
|
||||||
|
})]
|
||||||
|
})
|
||||||
|
po.button_confirm()
|
||||||
|
self.assertEqual(po.state, 'purchase')
|
||||||
|
self.assertEqual(len(po.picking_ids), 1)
|
||||||
|
|
||||||
|
picking = po.picking_ids
|
||||||
|
for i, line in enumerate(picking.move_lines.move_line_ids):
|
||||||
|
line.write({'lot_name': str(i), 'qty_done': 1.0, 'lot_catch_weight_ratio': ratios[i]})
|
||||||
|
picking.button_validate()
|
||||||
|
self.assertEqual(picking.state, 'done')
|
||||||
|
|
||||||
|
inv = self.env['account.invoice'].create({
|
||||||
|
'type': 'in_invoice',
|
||||||
|
'partner_id': self.partner1.id,
|
||||||
|
'purchase_id': po.id,
|
||||||
|
})
|
||||||
|
inv.purchase_order_change()
|
||||||
|
self.assertEqual(len(inv.invoice_line_ids), 1)
|
||||||
|
self.assertEqual(inv.invoice_line_ids.quantity, 2.0)
|
||||||
|
self.assertEqual(inv.amount_total, (ratio1 * price) + (ratio2 * price))
|
||||||
|
|
||||||
|
|
||||||
35
product_catch_weight/views/stock_views.xml
Normal file
35
product_catch_weight/views/stock_views.xml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_production_lot_form_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">stock.production.lot.form.inherit</field>
|
||||||
|
<field name="model">stock.production.lot</field>
|
||||||
|
<field name="inherit_id" ref="stock.view_production_lot_form" />
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='product_id']" position="after">
|
||||||
|
<field name="catch_weight_ratio"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_move_line_form_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">stock.move.line.form.inherit</field>
|
||||||
|
<field name="model">stock.move.line</field>
|
||||||
|
<field name="inherit_id" ref="stock.view_move_line_form" />
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='lot_name']" position="after">
|
||||||
|
<field name="lot_catch_weight_ratio" readonly="1"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="view_stock_move_line_operation_tree_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">stock.move.line.operations.tree.inherit</field>
|
||||||
|
<field name="model">stock.move.line</field>
|
||||||
|
<field name="inherit_id" ref="stock.view_stock_move_line_operation_tree" />
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='lot_name']" position="after">
|
||||||
|
<field name="lot_catch_weight_ratio" invisible="not context.get('show_lots_text')"/>
|
||||||
|
<field name="lot_catch_weight_ratio_related" invisible="not context.get('show_lots_m2o')"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
Reference in New Issue
Block a user