[REF] stock: quant: use in_date field

Set the `in_date` field to now when creating a tracked quant.
Set the correct `in_date` field when moving a quant (this is done by
adding a kw argument to `_update_available_quantity` and adapting the
code that moves quant in stock.move.line).

If quants from the same lot go together in the same location, we merge
them and keep the older date (as the user will have no way to tell the
difference anyway).

We add multiple test cases:
    - `in_date` are only set on tracked quants
    - FIFO and LIFO are correctly applied
    - keep the oldest `in_date` when merging quants
    - editing a done move line or creating one in a done picking is
      correctly working

Joint work with sle-odoo.
This commit is contained in:
Pierre Masereel
2017-07-28 11:38:31 +02:00
committed by Simon Lejeune
parent 3d4238f71e
commit 0fbe90b227
4 changed files with 430 additions and 14 deletions
+13 -9
View File
@@ -98,8 +98,9 @@ class StockMoveLine(models.Model):
if ml.state == 'done':
Quant = self.env['stock.quant']
quantity = ml.product_uom_id._compute_quantity(ml.qty_done, ml.move_id.product_id.uom_id,rounding_method='HALF-UP')
in_date = None
if ml.location_id.should_impact_quants() and ml.product_id.type == 'product':
available_qty = 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)
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)
@@ -108,7 +109,7 @@ class StockMoveLine(models.Model):
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)
if ml.location_dest_id.should_impact_quants() and ml.product_id.type == 'product':
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)
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)
next_moves = ml.move_id.move_dest_ids.filtered(lambda move: move.state not in ('done', 'cancel'))
next_moves.do_unreserve()
next_moves.action_assign()
@@ -187,14 +188,16 @@ class StockMoveLine(models.Model):
if updates or 'qty_done' in vals:
for ml in self.filtered(lambda ml: ml.move_id.state == 'done'):
# undo the original move line
in_date = None
if ml.location_dest_id.should_impact_quants() and ml.product_id.type == 'product':
Quant._update_available_quantity(ml.product_id, ml.location_dest_id, -ml.qty_done, lot_id=ml.lot_id,
package_id=ml.package_id, owner_id=ml.owner_id)
in_date = Quant._update_available_quantity(ml.product_id, ml.location_dest_id, -ml.qty_done, lot_id=ml.lot_id,
package_id=ml.package_id, owner_id=ml.owner_id)[1]
if ml.location_id.should_impact_quants() and ml.product_id.type == 'product':
Quant._update_available_quantity(ml.product_id, ml.location_id, ml.qty_done, lot_id=ml.lot_id,
package_id=ml.package_id, owner_id=ml.owner_id)
package_id=ml.package_id, owner_id=ml.owner_id, in_date=in_date)
# move what's been actually done
in_date = None
product_id = ml.product_id
location_id = updates.get('location_id', ml.location_id)
location_dest_id = updates.get('location_dest_id', ml.location_dest_id)
@@ -206,7 +209,7 @@ class StockMoveLine(models.Model):
quantity = ml.move_id.product_uom._compute_quantity(qty_done, ml.move_id.product_id.uom_id, rounding_method='HALF-UP')
if location_id.should_impact_quants() and ml.product_id.type == 'product':
ml._free_reservation(product_id, location_id, quantity, lot_id=lot_id, package_id=package_id, owner_id=owner_id)
available_qty = Quant._update_available_quantity(product_id, location_id, -quantity, lot_id=lot_id, package_id=package_id, owner_id=owner_id)
available_qty, in_date = Quant._update_available_quantity(product_id, location_id, -quantity, lot_id=lot_id, package_id=package_id, owner_id=owner_id)
if available_qty < 0 and lot_id:
# see if we can compensate the negative quants with some untracked quants
untracked_qty = Quant._get_available_quantity(product_id, location_id, lot_id=False, package_id=package_id, owner_id=owner_id, strict=True)
@@ -216,7 +219,7 @@ class StockMoveLine(models.Model):
Quant._update_available_quantity(product_id, location_id, taken_from_untracked_qty, lot_id=lot_id, package_id=package_id, owner_id=owner_id)
ml._free_reservation(ml.product_id, location_id, untracked_qty, lot_id=False, package_id=package_id, owner_id=owner_id)
if location_dest_id.should_impact_quants() and ml.product_id.type == 'product' and qty_done:
Quant._update_available_quantity(product_id, location_dest_id, quantity, lot_id=lot_id, package_id=result_package_id, owner_id=owner_id)
Quant._update_available_quantity(product_id, location_dest_id, quantity, lot_id=lot_id, package_id=result_package_id, owner_id=owner_id, in_date=in_date)
# Unreserve and reserve following move in order to have the real reserved quantity on move_line.
next_moves |= ml.move_id.move_dest_ids.filtered(lambda move: move.state not in ('done', 'cancel'))
res = super(StockMoveLine, self).write(vals)
@@ -304,8 +307,9 @@ class StockMoveLine(models.Model):
# 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')
in_date = None
if ml.location_id.should_impact_quants() and ml.product_id.type == 'product':
available_qty = 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)
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)
@@ -314,7 +318,7 @@ class StockMoveLine(models.Model):
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)
if ml.location_dest_id.should_impact_quants() and ml.product_id.type == 'product':
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)
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)
def _free_reservation(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None):
""" When editing a done move line or validating one with some forced quantities, it is
+41 -3
View File
@@ -57,6 +57,13 @@ class StockQuant(models.Model):
if quant.quantity > 1 and quant.lot_id and quant.product_id.tracking == 'serial':
raise ValidationError(_('A serial number should only be linked to a single product.'))
@api.multi
@api.constrains('in_date', 'lot_id')
def check_in_date(self):
for quant in self:
if quant.in_date and not quant.lot_id:
raise ValidationError(_('An incoming date cannot be set to an untracked product.'))
@api.one
def _compute_name(self):
self.name = '%s: %s%s' % (self.lot_id.name or self.product_id.code or '', self.quantity, self.product_id.uom_id.name)
@@ -126,14 +133,44 @@ class StockQuant(models.Model):
return sum(quants.mapped('quantity')) - sum(quants.mapped('reserved_quantity'))
@api.model
def _update_available_quantity(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None):
def _update_available_quantity(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None, in_date=None):
""" Increase or decrease `reserved_quantity` of a set of quants for a given set of
product_id/location_id/lot_id/package_id/owner_id.
:param product_id:
:param location_id:
:param quantity:
:param lot_id:
:param package_id:
:param owner_id:
:param datetime in_date: Should only be passed when calls to this method are done in
order to move a quant. When creating a tracked quant, the
current datetime will be used.
:return: tuple (available_quantity, in_date as a datetime)
"""
self = self.sudo()
quants = self._gather(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True)
if lot_id:
incoming_dates = quants.mapped('in_date') # `mapped` already filtered out falsy items
incoming_dates = [fields.Datetime.from_string(incoming_date) for incoming_date in incoming_dates]
if in_date:
incoming_dates += [in_date]
# If multiple incoming dates are available for a given lot_id/package_id/owner_id, we
# consider only the oldest one as being relevant.
if incoming_dates:
in_date = fields.Datetime.to_string(min(incoming_dates))
else:
in_date = fields.Datetime.now()
for quant in quants:
try:
with self._cr.savepoint():
self._cr.execute("SELECT 1 FROM stock_quant WHERE id = %s FOR UPDATE NOWAIT", [quant.id], log_exceptions=False)
quant.quantity += quantity
quant.write({
'quantity': quant.quantity + quantity,
'in_date': in_date,
})
# cleanup empty quants
if quant.quantity == 0 and quant.reserved_quantity == 0:
quant.unlink()
@@ -151,8 +188,9 @@ class StockQuant(models.Model):
'lot_id': lot_id and lot_id.id,
'package_id': package_id and package_id.id,
'owner_id': owner_id and owner_id.id,
'in_date': in_date,
})
return self._get_available_quantity(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=False)
return self._get_available_quantity(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=False), fields.Datetime.from_string(in_date)
@api.model
def _update_reserved_quantity(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None, strict=False):
+246 -2
View File
@@ -114,7 +114,10 @@ class StockMove(TransactionCase):
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product3, self.supplier_location), 0.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product3, self.stock_location), 5.0)
self.assertEqual(len(self.env['stock.quant']._gather(self.product3, self.supplier_location)), 0.0)
self.assertEqual(len(self.env['stock.quant']._gather(self.product3, self.stock_location)), 1.0)
quants = self.env['stock.quant']._gather(self.product3, self.stock_location)
self.assertEqual(len(quants), 1.0)
for quant in quants:
self.assertNotEqual(quant.in_date, False)
def test_in_3(self):
""" Receive 5 serial-tracked products from a supplier. The system should create 5 differents
@@ -162,7 +165,10 @@ class StockMove(TransactionCase):
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location), 5.0)
self.assertEqual(len(self.env['stock.quant']._gather(self.product2, self.supplier_location)), 0.0)
self.assertEqual(len(self.env['stock.quant']._gather(self.product2, self.stock_location)), 5.0)
quants = self.env['stock.quant']._gather(self.product2, self.stock_location)
self.assertEqual(len(quants), 5.0)
for quant in quants:
self.assertNotEqual(quant.in_date, False)
def test_out_1(self):
""" Send products to a client. Check that a move line is created reserving products in
@@ -2371,3 +2377,241 @@ class StockMove(TransactionCase):
self.assertEqual(move1.state, 'confirmed')
self.assertEqual(len(move1.move_line_ids), 0)
def test_in_date_1(self):
""" Check that moving a tracked quant keeps the incoming date.
"""
move1 = self.env['stock.move'].create({
'name': 'test_in_date_1',
'location_id': self.supplier_location.id,
'location_dest_id': self.stock_location.id,
'product_id': self.product3.id,
'product_uom': self.uom_unit.id,
'product_uom_qty': 1.0,
'picking_type_id': self.env.ref('stock.picking_type_in').id,
})
move1.action_confirm()
move1.action_assign()
move1.move_line_ids.lot_name = 'lot1'
move1.move_line_ids.qty_done = 1
move1.action_done()
quant = self.env['stock.quant']._gather(self.product3, self.stock_location)
self.assertEqual(len(quant), 1.0)
self.assertNotEqual(quant.in_date, False)
# Keep a reference to the initial incoming date in order to compare it later.
initial_incoming_date = quant.in_date
move2 = self.env['stock.move'].create({
'name': 'test_in_date_1',
'location_id': self.stock_location.id,
'location_dest_id': self.pack_location.id,
'product_id': self.product3.id,
'product_uom': self.uom_unit.id,
'product_uom_qty': 1.0,
})
move2.action_confirm()
move2.action_assign()
move2.move_line_ids.qty_done = 1
move2.action_done()
quant = self.env['stock.quant']._gather(self.product3, self.pack_location)
self.assertEqual(len(quant), 1.0)
self.assertEqual(quant.in_date, initial_incoming_date)
def test_in_date_2(self):
""" Check that editing a done move line for a tracked product and changing its lot
correctly restores the original lot with its incoming date and remove the new lot
with its incoming date.
"""
lot1 = self.env['stock.production.lot'].create({
'name': 'lot1',
'product_id': self.product3.id,
})
lot2 = self.env['stock.production.lot'].create({
'name': 'lot2',
'product_id': self.product3.id,
})
# receive lot1
move1 = self.env['stock.move'].create({
'name': 'test_in_date_1',
'location_id': self.supplier_location.id,
'location_dest_id': self.stock_location.id,
'product_id': self.product3.id,
'product_uom': self.uom_unit.id,
'product_uom_qty': 1.0,
'picking_type_id': self.env.ref('stock.picking_type_in').id,
})
move1.action_confirm()
move1.action_assign()
move1.move_line_ids.lot_id = lot1
move1.move_line_ids.qty_done = 1
move1.action_done()
# receive lot2
move2 = self.env['stock.move'].create({
'name': 'test_in_date_1',
'location_id': self.supplier_location.id,
'location_dest_id': self.stock_location.id,
'product_id': self.product3.id,
'product_uom': self.uom_unit.id,
'product_uom_qty': 1.0,
'picking_type_id': self.env.ref('stock.picking_type_in').id,
})
move2.action_confirm()
move2.action_assign()
move2.move_line_ids.lot_id = lot2
move2.move_line_ids.qty_done = 1
move2.action_done()
initial_in_date_lot2 = self.env['stock.quant'].search([
('product_id', '=', self.product3.id),
('lot_id', '=', lot2.id),
]).in_date
# Edit lot1's incoming date.
quant_lot1 = self.env['stock.quant'].search([
('product_id', '=', self.product3.id),
('lot_id', '=', lot1.id),
])
from datetime import datetime, timedelta
initial_in_date_lot1 = datetime.now() - timedelta(days=5)
quant_lot1.in_date = initial_in_date_lot1
# Move one quant to pack location
move3 = self.env['stock.move'].create({
'name': 'test_in_date_1',
'location_id': self.stock_location.id,
'location_dest_id': self.pack_location.id,
'product_id': self.product3.id,
'product_uom': self.uom_unit.id,
'product_uom_qty': 1.0,
})
move3.action_confirm()
move3.action_assign()
move3.move_line_ids.qty_done = 1
move3.action_done()
quant_in_pack = self.env['stock.quant'].search([
('product_id', '=', self.product3.id),
('location_id', '=', self.pack_location.id),
])
# As lot1 has an older date and FIFO is set by default, it's the one that should be
# in pack.
self.assertEqual(len(quant_in_pack), 1)
from odoo.fields import Datetime
self.assertEqual(quant_in_pack.in_date, Datetime.to_string(initial_in_date_lot1))
self.assertEqual(quant_in_pack.lot_id, lot1)
# Now, edit the move line and actually move the other lot
move3.move_line_ids.lot_id = lot2
# Check that lot1 correctly is back to stock with its right in_date
quant_lot1 = self.env['stock.quant'].search([
('product_id', '=', self.product3.id),
('lot_id', '=', lot1.id),
])
self.assertEqual(quant_lot1.location_id, self.stock_location)
self.assertEqual(quant_lot1.in_date, Datetime.to_string(initial_in_date_lot1))
# Check that lo2 is in pack with is right in_date
quant_lot2 = self.env['stock.quant'].search([
('product_id', '=', self.product3.id),
('lot_id', '=', lot2.id),
])
self.assertEqual(quant_lot2.location_id, self.pack_location)
self.assertEqual(quant_lot2.in_date, initial_in_date_lot2)
def test_in_date_3(self):
""" Check that, when creating a move line on a done stock move, the lot and its incoming
date are correctly moved to the destination location.
"""
lot1 = self.env['stock.production.lot'].create({
'name': 'lot1',
'product_id': self.product3.id,
})
lot2 = self.env['stock.production.lot'].create({
'name': 'lot2',
'product_id': self.product3.id,
})
# receive lot1
move1 = self.env['stock.move'].create({
'name': 'test_in_date_1',
'location_id': self.supplier_location.id,
'location_dest_id': self.stock_location.id,
'product_id': self.product3.id,
'product_uom': self.uom_unit.id,
'product_uom_qty': 1.0,
'picking_type_id': self.env.ref('stock.picking_type_in').id,
})
move1.action_confirm()
move1.action_assign()
move1.move_line_ids.lot_id = lot1
move1.move_line_ids.qty_done = 1
move1.action_done()
# receive lot2
move2 = self.env['stock.move'].create({
'name': 'test_in_date_1',
'location_id': self.supplier_location.id,
'location_dest_id': self.stock_location.id,
'product_id': self.product3.id,
'product_uom': self.uom_unit.id,
'product_uom_qty': 1.0,
'picking_type_id': self.env.ref('stock.picking_type_in').id,
})
move2.action_confirm()
move2.action_assign()
move2.move_line_ids.lot_id = lot2
move2.move_line_ids.qty_done = 1
move2.action_done()
initial_in_date_lot2 = self.env['stock.quant'].search([
('product_id', '=', self.product3.id),
('lot_id', '=', lot2.id),
]).in_date
# Edit lot1's incoming date.
quant_lot1 = self.env['stock.quant'].search([
('product_id', '=', self.product3.id),
('lot_id', '=', lot1.id),
])
from datetime import datetime, timedelta
initial_in_date_lot1 = datetime.now() - timedelta(days=5)
quant_lot1.in_date = initial_in_date_lot1
# Move one quant to pack location
move3 = self.env['stock.move'].create({
'name': 'test_in_date_1',
'location_id': self.stock_location.id,
'location_dest_id': self.pack_location.id,
'product_id': self.product3.id,
'product_uom': self.uom_unit.id,
'product_uom_qty': 1.0,
})
move3.action_confirm()
move3.action_assign()
move3.move_line_ids.qty_done = 1
move3.action_done()
# Now, also move lot2
self.env['stock.move.line'].create({
'move_id': move3.id,
'product_id': move3.product_id.id,
'qty_done': 1,
'product_uom_id': move3.product_uom.id,
'location_id': move3.location_id.id,
'location_dest_id': move3.location_dest_id.id,
'lot_id': lot2.id,
})
quants = self.env['stock.quant'].search([
('product_id', '=', self.product3.id),
])
self.assertEqual(len(quants), 2)
from odoo.fields import Datetime
for quant in quants:
if quant.lot_id == lot1:
self.assertEqual(quant.in_date, Datetime.to_string(initial_in_date_lot1))
elif quant.lot_id == lot2:
self.assertEqual(quant.in_date, initial_in_date_lot2)
+130
View File
@@ -5,6 +5,8 @@ from odoo.exceptions import ValidationError
from odoo.tests.common import TransactionCase
from odoo.exceptions import AccessError, UserError
from datetime import datetime, timedelta
class StockQuant(TransactionCase):
def setUp(self):
@@ -588,3 +590,131 @@ class StockQuant(TransactionCase):
quant.sudo(self.demo_user).write({'quantity': 2.0})
with self.assertRaises(AccessError):
quant.sudo(self.demo_user).unlink()
def test_in_date_1(self):
""" Check that no incoming date is set when updating the quantity of an untracked quant.
"""
stock_location = self.env.ref('stock.stock_location_stock')
product1 = self.env['product.product'].create({
'name': 'Product A',
'type': 'product',
})
quantity, in_date = self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0)
self.assertEqual(quantity, 1)
self.assertEqual(in_date, None)
def test_in_date_2(self):
""" Check that an incoming date is correctly set when updating the quantity of a tracked
quant.
"""
stock_location = self.env.ref('stock.stock_location_stock')
product1 = self.env['product.product'].create({
'name': 'Product A',
'type': 'product',
'tracking': 'serial',
})
lot1 = self.env['stock.production.lot'].create({
'name': 'lot1',
'product_id': product1.id,
})
quantity, in_date = self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0, lot_id=lot1)
self.assertEqual(quantity, 1)
self.assertNotEqual(in_date, None)
def test_in_date_3(self):
""" Check that the FIFO strategies correctly applies when you have multiple lot received
at different times for a tracked product.
"""
stock_location = self.env.ref('stock.stock_location_stock')
product1 = self.env['product.product'].create({
'name': 'Product A',
'type': 'product',
'tracking': 'serial',
})
lot1 = self.env['stock.production.lot'].create({
'name': 'lot1',
'product_id': product1.id,
})
lot2 = self.env['stock.production.lot'].create({
'name': 'lot2',
'product_id': product1.id,
})
in_date_lot1 = datetime.now()
in_date_lot2 = datetime.now() - timedelta(days=5)
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0, lot_id=lot1, in_date=in_date_lot1)
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0, lot_id=lot2, in_date=in_date_lot2)
quants = self.env['stock.quant']._update_reserved_quantity(product1, stock_location, 1)
# Default removal strategy is FIFO, so lot2 should be received as it was received earlier.
self.assertEqual(quants[0][0].lot_id.id, lot2.id)
def test_in_date_4(self):
""" Check that the LIFO strategies correctly applies when you have multiple lot received
at different times for a tracked product.
"""
stock_location = self.env.ref('stock.stock_location_stock')
lifo_strategy = self.env['product.removal'].search([('method', '=', 'lifo')])
stock_location.removal_strategy_id = lifo_strategy
product1 = self.env['product.product'].create({
'name': 'Product A',
'type': 'product',
'tracking': 'serial',
})
lot1 = self.env['stock.production.lot'].create({
'name': 'lot1',
'product_id': product1.id,
})
lot2 = self.env['stock.production.lot'].create({
'name': 'lot2',
'product_id': product1.id,
})
in_date_lot1 = datetime.now()
in_date_lot2 = datetime.now() - timedelta(days=5)
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0, lot_id=lot1, in_date=in_date_lot1)
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0, lot_id=lot2, in_date=in_date_lot2)
quants = self.env['stock.quant']._update_reserved_quantity(product1, stock_location, 1)
# Removal strategy is LIFO, so lot1 should be received as it was received later.
self.assertEqual(quants[0][0].lot_id.id, lot1.id)
def test_in_date_5(self):
""" Receive the same lot at different times, once they're in the same location, the quants
are merged and only the earliest incoming date is kept.
"""
stock_location = self.env.ref('stock.stock_location_stock')
product1 = self.env['product.product'].create({
'name': 'Product A',
'type': 'product',
'tracking': 'lot',
})
lot1 = self.env['stock.production.lot'].create({
'name': 'lot1',
'product_id': product1.id,
})
in_date1 = datetime.now()
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0, lot_id=lot1, in_date=in_date1)
quant = self.env['stock.quant'].search([
('product_id', '=', product1.id),
('location_id', '=', stock_location.id),
])
self.assertEqual(len(quant), 1)
self.assertEqual(quant.quantity, 1)
self.assertEqual(quant.lot_id.id, lot1.id)
from odoo.fields import Datetime
self.assertEqual(quant.in_date, Datetime.to_string(in_date1))
in_date2 = datetime.now() - timedelta(days=5)
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0, lot_id=lot1, in_date=in_date2)
quant = self.env['stock.quant'].search([
('product_id', '=', product1.id),
('location_id', '=', stock_location.id),
])
self.assertEqual(len(quant), 1)
self.assertEqual(quant.quantity, 2)
self.assertEqual(quant.lot_id.id, lot1.id)
self.assertEqual(quant.in_date, Datetime.to_string(in_date2))