From 4b64c8b72278f3d0a761779d54a02707b6c67473 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Tue, 5 Jun 2018 11:30:29 -0700 Subject: [PATCH 1/2] Initial commit of `product_catch_weight` for 11.0 --- product_catch_weight/__init__.py | 1 + product_catch_weight/__manifest__.py | 19 +++ product_catch_weight/models/__init__.py | 3 + .../models/account_invoice.py | 42 +++++ product_catch_weight/models/stock.py | 20 +++ product_catch_weight/models/stock_patch.py | 114 +++++++++++++ product_catch_weight/tests/__init__.py | 1 + .../tests/test_catch_weight.py | 152 ++++++++++++++++++ product_catch_weight/views/stock_views.xml | 35 ++++ 9 files changed, 387 insertions(+) create mode 100644 product_catch_weight/__init__.py create mode 100644 product_catch_weight/__manifest__.py create mode 100644 product_catch_weight/models/__init__.py create mode 100644 product_catch_weight/models/account_invoice.py create mode 100644 product_catch_weight/models/stock.py create mode 100644 product_catch_weight/models/stock_patch.py create mode 100644 product_catch_weight/tests/__init__.py create mode 100644 product_catch_weight/tests/test_catch_weight.py create mode 100644 product_catch_weight/views/stock_views.xml diff --git a/product_catch_weight/__init__.py b/product_catch_weight/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/product_catch_weight/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/product_catch_weight/__manifest__.py b/product_catch_weight/__manifest__.py new file mode 100644 index 00000000..7c2ef68c --- /dev/null +++ b/product_catch_weight/__manifest__.py @@ -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, +} diff --git a/product_catch_weight/models/__init__.py b/product_catch_weight/models/__init__.py new file mode 100644 index 00000000..57a87093 --- /dev/null +++ b/product_catch_weight/models/__init__.py @@ -0,0 +1,3 @@ +from . import account_invoice +from . import stock_patch +from . import stock diff --git a/product_catch_weight/models/account_invoice.py b/product_catch_weight/models/account_invoice.py new file mode 100644 index 00000000..ab72943c --- /dev/null +++ b/product_catch_weight/models/account_invoice.py @@ -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 \ No newline at end of file diff --git a/product_catch_weight/models/stock.py b/product_catch_weight/models/stock.py new file mode 100644 index 00000000..6acec3a4 --- /dev/null +++ b/product_catch_weight/models/stock.py @@ -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 diff --git a/product_catch_weight/models/stock_patch.py b/product_catch_weight/models/stock_patch.py new file mode 100644 index 00000000..8787f4f6 --- /dev/null +++ b/product_catch_weight/models/stock_patch.py @@ -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 diff --git a/product_catch_weight/tests/__init__.py b/product_catch_weight/tests/__init__.py new file mode 100644 index 00000000..0edad729 --- /dev/null +++ b/product_catch_weight/tests/__init__.py @@ -0,0 +1 @@ +from . import test_catch_weight diff --git a/product_catch_weight/tests/test_catch_weight.py b/product_catch_weight/tests/test_catch_weight.py new file mode 100644 index 00000000..a167e530 --- /dev/null +++ b/product_catch_weight/tests/test_catch_weight.py @@ -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)) + + diff --git a/product_catch_weight/views/stock_views.xml b/product_catch_weight/views/stock_views.xml new file mode 100644 index 00000000..cee50753 --- /dev/null +++ b/product_catch_weight/views/stock_views.xml @@ -0,0 +1,35 @@ + + + + stock.production.lot.form.inherit + stock.production.lot + + + + + + + + + + stock.move.line.form.inherit + stock.move.line + + + + + + + + + stock.move.line.operations.tree.inherit + stock.move.line + + + + + + + + + \ No newline at end of file From 7ae88479d2dfa9946529b890a7e98650245bb567 Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Sun, 10 Jun 2018 11:02:27 -0700 Subject: [PATCH 2/2] Improve UI, allowing user to measure the catch weight in a specific unit of measure. This UOM should be convertable (in the same category) as the normal stock UOM for the product. Example: If you want to sell 'units' of 50lbs then you should make a "50lbs" UOM in the Weight category and use that as the sale and purchase UOM, then your "Catch Weight UOM" can be the stock "lb(s)" UOM. --- product_catch_weight/__manifest__.py | 1 + product_catch_weight/models/__init__.py | 1 + .../models/account_invoice.py | 6 ++ product_catch_weight/models/product.py | 7 ++ product_catch_weight/models/stock.py | 44 +++++++++--- product_catch_weight/models/stock_patch.py | 3 +- .../tests/test_catch_weight.py | 46 +++++++------ .../views/account_invoice_views.xml | 69 +++++++++++++++++++ product_catch_weight/views/stock_views.xml | 43 +++++++++--- 9 files changed, 182 insertions(+), 38 deletions(-) create mode 100644 product_catch_weight/models/product.py create mode 100644 product_catch_weight/views/account_invoice_views.xml diff --git a/product_catch_weight/__manifest__.py b/product_catch_weight/__manifest__.py index 7c2ef68c..76d3cea2 100644 --- a/product_catch_weight/__manifest__.py +++ b/product_catch_weight/__manifest__.py @@ -12,6 +12,7 @@ 'license': 'AGPL-3', 'website': 'https://hibou.io/', 'data': [ + 'views/account_invoice_views.xml', 'views/stock_views.xml', ], 'installable': True, diff --git a/product_catch_weight/models/__init__.py b/product_catch_weight/models/__init__.py index 57a87093..5e099bc5 100644 --- a/product_catch_weight/models/__init__.py +++ b/product_catch_weight/models/__init__.py @@ -1,3 +1,4 @@ from . import account_invoice +from . import product from . import stock_patch from . import stock diff --git a/product_catch_weight/models/account_invoice.py b/product_catch_weight/models/account_invoice.py index ab72943c..ee8c9f92 100644 --- a/product_catch_weight/models/account_invoice.py +++ b/product_catch_weight/models/account_invoice.py @@ -7,6 +7,9 @@ _logger = logging.getLogger(__name__) class AccountInvoiceLine(models.Model): _inherit = 'account.invoice.line' + catch_weight = fields.Float(string='Catch Weight', digits=(10, 4), compute='_compute_price', store=True) + catch_weight_uom_id = fields.Many2one('product.uom', related='product_id.catch_weight_uom_id') + @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', @@ -17,6 +20,7 @@ class AccountInvoiceLine(models.Model): ratio = 1.0 qty_done_total = 0.0 + catch_weight = 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: @@ -26,7 +30,9 @@ class AccountInvoiceLine(models.Model): 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 + catch_weight += move_line.lot_id.catch_weight price = price * ratio + self.catch_weight = catch_weight taxes = False if self.invoice_line_tax_ids: diff --git a/product_catch_weight/models/product.py b/product_catch_weight/models/product.py new file mode 100644 index 00000000..16cb4b91 --- /dev/null +++ b/product_catch_weight/models/product.py @@ -0,0 +1,7 @@ +from odoo import api, fields, models + + +class ProductProduct(models.Model): + _inherit = 'product.template' + + catch_weight_uom_id = fields.Many2one('product.uom', string='Catch Weight UOM') diff --git a/product_catch_weight/models/stock.py b/product_catch_weight/models/stock.py index 6acec3a4..1ad5d3b6 100644 --- a/product_catch_weight/models/stock.py +++ b/product_catch_weight/models/stock.py @@ -4,17 +4,43 @@ 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) + catch_weight_ratio = fields.Float(string='Catch Weight Ratio', digits=(10, 6), compute='_compute_catch_weight_ratio') + catch_weight = fields.Float(string='Catch Weight', digits=(10, 4)) + catch_weight_uom_id = fields.Many2one('product.uom', related='product_id.catch_weight_uom_id') + + + @api.depends('catch_weight') + def _compute_catch_weight_ratio(self): + for lot in self: + if not lot.catch_weight_uom_id: + lot.catch_weight_ratio = 1.0 + else: + lot.catch_weight_ratio = lot.catch_weight_uom_id._compute_quantity(lot.catch_weight, + lot.product_id.uom_id, + rounding_method='DOWN') + + +class StockMove(models.Model): + _inherit = 'stock.move' + + product_catch_weight_uom_id = fields.Many2one('product.uom', related="product_id.catch_weight_uom_id") + + def _prepare_move_line_vals(self, quantity=None, reserved_quant=None): + vals = super(StockMove, self)._prepare_move_line_vals(quantity=quantity, reserved_quant=reserved_quant) + vals['catch_weight_uom_id'] = self.product_catch_weight_uom_id.id if self.product_catch_weight_uom_id else False + return vals + + def action_show_details(self): + action = super(StockMove, self).action_show_details() + action['context']['show_catch_weight'] = bool(self.product_id.catch_weight_uom_id) + return action 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 + catch_weight_ratio = fields.Float(string='Catch Weight Ratio', digits=(10, 6), default=1.0) + catch_weight = fields.Float(string='Catch Weight', digits=(10,4)) + catch_weight_uom_id = fields.Many2one('product.uom', string='Catch Weight UOM') + lot_catch_weight = fields.Float(related='lot_id.catch_weight') + lot_catch_weight_uom_id = fields.Many2one('product.uom', related='product_id.catch_weight_uom_id') diff --git a/product_catch_weight/models/stock_patch.py b/product_catch_weight/models/stock_patch.py index 8787f4f6..fca04e4f 100644 --- a/product_catch_weight/models/stock_patch.py +++ b/product_catch_weight/models/stock_patch.py @@ -41,8 +41,9 @@ def _action_done(self): # 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_catch_weight = ml.catch_weight_uom_id._compute_quantity(ml.catch_weight, ml.product_id.catch_weight_uom_id, rounding_method='DOWN') 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} + {'name': ml.lot_name, 'product_id': ml.product_id.id, 'catch_weight': lot_catch_weight} ) ml.write({'lot_id': lot.id}) elif not picking_type_id.use_create_lots and not picking_type_id.use_existing_lots: diff --git a/product_catch_weight/tests/test_catch_weight.py b/product_catch_weight/tests/test_catch_weight.py index a167e530..1b13cb55 100644 --- a/product_catch_weight/tests/test_catch_weight.py +++ b/product_catch_weight/tests/test_catch_weight.py @@ -9,7 +9,16 @@ _logger = logging.getLogger(__name__) class TestPicking(TransactionCase): def setUp(self): super(TestPicking, self).setUp() + self.nominal_weight = 50.0 self.partner1 = self.env.ref('base.res_partner_2') + self.stock_location = self.env.ref('stock.stock_location_stock') + self.ref_uom_id = self.env.ref('product.product_uom_kgm') + self.product_uom_id = self.env['product.uom'].create({ + 'name': '50 ref', + 'category_id': self.ref_uom_id.category_id.id, + 'uom_type': 'bigger', + 'factor_inv': self.nominal_weight, + }) self.product1 = self.env['product.product'].create({ 'name': 'Product 1', 'type': 'product', @@ -17,13 +26,10 @@ class TestPicking(TransactionCase): 'list_price': 100.0, 'standard_price': 50.0, 'taxes_id': [(5, 0, 0)], + 'uom_id': self.product_uom_id.id, + 'uom_po_id': self.product_uom_id.id, + 'catch_weight_uom_id': self.ref_uom_id.id, }) - #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): @@ -50,12 +56,13 @@ class TestPicking(TransactionCase): # self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 1.0, lot_id=lot) def test_so_invoice(self): - ratio = 0.8 + ref_weight = 45.0 lot = self.env['stock.production.lot'].create({ 'product_id': self.product1.id, 'name': '123456789', - 'catch_weight_ratio': ratio, + 'catch_weight': ref_weight, }) + self.assertAlmostEqual(lot.catch_weight_ratio, ref_weight / self.nominal_weight) 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, @@ -75,20 +82,20 @@ class TestPicking(TransactionCase): inv_id = so.action_invoice_create() inv = self.env['account.invoice'].browse(inv_id) - self.assertEqual(inv.amount_total, ratio * self.product1.list_price) + self.assertAlmostEqual(inv.amount_total, lot.catch_weight_ratio * self.product1.list_price) def test_so_invoice2(self): - ratio1 = 0.8 - ratio2 = 1.1 + ref_weight1 = 45.0 + ref_weight2 = 51.0 lot1 = self.env['stock.production.lot'].create({ 'product_id': self.product1.id, 'name': '1-low', - 'catch_weight_ratio': ratio1, + 'catch_weight': ref_weight1, }) lot2 = self.env['stock.production.lot'].create({ 'product_id': self.product1.id, 'name': '1-high', - 'catch_weight_ratio': ratio2, + 'catch_weight': ref_weight2, }) 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) @@ -111,12 +118,12 @@ class TestPicking(TransactionCase): 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)) + self.assertAlmostEqual(inv.amount_total, self.product1.list_price * (lot1.catch_weight_ratio + lot2.catch_weight_ratio)) def test_po_invoice(self): - ratio1 = 0.8 - ratio2 = 1.1 - ratios = (ratio1, ratio2) + ref_weight1 = 45.0 + ref_weight2 = 51.0 + weights = (ref_weight1, ref_weight2) price = self.product1.standard_price po = self.env['purchase.order'].create({ 'partner_id': self.partner1.id, @@ -135,7 +142,7 @@ class TestPicking(TransactionCase): 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]}) + line.write({'lot_name': str(i), 'qty_done': 1.0, 'catch_weight': weights[i]}) picking.button_validate() self.assertEqual(picking.state, 'done') @@ -147,6 +154,5 @@ class TestPicking(TransactionCase): 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)) - + self.assertAlmostEqual(inv.amount_total, price * sum(w / self.nominal_weight for w in weights)) diff --git a/product_catch_weight/views/account_invoice_views.xml b/product_catch_weight/views/account_invoice_views.xml new file mode 100644 index 00000000..8eb57740 --- /dev/null +++ b/product_catch_weight/views/account_invoice_views.xml @@ -0,0 +1,69 @@ + + + + account.invoice.form.inherit + account.invoice + + + + + + + + + + account.invoice.supplier.form.inherit + account.invoice + + + + + + + + + + + + \ No newline at end of file diff --git a/product_catch_weight/views/stock_views.xml b/product_catch_weight/views/stock_views.xml index cee50753..40e2d9dd 100644 --- a/product_catch_weight/views/stock_views.xml +++ b/product_catch_weight/views/stock_views.xml @@ -7,17 +7,32 @@ + + - - stock.move.line.form.inherit - stock.move.line - + + + + + + + + + + + + stock.move.operations.form.inherit + stock.move + - - + + + + + {'tree_view_ref': 'stock.view_stock_move_line_operation_tree', 'default_product_uom_id': product_uom, 'default_picking_id': picking_id, 'default_move_id': id, 'default_product_id': product_id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_catch_weight_uom_id': product_catch_weight_uom_id} @@ -27,8 +42,20 @@ - - + + + + + + + + + product.template.common.form.inherit + product.template + + + +