To reproduce issue: .create a new picking of any operation type .save the picking (so that a name is formed) .edit the picking and change the operation type to another type .save manually Expected result: new name with matching prefix to the new operation type Actual result: previous name with prefix from previous operation type is still there. This is an issue because then the name doesn't match and this can be confusing to users When a user create a new picking of any operation type and save the picking, the name is formed based on your type. If the status of picking is draft, and he needs change the picking type, the name of picking is not changed, so, this PR solves this error. Now, when change the picking type and save it manually, the name of picking will be regenerated with the correct specification. Should be receipts, workorders or delivery. task-3703643 closes odoo/odoo#153147 Signed-off-by: Steve Van Essche <svs@odoo.com>
2647 lines
138 KiB
Python
2647 lines
138 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from odoo.addons.stock.tests.common import TestStockCommon
|
|
from odoo.exceptions import ValidationError
|
|
from odoo.tests import Form, tagged
|
|
from odoo.tools import mute_logger, float_round
|
|
from odoo import fields
|
|
|
|
|
|
class TestStockFlow(TestStockCommon):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.env.ref('base.group_user').write({'implied_ids': [(4, cls.env.ref('stock.group_production_lot').id)]})
|
|
decimal_product_uom = cls.env.ref('product.decimal_product_uom')
|
|
decimal_product_uom.digits = 3
|
|
cls.partner_company2 = cls.env['res.partner'].create({
|
|
'name': 'My Company (Chicago)-demo',
|
|
'email': 'chicago@yourcompany.com',
|
|
'company_id': False,
|
|
})
|
|
cls.company = cls.env['res.company'].create({
|
|
'currency_id': cls.env.ref('base.USD').id,
|
|
'partner_id': cls.partner_company2.id,
|
|
'name': 'My Company (Chicago)-demo',
|
|
})
|
|
|
|
@mute_logger('odoo.addons.base.models.ir_model', 'odoo.models')
|
|
def test_00_picking_create_and_transfer_quantity(self):
|
|
""" Basic stock operation on incoming and outgoing shipment. """
|
|
LotObj = self.env['stock.lot']
|
|
# ----------------------------------------------------------------------
|
|
# Create incoming shipment of product A, B, C, D
|
|
# ----------------------------------------------------------------------
|
|
# Product A ( 1 Unit ) , Product C ( 10 Unit )
|
|
# Product B ( 1 Unit ) , Product D ( 10 Unit )
|
|
# Product D ( 5 Unit )
|
|
# ----------------------------------------------------------------------
|
|
|
|
picking_in = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_in,
|
|
'location_id': self.supplier_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.stock_location})
|
|
move_a = self.MoveObj.create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 1,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking_in.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
move_b = self.MoveObj.create({
|
|
'name': self.productB.name,
|
|
'product_id': self.productB.id,
|
|
'product_uom_qty': 1,
|
|
'product_uom': self.productB.uom_id.id,
|
|
'picking_id': picking_in.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
move_c = self.MoveObj.create({
|
|
'name': self.productC.name,
|
|
'product_id': self.productC.id,
|
|
'product_uom_qty': 10,
|
|
'product_uom': self.productC.uom_id.id,
|
|
'picking_id': picking_in.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
move_d = self.MoveObj.create({
|
|
'name': self.productD.name,
|
|
'product_id': self.productD.id,
|
|
'product_uom_qty': 10,
|
|
'product_uom': self.productD.uom_id.id,
|
|
'picking_id': picking_in.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
self.MoveObj.create({
|
|
'name': self.productD.name,
|
|
'product_id': self.productD.id,
|
|
'product_uom_qty': 5,
|
|
'product_uom': self.productD.uom_id.id,
|
|
'picking_id': picking_in.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
|
|
# Check incoming shipment move lines state.
|
|
for move in picking_in.move_ids:
|
|
self.assertEqual(move.state, 'draft', 'Wrong state of move line.')
|
|
# Confirm incoming shipment.
|
|
picking_in.action_confirm()
|
|
# Check incoming shipment move lines state.
|
|
for move in picking_in.move_ids:
|
|
self.assertEqual(move.state, 'assigned', 'Wrong state of move line.')
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Replace pack operation of incoming shipments.
|
|
# ----------------------------------------------------------------------
|
|
picking_in.action_assign()
|
|
move_a.move_line_ids.quantity = 4
|
|
move_b.move_line_ids.quantity = 5
|
|
move_c.move_line_ids.quantity = 5
|
|
move_d.move_line_ids.quantity = 5
|
|
(move_a | move_b | move_c | move_d).picked = True
|
|
lot2_productC = LotObj.create({'name': 'C Lot 2', 'product_id': self.productC.id, 'company_id': self.env.company.id})
|
|
self.StockPackObj.create({
|
|
'product_id': self.productC.id,
|
|
'quantity': 2,
|
|
'product_uom_id': self.productC.uom_id.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location,
|
|
'move_id': move_c.id,
|
|
'lot_id': lot2_productC.id,
|
|
})
|
|
self.StockPackObj.create({
|
|
'product_id': self.productD.id,
|
|
'quantity': 2,
|
|
'product_uom_id': self.productD.uom_id.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location,
|
|
'move_id': move_d.id
|
|
})
|
|
|
|
# Check incoming shipment total quantity of pack operation
|
|
total_qty = sum(self.StockPackObj.search([('move_id', 'in', picking_in.move_ids.ids)]).mapped('quantity'))
|
|
self.assertEqual(total_qty, 23, 'Wrong quantity in pack operation')
|
|
|
|
# Transfer Incoming Shipment.
|
|
picking_in._action_done()
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Check state, quantity and total moves of incoming shipment.
|
|
# ----------------------------------------------------------------------
|
|
|
|
# Check total no of move lines of incoming shipment. move line e disappear from original picking to go in backorder.
|
|
self.assertEqual(len(picking_in.move_ids), 4, 'Wrong number of move lines.')
|
|
# Check incoming shipment state.
|
|
self.assertEqual(picking_in.state, 'done', 'Incoming shipment state should be done.')
|
|
# Check incoming shipment move lines state.
|
|
for move in picking_in.move_ids:
|
|
self.assertEqual(move.state, 'done', 'Wrong state of move line.')
|
|
# Check product A done quantity must be 3 and 1
|
|
moves = self.MoveObj.search([('product_id', '=', self.productA.id), ('picking_id', '=', picking_in.id)])
|
|
self.assertEqual(moves.quantity, 4.0, 'Wrong move quantity for product A.')
|
|
# Check product B done quantity must be 4 and 1
|
|
moves = self.MoveObj.search([('product_id', '=', self.productB.id), ('picking_id', '=', picking_in.id)])
|
|
self.assertEqual(moves.quantity, 5.0, 'Wrong move quantity for product B.')
|
|
# Check product C done quantity must be 7
|
|
c_done_qty = self.MoveObj.search([('product_id', '=', self.productC.id), ('picking_id', '=', picking_in.id)], limit=1).product_uom_qty
|
|
self.assertEqual(c_done_qty, 7.0, 'Wrong move quantity of product C (%s found instead of 7)' % (c_done_qty))
|
|
# Check product D done quantity must be 7
|
|
d_done_qty = self.MoveObj.search([('product_id', '=', self.productD.id), ('picking_id', '=', picking_in.id)], limit=1).product_uom_qty
|
|
self.assertEqual(d_done_qty, 7.0, 'Wrong move quantity of product D (%s found instead of 7)' % (d_done_qty))
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Check Back order of Incoming shipment.
|
|
# ----------------------------------------------------------------------
|
|
|
|
# Check back order created or not.
|
|
back_order_in = self.PickingObj.search([('backorder_id', '=', picking_in.id)])
|
|
self.assertEqual(len(back_order_in), 1, 'Back order should be created.')
|
|
# Check total move lines of back order.
|
|
self.assertEqual(len(back_order_in.move_ids), 2, 'Wrong number of move lines.')
|
|
# Check back order should be created with 3 quantity of product C.
|
|
moves = self.MoveObj.search([('product_id', '=', self.productC.id), ('picking_id', '=', back_order_in.id)])
|
|
product_c_qty = [move.product_uom_qty for move in moves]
|
|
self.assertEqual(sum(product_c_qty), 3.0, 'Wrong move quantity of product C (%s found instead of 3)' % (product_c_qty))
|
|
# Check back order should be created with 8 quantity of product D.
|
|
moves = self.MoveObj.search([('product_id', '=', self.productD.id), ('picking_id', '=', back_order_in.id)])
|
|
product_d_qty = [move.product_uom_qty for move in moves]
|
|
self.assertEqual(sum(product_d_qty), 8.0, 'Wrong move quantity of product D (%s found instead of 8)' % (product_d_qty))
|
|
|
|
# ======================================================================
|
|
# Create Outgoing shipment with ...
|
|
# product A ( 10 Unit ) , product B ( 5 Unit )
|
|
# product C ( 3 unit ) , product D ( 10 Unit )
|
|
# ======================================================================
|
|
|
|
picking_out = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_out,
|
|
'location_id': self.stock_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.customer_location})
|
|
move_cust_a = self.MoveObj.create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 10,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
move_cust_b = self.MoveObj.create({
|
|
'name': self.productB.name,
|
|
'product_id': self.productB.id,
|
|
'product_uom_qty': 5,
|
|
'product_uom': self.productB.uom_id.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
move_cust_c = self.MoveObj.create({
|
|
'name': self.productC.name,
|
|
'product_id': self.productC.id,
|
|
'product_uom_qty': 3,
|
|
'product_uom': self.productC.uom_id.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
move_cust_d = self.MoveObj.create({
|
|
'name': self.productD.name,
|
|
'product_id': self.productD.id,
|
|
'product_uom_qty': 10,
|
|
'product_uom': self.productD.uom_id.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
# Confirm outgoing shipment.
|
|
picking_out.action_confirm()
|
|
for move in picking_out.move_ids:
|
|
self.assertEqual(move.state, 'confirmed', 'Wrong state of move line.')
|
|
# Product assign to outgoing shipments
|
|
picking_out.action_assign()
|
|
self.assertEqual(move_cust_a.state, 'partially_available', 'Wrong state of move line.')
|
|
self.assertEqual(move_cust_b.state, 'assigned', 'Wrong state of move line.')
|
|
self.assertEqual(move_cust_c.state, 'assigned', 'Wrong state of move line.')
|
|
self.assertEqual(move_cust_d.state, 'partially_available', 'Wrong state of move line.')
|
|
# Check availability for product A
|
|
aval_a_qty = self.MoveObj.search([('product_id', '=', self.productA.id), ('picking_id', '=', picking_out.id)], limit=1).quantity
|
|
self.assertEqual(aval_a_qty, 4.0, 'Wrong move quantity availability of product A (%s found instead of 4)' % (aval_a_qty))
|
|
# Check availability for product B
|
|
aval_b_qty = self.MoveObj.search([('product_id', '=', self.productB.id), ('picking_id', '=', picking_out.id)], limit=1).quantity
|
|
self.assertEqual(aval_b_qty, 5.0, 'Wrong move quantity availability of product B (%s found instead of 5)' % (aval_b_qty))
|
|
# Check availability for product C
|
|
aval_c_qty = self.MoveObj.search([('product_id', '=', self.productC.id), ('picking_id', '=', picking_out.id)], limit=1).quantity
|
|
self.assertEqual(aval_c_qty, 3.0, 'Wrong move quantity availability of product C (%s found instead of 3)' % (aval_c_qty))
|
|
# Check availability for product D
|
|
aval_d_qty = self.MoveObj.search([('product_id', '=', self.productD.id), ('picking_id', '=', picking_out.id)], limit=1).quantity
|
|
self.assertEqual(aval_d_qty, 7.0, 'Wrong move quantity availability of product D (%s found instead of 7)' % (aval_d_qty))
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Replace pack operation of outgoing shipment.
|
|
# ----------------------------------------------------------------------
|
|
|
|
move_cust_a.move_line_ids.quantity = 2.0
|
|
move_cust_b.move_line_ids.quantity = 3.0
|
|
self.StockPackObj.create({
|
|
'product_id': self.productB.id,
|
|
'quantity': 2,
|
|
'product_uom_id': self.productB.uom_id.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location,
|
|
'move_id': move_cust_b.id})
|
|
# TODO care if product_qty and lot_id are set at the same times the system do 2 unreserve.
|
|
move_cust_c.move_line_ids[0].write({
|
|
'quantity': 2.0,
|
|
'lot_id': lot2_productC.id,
|
|
})
|
|
move_cust_c.move_line_ids[1].write({
|
|
'quantity': 3.0,
|
|
})
|
|
move_cust_d.move_line_ids.quantity = 6.0
|
|
|
|
# Transfer picking.
|
|
(move_cust_a | move_cust_b | move_cust_c | move_cust_d).picked = True
|
|
picking_out._action_done()
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Check state, quantity and total moves of outgoing shipment.
|
|
# ----------------------------------------------------------------------
|
|
|
|
# check outgoing shipment status.
|
|
self.assertEqual(picking_out.state, 'done', 'Wrong state of outgoing shipment.')
|
|
# check outgoing shipment total moves and and its state.
|
|
self.assertEqual(len(picking_out.move_ids), 4, 'Wrong number of move lines')
|
|
for move in picking_out.move_ids:
|
|
self.assertEqual(move.state, 'done', 'Wrong state of move line.')
|
|
back_order_out = self.PickingObj.search([('backorder_id', '=', picking_out.id)])
|
|
|
|
# ------------------
|
|
# Check back order.
|
|
# -----------------
|
|
|
|
self.assertEqual(len(back_order_out), 1, 'Back order should be created.')
|
|
# Check total move lines of back order.
|
|
self.assertEqual(len(back_order_out.move_ids), 2, 'Wrong number of move lines')
|
|
# Check back order should be created with 8 quantity of product A.
|
|
product_a_qty = self.MoveObj.search([('product_id', '=', self.productA.id), ('picking_id', '=', back_order_out.id)], limit=1).product_uom_qty
|
|
self.assertEqual(product_a_qty, 8.0, 'Wrong move quantity of product A (%s found instead of 8)' % (product_a_qty))
|
|
# Check back order should be created with 4 quantity of product D.
|
|
product_d_qty = self.MoveObj.search([('product_id', '=', self.productD.id), ('picking_id', '=', back_order_out.id)], limit=1).product_uom_qty
|
|
self.assertEqual(product_d_qty, 4.0, 'Wrong move quantity of product D (%s found instead of 4)' % (product_d_qty))
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Check stock location quant quantity and quantity available
|
|
# of product A, B, C, D
|
|
# -----------------------------------------------------------------------
|
|
|
|
# Check quants and available quantity for product A
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.productA.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
|
|
self.assertEqual(sum(total_qty), 2.0, 'Expecting 2.0 Unit , got %.4f Unit on location stock!' % (sum(total_qty)))
|
|
self.assertEqual(self.productA.qty_available, 2.0, 'Wrong quantity available (%s found instead of 2.0)' % (self.productA.qty_available))
|
|
# Check quants and available quantity for product B
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.productB.id), ('location_id', '=', self.stock_location), ('quantity', '!=', 0)])
|
|
self.assertFalse(quants, 'No quant should found as outgoing shipment took everything out of stock.')
|
|
self.assertEqual(self.productB.qty_available, 0.0, 'Product B should have zero quantity available.')
|
|
# Check quants and available quantity for product C
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.productC.id), ('location_id', '=', self.stock_location), ('quantity', '!=', 0)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(sum(total_qty), 2.0, 'Expecting 2.0 Unit, got %.4f Unit on location stock!' % (sum(total_qty)))
|
|
self.assertEqual(self.productC.qty_available, 2.0, 'Wrong quantity available (%s found instead of 2.0)' % (self.productC.qty_available))
|
|
# Check quants and available quantity for product D
|
|
quant = self.StockQuantObj.search([('product_id', '=', self.productD.id), ('location_id', '=', self.stock_location), ('quantity', '!=', 0)], limit=1)
|
|
self.assertEqual(quant.quantity, 1.0, 'Expecting 1.0 Unit , got %.4f Unit on location stock!' % (quant.quantity))
|
|
self.assertEqual(self.productD.qty_available, 1.0, 'Wrong quantity available (%s found instead of 1.0)' % (self.productD.qty_available))
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Back Order of Incoming shipment
|
|
# -----------------------------------------------------------------------
|
|
|
|
lot3_productC = LotObj.create({'name': 'Lot 3', 'product_id': self.productC.id, 'company_id': self.env.company.id})
|
|
lot4_productC = LotObj.create({'name': 'Lot 4', 'product_id': self.productC.id, 'company_id': self.env.company.id})
|
|
lot5_productC = LotObj.create({'name': 'Lot 5', 'product_id': self.productC.id, 'company_id': self.env.company.id})
|
|
lot6_productC = LotObj.create({'name': 'Lot 6', 'product_id': self.productC.id, 'company_id': self.env.company.id})
|
|
lot1_productD = LotObj.create({'name': 'Lot 1', 'product_id': self.productD.id, 'company_id': self.env.company.id})
|
|
LotObj.create({'name': 'Lot 2', 'product_id': self.productD.id, 'company_id': self.env.company.id})
|
|
|
|
# Confirm back order of incoming shipment.
|
|
back_order_in.action_confirm()
|
|
self.assertEqual(back_order_in.state, 'assigned', 'Wrong state of incoming shipment back order: %s instead of %s' % (back_order_in.state, 'assigned'))
|
|
for move in back_order_in.move_ids:
|
|
self.assertEqual(move.state, 'assigned', 'Wrong state of move line.')
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Replace pack operation (Back order of Incoming shipment)
|
|
# ----------------------------------------------------------------------
|
|
|
|
packD = self.StockPackObj.search([('product_id', '=', self.productD.id), ('picking_id', '=', back_order_in.id)], order='quantity')
|
|
self.assertEqual(len(packD), 1, 'Wrong number of pack operation.')
|
|
packD[0].write({
|
|
'quantity': 8,
|
|
'lot_id': lot1_productD.id,
|
|
})
|
|
packCs = self.StockPackObj.search([('product_id', '=', self.productC.id), ('picking_id', '=', back_order_in.id)], limit=1)
|
|
packCs.write({
|
|
'quantity': 1,
|
|
'lot_id': lot3_productC.id,
|
|
})
|
|
self.StockPackObj.create({
|
|
'product_id': self.productC.id,
|
|
'quantity': 1,
|
|
'product_uom_id': self.productC.uom_id.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location,
|
|
'picking_id': back_order_in.id,
|
|
'lot_id': lot4_productC.id,
|
|
})
|
|
self.StockPackObj.create({
|
|
'product_id': self.productC.id,
|
|
'quantity': 2,
|
|
'product_uom_id': self.productC.uom_id.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location,
|
|
'picking_id': back_order_in.id,
|
|
'lot_id': lot5_productC.id,
|
|
})
|
|
self.StockPackObj.create({
|
|
'product_id': self.productC.id,
|
|
'quantity': 2,
|
|
'product_uom_id': self.productC.uom_id.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location,
|
|
'picking_id': back_order_in.id,
|
|
'lot_id': lot6_productC.id,
|
|
})
|
|
self.StockPackObj.create({
|
|
'product_id': self.productA.id,
|
|
'quantity': 10,
|
|
'product_uom_id': self.productA.uom_id.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location,
|
|
'picking_id': back_order_in.id
|
|
})
|
|
back_order_in.move_ids.picked = True
|
|
back_order_in._action_done()
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Check state, quantity and total moves (Back order of Incoming shipment).
|
|
# ----------------------------------------------------------------------
|
|
|
|
# Check total no of move lines.
|
|
self.assertEqual(len(back_order_in.move_ids), 3, 'Wrong number of move lines')
|
|
# Check incoming shipment state must be 'Done'.
|
|
self.assertEqual(back_order_in.state, 'done', 'Wrong state of picking.')
|
|
# Check incoming shipment move lines state must be 'Done'.
|
|
for move in back_order_in.move_ids:
|
|
self.assertEqual(move.state, 'done', 'Wrong state of move lines.')
|
|
# Check product A done quantity must be 10
|
|
movesA = self.MoveObj.search([('product_id', '=', self.productA.id), ('picking_id', '=', back_order_in.id)])
|
|
self.assertEqual(movesA.quantity, 10, "Wrong move quantity of product A (%s found instead of 10)" % (movesA.quantity))
|
|
# Check product C done quantity must be 3.0, 1.0, 2.0
|
|
movesC = self.MoveObj.search([('product_id', '=', self.productC.id), ('picking_id', '=', back_order_in.id)])
|
|
self.assertEqual(movesC.quantity, 6.0, 'Wrong quantity of moves product C.')
|
|
# Check product D done quantity must be 5.0 and 3.0
|
|
movesD = self.MoveObj.search([('product_id', '=', self.productD.id), ('picking_id', '=', back_order_in.id)])
|
|
d_done_qty = [move.quantity for move in movesD]
|
|
self.assertEqual(set(d_done_qty), set([8.0]), 'Wrong quantity of moves product D.')
|
|
# Check no back order is created.
|
|
self.assertFalse(self.PickingObj.search([('backorder_id', '=', back_order_in.id)]), "Should not create any back order.")
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Check stock location quant quantity and quantity available
|
|
# of product A, B, C, D
|
|
# -----------------------------------------------------------------------
|
|
|
|
# Check quants and available quantity for product A.
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.productA.id), ('location_id', '=', self.stock_location), ('quantity', '!=', 0)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(sum(total_qty), 12.0, 'Wrong total stock location quantity (%s found instead of 12)' % (sum(total_qty)))
|
|
self.assertEqual(self.productA.qty_available, 12.0, 'Wrong quantity available (%s found instead of 12)' % (self.productA.qty_available))
|
|
# Check quants and available quantity for product B.
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.productB.id), ('location_id', '=', self.stock_location), ('quantity', '!=', 0)])
|
|
self.assertFalse(quants, 'No quant should found as outgoing shipment took everything out of stock')
|
|
self.assertEqual(self.productB.qty_available, 0.0, 'Total quantity in stock should be 0 as the backorder took everything out of stock')
|
|
# Check quants and available quantity for product C.
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.productC.id), ('location_id', '=', self.stock_location), ('quantity', '!=', 0)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(sum(total_qty), 8.0, 'Wrong total stock location quantity (%s found instead of 8)' % (sum(total_qty)))
|
|
self.assertEqual(self.productC.qty_available, 8.0, 'Wrong quantity available (%s found instead of 8)' % (self.productC.qty_available))
|
|
# Check quants and available quantity for product D.
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.productD.id), ('location_id', '=', self.stock_location), ('quantity', '!=', 0)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(sum(total_qty), 9.0, 'Wrong total stock location quantity (%s found instead of 9)' % (sum(total_qty)))
|
|
self.assertEqual(self.productD.qty_available, 9.0, 'Wrong quantity available (%s found instead of 9)' % (self.productD.qty_available))
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Back order of Outgoing shipment
|
|
# ----------------------------------------------------------------------
|
|
|
|
back_order_out._action_done()
|
|
|
|
# Check stock location quants and available quantity for product A.
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.productA.id), ('location_id', '=', self.stock_location), ('quantity', '!=', 0)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertGreaterEqual(float_round(sum(total_qty), precision_rounding=0.0001), 1, 'Total stock location quantity for product A should not be nagative.')
|
|
|
|
def test_10_pickings_transfer_with_different_uom(self):
|
|
""" Picking transfer with diffrent unit of meassure. """
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Create incoming shipment of products DozA, SDozA, SDozARound, kgB, gB
|
|
# ----------------------------------------------------------------------
|
|
# DozA ( 10 Dozen ) , SDozA ( 10.5 SuperDozen )
|
|
# SDozARound ( 10.5 10.5 SuperDozenRound ) , kgB ( 0.020 kg )
|
|
# gB ( 525.3 g )
|
|
# ----------------------------------------------------------------------
|
|
|
|
picking_in_A = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_in,
|
|
'location_id': self.supplier_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.stock_location})
|
|
self.MoveObj.create({
|
|
'name': self.DozA.name,
|
|
'product_id': self.DozA.id,
|
|
'product_uom_qty': 10,
|
|
'product_uom': self.DozA.uom_id.id,
|
|
'picking_id': picking_in_A.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
self.MoveObj.create({
|
|
'name': self.SDozA.name,
|
|
'product_id': self.SDozA.id,
|
|
'product_uom_qty': 10.5,
|
|
'product_uom': self.SDozA.uom_id.id,
|
|
'picking_id': picking_in_A.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
self.MoveObj.create({
|
|
'name': self.SDozARound.name,
|
|
'product_id': self.SDozARound.id,
|
|
'product_uom_qty': 10.5,
|
|
'product_uom': self.SDozARound.uom_id.id,
|
|
'picking_id': picking_in_A.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
self.MoveObj.create({
|
|
'name': self.kgB.name,
|
|
'product_id': self.kgB.id,
|
|
'product_uom_qty': 0.020,
|
|
'product_uom': self.kgB.uom_id.id,
|
|
'picking_id': picking_in_A.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
self.MoveObj.create({
|
|
'name': self.gB.name,
|
|
'product_id': self.gB.id,
|
|
'product_uom_qty': 525.3,
|
|
'product_uom': self.gB.uom_id.id,
|
|
'picking_id': picking_in_A.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
|
|
# Check incoming shipment move lines state.
|
|
for move in picking_in_A.move_ids:
|
|
self.assertEqual(move.state, 'draft', 'Move state must be draft.')
|
|
# Confirm incoming shipment.
|
|
picking_in_A.action_confirm()
|
|
# Check incoming shipment move lines state.
|
|
for move in picking_in_A.move_ids:
|
|
self.assertEqual(move.state, 'assigned', 'Move state must be draft.')
|
|
|
|
# ----------------------------------------------------
|
|
# Check pack operation quantity of incoming shipments.
|
|
# ----------------------------------------------------
|
|
|
|
PackSdozAround = self.StockPackObj.search([('product_id', '=', self.SDozARound.id), ('picking_id', '=', picking_in_A.id)], limit=1)
|
|
self.assertEqual(PackSdozAround.quantity, 11, 'Wrong quantity in pack operation (%s found instead of 11)' % (PackSdozAround.quantity))
|
|
picking_in_A.button_validate()
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Check stock location quant quantity and quantity available
|
|
# -----------------------------------------------------------------------
|
|
|
|
# Check quants and available quantity for product DozA
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.DozA.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(sum(total_qty), 10, 'Expecting 10 Dozen , got %.4f Dozen on location stock!' % (sum(total_qty)))
|
|
self.assertEqual(self.DozA.qty_available, 10, 'Wrong quantity available (%s found instead of 10)' % (self.DozA.qty_available))
|
|
# Check quants and available quantity for product SDozA
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.SDozA.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(sum(total_qty), 10.5, 'Expecting 10.5 SDozen , got %.4f SDozen on location stock!' % (sum(total_qty)))
|
|
self.assertEqual(self.SDozA.qty_available, 10.5, 'Wrong quantity available (%s found instead of 10.5)' % (self.SDozA.qty_available))
|
|
# Check quants and available quantity for product SDozARound
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.SDozARound.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(sum(total_qty), 11, 'Expecting 11 SDozenRound , got %.4f SDozenRound on location stock!' % (sum(total_qty)))
|
|
self.assertEqual(self.SDozARound.qty_available, 11, 'Wrong quantity available (%s found instead of 11)' % (self.SDozARound.qty_available))
|
|
# Check quants and available quantity for product gB
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.gB.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertAlmostEqual(sum(total_qty), 525.3, msg='Expecting 525.3 gram , got %.4f gram on location stock!' % (sum(total_qty)))
|
|
self.assertAlmostEqual(self.gB.qty_available, 525.3, msg='Wrong quantity available (%s found instead of 525.3' % (self.gB.qty_available))
|
|
# Check quants and available quantity for product kgB
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.kgB.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(sum(total_qty), 0.020, 'Expecting 0.020 kg , got %.4f kg on location stock!' % (sum(total_qty)))
|
|
self.assertEqual(self.kgB.qty_available, 0.020, 'Wrong quantity available (%s found instead of 0.020)' % (self.kgB.qty_available))
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Create Incoming Shipment B
|
|
# ----------------------------------------------------------------------
|
|
|
|
picking_in_B = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_in,
|
|
'location_id': self.supplier_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.stock_location})
|
|
move_in_a = self.MoveObj.create({
|
|
'name': self.DozA.name,
|
|
'product_id': self.DozA.id,
|
|
'product_uom_qty': 120,
|
|
'product_uom': self.uom_unit.id,
|
|
'picking_id': picking_in_B.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
self.MoveObj.create({
|
|
'name': self.SDozA.name,
|
|
'product_id': self.SDozA.id,
|
|
'product_uom_qty': 1512,
|
|
'product_uom': self.uom_unit.id,
|
|
'picking_id': picking_in_B.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
self.MoveObj.create({
|
|
'name': self.SDozARound.name,
|
|
'product_id': self.SDozARound.id,
|
|
'product_uom_qty': 1584,
|
|
'product_uom': self.uom_unit.id,
|
|
'picking_id': picking_in_B.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
self.MoveObj.create({
|
|
'name': self.kgB.name,
|
|
'product_id': self.kgB.id,
|
|
'product_uom_qty': 20.0,
|
|
'product_uom': self.uom_gm.id,
|
|
'picking_id': picking_in_B.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
self.MoveObj.create({
|
|
'name': self.gB.name,
|
|
'product_id': self.gB.id,
|
|
'product_uom_qty': 0.525,
|
|
'product_uom': self.uom_kg.id,
|
|
'picking_id': picking_in_B.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
|
|
# Check incoming shipment move lines state.
|
|
for move in picking_in_B.move_ids:
|
|
self.assertEqual(move.state, 'draft', 'Wrong state of move line.')
|
|
# Confirm incoming shipment.
|
|
picking_in_B.action_confirm()
|
|
# Check incoming shipment move lines state.
|
|
for move in picking_in_B.move_ids:
|
|
self.assertEqual(move.state, 'assigned', 'Wrong state of move line.')
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Check product quantity and unit of measure of pack operaation.
|
|
# ----------------------------------------------------------------------
|
|
|
|
# Check pack operation quantity and unit of measure for product DozA.
|
|
PackdozA = self.StockPackObj.search([('product_id', '=', self.DozA.id), ('picking_id', '=', picking_in_B.id)], limit=1)
|
|
self.assertEqual(PackdozA.quantity, 120, 'Wrong quantity in pack operation (%s found instead of 120)' % (PackdozA.quantity))
|
|
self.assertEqual(PackdozA.quantity_product_uom, 10, 'Wrong real quantity in pack operation (%s found instead of 10)' % (PackdozA.quantity_product_uom))
|
|
self.assertEqual(PackdozA.product_uom_id.id, self.uom_unit.id, 'Wrong uom in pack operation for product DozA.')
|
|
# Check pack operation quantity and unit of measure for product SDozA.
|
|
PackSdozA = self.StockPackObj.search([('product_id', '=', self.SDozA.id), ('picking_id', '=', picking_in_B.id)], limit=1)
|
|
self.assertEqual(PackSdozA.quantity, 1512, 'Wrong quantity in pack operation (%s found instead of 1512)' % (PackSdozA.quantity))
|
|
self.assertEqual(PackSdozA.product_uom_id.id, self.uom_unit.id, 'Wrong uom in pack operation for product SDozA.')
|
|
# Check pack operation quantity and unit of measure for product SDozARound.
|
|
PackSdozAround = self.StockPackObj.search([('product_id', '=', self.SDozARound.id), ('picking_id', '=', picking_in_B.id)], limit=1)
|
|
self.assertEqual(PackSdozAround.quantity, 1584, 'Wrong quantity in pack operation (%s found instead of 1584)' % (PackSdozAround.quantity))
|
|
self.assertEqual(PackSdozAround.product_uom_id.id, self.uom_unit.id, 'Wrong uom in pack operation for product SDozARound.')
|
|
# Check pack operation quantity and unit of measure for product gB.
|
|
packgB = self.StockPackObj.search([('product_id', '=', self.gB.id), ('picking_id', '=', picking_in_B.id)], limit=1)
|
|
self.assertEqual(packgB.quantity, 0.525, 'Wrong quantity in pack operation (%s found instead of 0.525)' % (packgB.quantity))
|
|
self.assertEqual(packgB.quantity_product_uom, 525, 'Wrong real quantity in pack operation (%s found instead of 525)' % (packgB.quantity_product_uom))
|
|
self.assertEqual(packgB.product_uom_id.id, packgB.move_id.product_uom.id, 'Wrong uom in pack operation for product kgB.')
|
|
# Check pack operation quantity and unit of measure for product kgB.
|
|
packkgB = self.StockPackObj.search([('product_id', '=', self.kgB.id), ('picking_id', '=', picking_in_B.id)], limit=1)
|
|
self.assertEqual(packkgB.quantity, 20.0, 'Wrong quantity in pack operation (%s found instead of 20)' % (packkgB.quantity))
|
|
self.assertEqual(packkgB.product_uom_id.id, self.uom_gm.id, 'Wrong uom in pack operation for product kgB')
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Replace pack operation of incoming shipment.
|
|
# ----------------------------------------------------------------------
|
|
|
|
self.StockPackObj.search([('product_id', '=', self.kgB.id), ('picking_id', '=', picking_in_B.id)]).write({
|
|
'quantity': 0.020, 'product_uom_id': self.uom_kg.id})
|
|
self.StockPackObj.search([('product_id', '=', self.gB.id), ('picking_id', '=', picking_in_B.id)]).write({
|
|
'quantity': 526, 'product_uom_id': self.uom_gm.id})
|
|
self.StockPackObj.search([('product_id', '=', self.DozA.id), ('picking_id', '=', picking_in_B.id)]).write({
|
|
'quantity': 4, 'product_uom_id': self.uom_dozen.id})
|
|
self.StockPackObj.create({
|
|
'product_id': self.DozA.id,
|
|
'quantity': 48,
|
|
'product_uom_id': self.uom_unit.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location,
|
|
'move_id': move_in_a.id
|
|
})
|
|
|
|
# -----------------
|
|
# Transfer product.
|
|
# -----------------
|
|
|
|
res_dict_for_back_order = picking_in_B.button_validate()
|
|
backorder_wizard = self.env[(res_dict_for_back_order.get('res_model'))].browse(res_dict_for_back_order.get('res_id')).with_context(res_dict_for_back_order['context'])
|
|
backorder_wizard.process()
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Check incoming shipment
|
|
# -----------------------------------------------------------------------
|
|
# Check incoming shipment state.
|
|
self.assertEqual(picking_in_B.state, 'done', 'Incoming shipment state should be done.')
|
|
# Check incoming shipment move lines state.
|
|
for move in picking_in_B.move_ids:
|
|
self.assertEqual(move.state, 'done', 'Wrong state of move line.')
|
|
# Check total done move lines for incoming shipment.
|
|
self.assertEqual(len(picking_in_B.move_ids), 5, 'Wrong number of move lines')
|
|
# Check product DozA done quantity.
|
|
moves_DozA = self.MoveObj.search([('product_id', '=', self.DozA.id), ('picking_id', '=', picking_in_B.id)], limit=1)
|
|
self.assertEqual(moves_DozA.quantity, 96, 'Wrong move quantity (%s found instead of 96)' % (moves_DozA.product_uom_qty))
|
|
self.assertEqual(moves_DozA.product_uom.id, self.uom_unit.id, 'Wrong uom in move for product DozA.')
|
|
# Check product SDozA done quantity.
|
|
moves_SDozA = self.MoveObj.search([('product_id', '=', self.SDozA.id), ('picking_id', '=', picking_in_B.id)], limit=1)
|
|
self.assertEqual(moves_SDozA.quantity, 1512, 'Wrong move quantity (%s found instead of 1512)' % (moves_SDozA.product_uom_qty))
|
|
self.assertEqual(moves_SDozA.product_uom.id, self.uom_unit.id, 'Wrong uom in move for product SDozA.')
|
|
# Check product SDozARound done quantity.
|
|
moves_SDozARound = self.MoveObj.search([('product_id', '=', self.SDozARound.id), ('picking_id', '=', picking_in_B.id)], limit=1)
|
|
self.assertEqual(moves_SDozARound.quantity, 1584, 'Wrong move quantity (%s found instead of 1584)' % (moves_SDozARound.product_uom_qty))
|
|
self.assertEqual(moves_SDozARound.product_uom.id, self.uom_unit.id, 'Wrong uom in move for product SDozARound.')
|
|
# Check product kgB done quantity.
|
|
moves_kgB = self.MoveObj.search([('product_id', '=', self.kgB.id), ('picking_id', '=', picking_in_B.id)], limit=1)
|
|
self.assertEqual(moves_kgB.quantity, 20, 'Wrong quantity in move (%s found instead of 20)' % (moves_kgB.product_uom_qty))
|
|
self.assertEqual(moves_kgB.product_uom.id, self.uom_gm.id, 'Wrong uom in move for product kgB.')
|
|
# Check two moves created for product gB with quantity (0.525 kg and 0.3 g)
|
|
moves_gB_kg = self.MoveObj.search([('product_id', '=', self.gB.id), ('picking_id', '=', picking_in_B.id), ('product_uom', '=', self.uom_kg.id)], limit=1)
|
|
self.assertEqual(moves_gB_kg.quantity, 0.526, 'Wrong move quantity (%s found instead of 0.526)' % (moves_gB_kg.product_uom_qty))
|
|
self.assertEqual(moves_gB_kg.product_uom.id, self.uom_kg.id, 'Wrong uom in move for product gB.')
|
|
|
|
# TODO Test extra move once the uom is editable in the move_lines
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Check Back order of Incoming shipment.
|
|
# ----------------------------------------------------------------------
|
|
|
|
# Check back order created or not.
|
|
bo_in_B = self.PickingObj.search([('backorder_id', '=', picking_in_B.id)])
|
|
self.assertEqual(len(bo_in_B), 1, 'Back order should be created.')
|
|
# Check total move lines of back order.
|
|
self.assertEqual(len(bo_in_B.move_ids), 1, 'Wrong number of move lines')
|
|
# Check back order created with correct quantity and uom or not.
|
|
moves_DozA = self.MoveObj.search([('product_id', '=', self.DozA.id), ('picking_id', '=', bo_in_B.id)], limit=1)
|
|
self.assertEqual(moves_DozA.product_uom_qty, 24.0, 'Wrong move quantity (%s found instead of 0.525)' % (moves_DozA.product_uom_qty))
|
|
self.assertEqual(moves_DozA.product_uom.id, self.uom_unit.id, 'Wrong uom in move for product DozA.')
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Check product stock location quantity and quantity available.
|
|
# ----------------------------------------------------------------------
|
|
|
|
# Check quants and available quantity for product DozA
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.DozA.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(sum(total_qty), 18, 'Expecting 18 Dozen , got %.4f Dozen on location stock!' % (sum(total_qty)))
|
|
self.assertEqual(self.DozA.qty_available, 18, 'Wrong quantity available (%s found instead of 18)' % (self.DozA.qty_available))
|
|
# Check quants and available quantity for product SDozA
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.SDozA.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(sum(total_qty), 21, 'Expecting 21 SDozen , got %.4f SDozen on location stock!' % (sum(total_qty)))
|
|
self.assertEqual(self.SDozA.qty_available, 21, 'Wrong quantity available (%s found instead of 21)' % (self.SDozA.qty_available))
|
|
# Check quants and available quantity for product SDozARound
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.SDozARound.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(sum(total_qty), 22, 'Expecting 22 SDozenRound , got %.4f SDozenRound on location stock!' % (sum(total_qty)))
|
|
self.assertEqual(self.SDozARound.qty_available, 22, 'Wrong quantity available (%s found instead of 22)' % (self.SDozARound.qty_available))
|
|
# Check quants and available quantity for product gB.
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.gB.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(round(sum(total_qty), 1), 1051.3, 'Expecting 1051 Gram , got %.4f Gram on location stock!' % (sum(total_qty)))
|
|
self.assertEqual(round(self.gB.qty_available, 1), 1051.3, 'Wrong quantity available (%s found instead of 1051)' % (self.gB.qty_available))
|
|
# Check quants and available quantity for product kgB.
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.kgB.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(sum(total_qty), 0.040, 'Expecting 0.040 kg , got %.4f kg on location stock!' % (sum(total_qty)))
|
|
self.assertEqual(self.kgB.qty_available, 0.040, 'Wrong quantity available (%s found instead of 0.040)' % (self.kgB.qty_available))
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Create outgoing shipment.
|
|
# ----------------------------------------------------------------------
|
|
|
|
before_out_quantity = self.kgB.qty_available
|
|
picking_out = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_out,
|
|
'location_id': self.stock_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.customer_location})
|
|
self.MoveObj.create({
|
|
'name': self.kgB.name,
|
|
'product_id': self.kgB.id,
|
|
'product_uom_qty': 0.966,
|
|
'product_uom': self.uom_gm.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
self.MoveObj.create({
|
|
'name': self.kgB.name,
|
|
'product_id': self.kgB.id,
|
|
'product_uom_qty': 0.034,
|
|
'product_uom': self.uom_gm.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
picking_out.action_confirm()
|
|
picking_out.action_assign()
|
|
picking_out.button_validate()
|
|
|
|
# Check quantity difference after stock transfer.
|
|
quantity_diff = before_out_quantity - self.kgB.qty_available
|
|
self.assertEqual(float_round(quantity_diff, precision_rounding=0.0001), 0.001, 'Wrong quantity difference.')
|
|
self.assertEqual(self.kgB.qty_available, 0.039, 'Wrong quantity available (%s found instead of 0.039)' % (self.kgB.qty_available))
|
|
|
|
# ======================================================================
|
|
# Outgoing shipments.
|
|
# ======================================================================
|
|
|
|
# Create Outgoing shipment with ...
|
|
# product DozA ( 54 Unit ) , SDozA ( 288 Unit )
|
|
# product SDozRound ( 360 unit ) , product gB ( 0.503 kg )
|
|
# product kgB ( 19 g )
|
|
# ======================================================================
|
|
|
|
picking_out = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_out,
|
|
'location_id': self.stock_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.customer_location})
|
|
self.MoveObj.create({
|
|
'name': self.DozA.name,
|
|
'product_id': self.DozA.id,
|
|
'product_uom_qty': 54,
|
|
'product_uom': self.uom_unit.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
self.MoveObj.create({
|
|
'name': self.SDozA.name,
|
|
'product_id': self.SDozA.id,
|
|
'product_uom_qty': 288,
|
|
'product_uom': self.uom_unit.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
self.MoveObj.create({
|
|
'name': self.SDozARound.name,
|
|
'product_id': self.SDozARound.id,
|
|
'product_uom_qty': 361,
|
|
'product_uom': self.uom_unit.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
self.MoveObj.create({
|
|
'name': self.gB.name,
|
|
'product_id': self.gB.id,
|
|
'product_uom_qty': 0.503,
|
|
'product_uom': self.uom_kg.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
self.MoveObj.create({
|
|
'name': self.kgB.name,
|
|
'product_id': self.kgB.id,
|
|
'product_uom_qty': 20,
|
|
'product_uom': self.uom_gm.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
# Confirm outgoing shipment.
|
|
picking_out.action_confirm()
|
|
for move in picking_out.move_ids:
|
|
self.assertEqual(move.state, 'confirmed', 'Wrong state of move line.')
|
|
# Assing product to outgoing shipments
|
|
picking_out.action_assign()
|
|
for move in picking_out.move_ids:
|
|
self.assertEqual(move.state, 'assigned', 'Wrong state of move line.')
|
|
# Check product A available quantity
|
|
DozA_qty = self.MoveObj.search([('product_id', '=', self.DozA.id), ('picking_id', '=', picking_out.id)], limit=1).product_qty
|
|
self.assertEqual(DozA_qty, 4.5, 'Wrong move quantity availability (%s found instead of 4.5)' % (DozA_qty))
|
|
# Check product B available quantity
|
|
SDozA_qty = self.MoveObj.search([('product_id', '=', self.SDozA.id), ('picking_id', '=', picking_out.id)], limit=1).product_qty
|
|
self.assertEqual(SDozA_qty, 2, 'Wrong move quantity availability (%s found instead of 2)' % (SDozA_qty))
|
|
# Check product C available quantity
|
|
SDozARound_qty = self.MoveObj.search([('product_id', '=', self.SDozARound.id), ('picking_id', '=', picking_out.id)], limit=1).product_qty
|
|
self.assertEqual(SDozARound_qty, 3, 'Wrong move quantity availability (%s found instead of 3)' % (SDozARound_qty))
|
|
# Check product D available quantity
|
|
gB_qty = self.MoveObj.search([('product_id', '=', self.gB.id), ('picking_id', '=', picking_out.id)], limit=1).product_qty
|
|
self.assertEqual(gB_qty, 503, 'Wrong move quantity availability (%s found instead of 503)' % (gB_qty))
|
|
# Check product D available quantity
|
|
kgB_qty = self.MoveObj.search([('product_id', '=', self.kgB.id), ('picking_id', '=', picking_out.id)], limit=1).product_qty
|
|
self.assertEqual(kgB_qty, 0.020, 'Wrong move quantity availability (%s found instead of 0.020)' % (kgB_qty))
|
|
|
|
picking_out.button_validate()
|
|
# ----------------------------------------------------------------------
|
|
# Check product stock location quantity and quantity available.
|
|
# ----------------------------------------------------------------------
|
|
|
|
# Check quants and available quantity for product DozA
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.DozA.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(sum(total_qty), 13.5, 'Expecting 13.5 Dozen , got %.4f Dozen on location stock!' % (sum(total_qty)))
|
|
self.assertEqual(self.DozA.qty_available, 13.5, 'Wrong quantity available (%s found instead of 13.5)' % (self.DozA.qty_available))
|
|
# Check quants and available quantity for product SDozA
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.SDozA.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(sum(total_qty), 19, 'Expecting 19 SDozen , got %.4f SDozen on location stock!' % (sum(total_qty)))
|
|
self.assertEqual(self.SDozA.qty_available, 19, 'Wrong quantity available (%s found instead of 19)' % (self.SDozA.qty_available))
|
|
# Check quants and available quantity for product SDozARound
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.SDozARound.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(sum(total_qty), 19, 'Expecting 19 SDozRound , got %.4f SDozRound on location stock!' % (sum(total_qty)))
|
|
self.assertEqual(self.SDozARound.qty_available, 19, 'Wrong quantity available (%s found instead of 19)' % (self.SDozARound.qty_available))
|
|
# Check quants and available quantity for product gB.
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.gB.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(round(sum(total_qty), 1), 548.3, 'Expecting 547.6 g , got %.4f g on location stock!' % (sum(total_qty)))
|
|
self.assertEqual(round(self.gB.qty_available, 1), 548.3, 'Wrong quantity available (%s found instead of 547.6)' % (self.gB.qty_available))
|
|
# Check quants and available quantity for product kgB.
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.kgB.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(sum(total_qty), 0.019, 'Expecting 0.019 kg , got %.4f kg on location stock!' % (sum(total_qty)))
|
|
self.assertEqual(self.kgB.qty_available, 0.019, 'Wrong quantity available (%s found instead of 0.019)' % (self.kgB.qty_available))
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Receipt back order of incoming shipment.
|
|
# ----------------------------------------------------------------------
|
|
|
|
bo_in_B.button_validate()
|
|
# Check quants and available quantity for product kgB.
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.DozA.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(sum(total_qty), 15.5, 'Expecting 15.5 Dozen , got %.4f Dozen on location stock!' % (sum(total_qty)))
|
|
self.assertEqual(self.DozA.qty_available, 15.5, 'Wrong quantity available (%s found instead of 15.5)' % (self.DozA.qty_available))
|
|
|
|
# -----------------------------------------
|
|
# Create product in kg and receive in ton.
|
|
# -----------------------------------------
|
|
|
|
productKG = self.ProductObj.create({'name': 'Product KG', 'uom_id': self.uom_kg.id, 'uom_po_id': self.uom_kg.id, 'type': 'product'})
|
|
picking_in = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_in,
|
|
'location_id': self.supplier_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.stock_location})
|
|
self.MoveObj.create({
|
|
'name': productKG.name,
|
|
'product_id': productKG.id,
|
|
'product_uom_qty': 1.0,
|
|
'product_uom': self.uom_tone.id,
|
|
'picking_id': picking_in.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
# Check incoming shipment state.
|
|
self.assertEqual(picking_in.state, 'draft', 'Incoming shipment state should be draft.')
|
|
# Check incoming shipment move lines state.
|
|
for move in picking_in.move_ids:
|
|
self.assertEqual(move.state, 'draft', 'Wrong state of move line.')
|
|
# Confirm incoming shipment.
|
|
picking_in.action_confirm()
|
|
# Check incoming shipment move lines state.
|
|
for move in picking_in.move_ids:
|
|
self.assertEqual(move.state, 'assigned', 'Wrong state of move line.')
|
|
# Check pack operation quantity.
|
|
packKG = self.StockPackObj.search([('product_id', '=', productKG.id), ('picking_id', '=', picking_in.id)], limit=1)
|
|
self.assertEqual(packKG.quantity_product_uom, 1000, 'Wrong product real quantity in pack operation (%s found instead of 1000)' % (packKG.quantity_product_uom))
|
|
self.assertEqual(packKG.quantity, 1, 'Wrong product quantity in pack operation (%s found instead of 1)' % (packKG.quantity))
|
|
self.assertEqual(packKG.product_uom_id.id, self.uom_tone.id, 'Wrong product uom in pack operation.')
|
|
# Transfer Incoming shipment.
|
|
picking_in.button_validate()
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Check incoming shipment after transfer.
|
|
# -----------------------------------------------------------------------
|
|
|
|
# Check incoming shipment state.
|
|
self.assertEqual(picking_in.state, 'done', 'Incoming shipment state: %s instead of %s' % (picking_in.state, 'done'))
|
|
# Check incoming shipment move lines state.
|
|
for move in picking_in.move_ids:
|
|
self.assertEqual(move.state, 'done', 'Wrong state of move lines.')
|
|
# Check total done move lines for incoming shipment.
|
|
self.assertEqual(len(picking_in.move_ids), 1, 'Wrong number of move lines')
|
|
# Check product DozA done quantity.
|
|
move = self.MoveObj.search([('product_id', '=', productKG.id), ('picking_id', '=', picking_in.id)], limit=1)
|
|
self.assertEqual(move.product_uom_qty, 1, 'Wrong product quantity in done move.')
|
|
self.assertEqual(move.product_uom.id, self.uom_tone.id, 'Wrong unit of measure in done move.')
|
|
self.assertEqual(productKG.qty_available, 1000, 'Wrong quantity available of product (%s found instead of 1000)' % (productKG.qty_available))
|
|
picking_out = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_out,
|
|
'location_id': self.stock_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.customer_location})
|
|
self.MoveObj.create({
|
|
'name': productKG.name,
|
|
'product_id': productKG.id,
|
|
'product_uom_qty': 25,
|
|
'product_uom': self.uom_gm.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
picking_out.action_confirm()
|
|
picking_out.action_assign()
|
|
pack_opt = self.StockPackObj.search([('product_id', '=', productKG.id), ('picking_id', '=', picking_out.id)], limit=1)
|
|
pack_opt.write({'quantity': 5})
|
|
res_dict_for_back_order = picking_out.button_validate()
|
|
backorder_wizard = self.env[(res_dict_for_back_order.get('res_model'))].browse(res_dict_for_back_order.get('res_id')).with_context(res_dict_for_back_order['context'])
|
|
backorder_wizard.process()
|
|
quants = self.StockQuantObj.search([('product_id', '=', productKG.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
# Check total quantity stock location.
|
|
self.assertEqual(sum(total_qty), 999.995, 'Expecting 999.995 kg , got %.4f kg on location stock!' % (sum(total_qty)))
|
|
|
|
# ---------------------------------
|
|
# Check Back order created or not.
|
|
# ---------------------------------
|
|
bo_out_1 = self.PickingObj.search([('backorder_id', '=', picking_out.id)])
|
|
self.assertEqual(len(bo_out_1), 1, 'Back order should be created.')
|
|
# Check total move lines of back order.
|
|
self.assertEqual(len(bo_out_1.move_ids), 1, 'Wrong number of move lines')
|
|
moves_KG = self.MoveObj.search([('product_id', '=', productKG.id), ('picking_id', '=', bo_out_1.id)], limit=1)
|
|
# Check back order created with correct quantity and uom or not.
|
|
self.assertEqual(moves_KG.product_uom_qty, 20, 'Wrong move quantity (%s found instead of 20)' % (moves_KG.product_uom_qty))
|
|
self.assertEqual(moves_KG.product_uom.id, self.uom_gm.id, 'Wrong uom in move for product KG.')
|
|
bo_out_1.action_assign()
|
|
pack_opt = self.StockPackObj.search([('product_id', '=', productKG.id), ('picking_id', '=', bo_out_1.id)], limit=1)
|
|
pack_opt.write({'quantity': 5})
|
|
res_dict_for_back_order = bo_out_1.button_validate()
|
|
backorder_wizard = self.env[(res_dict_for_back_order.get('res_model'))].browse(res_dict_for_back_order.get('res_id')).with_context(res_dict_for_back_order['context'])
|
|
backorder_wizard.process()
|
|
quants = self.StockQuantObj.search([('product_id', '=', productKG.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
|
|
# Check total quantity stock location.
|
|
self.assertEqual(sum(total_qty), 999.990, 'Expecting 999.990 kg , got %.4f kg on location stock!' % (sum(total_qty)))
|
|
|
|
# Check Back order created or not.
|
|
# ---------------------------------
|
|
bo_out_2 = self.PickingObj.search([('backorder_id', '=', bo_out_1.id)])
|
|
self.assertEqual(len(bo_out_2), 1, 'Back order should be created.')
|
|
# Check total move lines of back order.
|
|
self.assertEqual(len(bo_out_2.move_ids), 1, 'Wrong number of move lines')
|
|
# Check back order created with correct move quantity and uom or not.
|
|
moves_KG = self.MoveObj.search([('product_id', '=', productKG.id), ('picking_id', '=', bo_out_2.id)], limit=1)
|
|
self.assertEqual(moves_KG.product_uom_qty, 15, 'Wrong move quantity (%s found instead of 15)' % (moves_KG.product_uom_qty))
|
|
self.assertEqual(moves_KG.product_uom.id, self.uom_gm.id, 'Wrong uom in move for product KG.')
|
|
bo_out_2.action_assign()
|
|
pack_opt = self.StockPackObj.search([('product_id', '=', productKG.id), ('picking_id', '=', bo_out_2.id)], limit=1)
|
|
pack_opt.write({'quantity': 5})
|
|
res_dict_for_back_order = bo_out_2.button_validate()
|
|
backorder_wizard = self.env[(res_dict_for_back_order.get('res_model'))].browse(res_dict_for_back_order.get('res_id')).with_context(res_dict_for_back_order['context'])
|
|
backorder_wizard.process()
|
|
# Check total quantity stock location of product KG.
|
|
quants = self.StockQuantObj.search([('product_id', '=', productKG.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(sum(total_qty), 999.985, 'Expecting 999.985 kg , got %.4f kg on location stock!' % (sum(total_qty)))
|
|
|
|
# Check Back order created or not.
|
|
# ---------------------------------
|
|
bo_out_3 = self.PickingObj.search([('backorder_id', '=', bo_out_2.id)])
|
|
self.assertEqual(len(bo_out_3), 1, 'Back order should be created.')
|
|
# Check total move lines of back order.
|
|
self.assertEqual(len(bo_out_3.move_ids), 1, 'Wrong number of move lines')
|
|
# Check back order created with correct quantity and uom or not.
|
|
moves_KG = self.MoveObj.search([('product_id', '=', productKG.id), ('picking_id', '=', bo_out_3.id)], limit=1)
|
|
self.assertEqual(moves_KG.product_uom_qty, 10, 'Wrong move quantity (%s found instead of 10)' % (moves_KG.product_uom_qty))
|
|
self.assertEqual(moves_KG.product_uom.id, self.uom_gm.id, 'Wrong uom in move for product KG.')
|
|
bo_out_3.action_assign()
|
|
pack_opt = self.StockPackObj.search([('product_id', '=', productKG.id), ('picking_id', '=', bo_out_3.id)], limit=1)
|
|
pack_opt.write({'quantity': 5})
|
|
res_dict_for_back_order = bo_out_3.button_validate()
|
|
backorder_wizard = self.env[(res_dict_for_back_order.get('res_model'))].browse(res_dict_for_back_order.get('res_id')).with_context(res_dict_for_back_order['context'])
|
|
backorder_wizard.process()
|
|
quants = self.StockQuantObj.search([('product_id', '=', productKG.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertEqual(sum(total_qty), 999.980, 'Expecting 999.980 kg , got %.4f kg on location stock!' % (sum(total_qty)))
|
|
|
|
# Check Back order created or not.
|
|
# ---------------------------------
|
|
bo_out_4 = self.PickingObj.search([('backorder_id', '=', bo_out_3.id)])
|
|
|
|
self.assertEqual(len(bo_out_4), 1, 'Back order should be created.')
|
|
# Check total move lines of back order.
|
|
self.assertEqual(len(bo_out_4.move_ids), 1, 'Wrong number of move lines')
|
|
# Check back order created with correct quantity and uom or not.
|
|
moves_KG = self.MoveObj.search([('product_id', '=', productKG.id), ('picking_id', '=', bo_out_4.id)], limit=1)
|
|
self.assertEqual(moves_KG.product_uom_qty, 5, 'Wrong move quantity (%s found instead of 5)' % (moves_KG.product_uom_qty))
|
|
self.assertEqual(moves_KG.product_uom.id, self.uom_gm.id, 'Wrong uom in move for product KG.')
|
|
bo_out_4.action_assign()
|
|
pack_opt = self.StockPackObj.search([('product_id', '=', productKG.id), ('picking_id', '=', bo_out_4.id)], limit=1)
|
|
pack_opt.write({'quantity': 5})
|
|
bo_out_4.button_validate()
|
|
quants = self.StockQuantObj.search([('product_id', '=', productKG.id), ('location_id', '=', self.stock_location)])
|
|
total_qty = [quant.quantity for quant in quants]
|
|
self.assertAlmostEqual(sum(total_qty), 999.975, msg='Expecting 999.975 kg , got %.4f kg on location stock!' % (sum(total_qty)))
|
|
|
|
def test_20_create_inventory_with_packs_and_lots(self):
|
|
# --------------------------------------------------------
|
|
# TEST EMPTY INVENTORY WITH PACKS and LOTS
|
|
# ---------------------------------------------------------
|
|
|
|
packproduct = self.ProductObj.create({'name': 'Pack Product', 'uom_id': self.uom_unit.id, 'uom_po_id': self.uom_unit.id, 'type': 'product'})
|
|
lotproduct = self.ProductObj.create({'name': 'Lot Product', 'uom_id': self.uom_unit.id, 'uom_po_id': self.uom_unit.id, 'type': 'product'})
|
|
quant_obj = self.env['stock.quant'].with_context(inventory_mode=True)
|
|
pack_obj = self.env['stock.quant.package']
|
|
lot_obj = self.env['stock.lot']
|
|
pack1 = pack_obj.create({'name': 'PACK00TEST1'})
|
|
pack_obj.create({'name': 'PACK00TEST2'})
|
|
lot1 = lot_obj.create({'name': 'Lot001', 'product_id': lotproduct.id, 'company_id': self.env.company.id})
|
|
|
|
packproduct_no_pack_quant = quant_obj.create({
|
|
'product_id': packproduct.id,
|
|
'inventory_quantity': 10.0,
|
|
'location_id': self.stock_location
|
|
})
|
|
packproduct_quant = quant_obj.create({
|
|
'product_id': packproduct.id,
|
|
'inventory_quantity': 20.0,
|
|
'location_id': self.stock_location,
|
|
'package_id': pack1.id
|
|
})
|
|
lotproduct_no_lot_quant = quant_obj.create({
|
|
'product_id': lotproduct.id,
|
|
'inventory_quantity': 25.0,
|
|
'location_id': self.stock_location
|
|
})
|
|
lotproduct_quant = quant_obj.create({
|
|
'product_id': lotproduct.id,
|
|
'inventory_quantity': 30.0,
|
|
'location_id': self.stock_location,
|
|
'lot_id': lot1.id
|
|
})
|
|
(packproduct_no_pack_quant | packproduct_quant | lotproduct_no_lot_quant | lotproduct_quant).action_apply_inventory()
|
|
|
|
self.assertEqual(packproduct.qty_available, 30, "Wrong qty available for packproduct")
|
|
self.assertEqual(lotproduct.qty_available, 55, "Wrong qty available for lotproduct")
|
|
quants = self.StockQuantObj.search([('product_id', '=', packproduct.id), ('location_id', '=', self.stock_location), ('package_id', '=', pack1.id)])
|
|
total_qty = sum([quant.quantity for quant in quants])
|
|
self.assertEqual(total_qty, 20, 'Expecting 20 units on package 1 of packproduct, but we got %.4f on location stock!' % (total_qty))
|
|
|
|
# Create an inventory that will put the lots without lot to 0 and check that taking without pack will not take it from the pack
|
|
packproduct_no_pack_quant.inventory_quantity = 20
|
|
lotproduct_no_lot_quant.inventory_quantity = 0
|
|
lotproduct_quant.inventory_quantity = 10
|
|
packproduct_no_pack_quant.action_apply_inventory()
|
|
lotproduct_no_lot_quant.action_apply_inventory()
|
|
lotproduct_quant.action_apply_inventory()
|
|
|
|
self.assertEqual(packproduct.qty_available, 40, "Wrong qty available for packproduct")
|
|
self.assertEqual(lotproduct.qty_available, 10, "Wrong qty available for lotproduct")
|
|
quants = self.StockQuantObj.search([('product_id', '=', lotproduct.id), ('location_id', '=', self.stock_location), ('lot_id', '=', lot1.id)])
|
|
total_qty = sum([quant.quantity for quant in quants])
|
|
self.assertEqual(total_qty, 10, 'Expecting 10 units lot of lotproduct, but we got %.4f on location stock!' % (total_qty))
|
|
quants = self.StockQuantObj.search([('product_id', '=', lotproduct.id), ('location_id', '=', self.stock_location), ('lot_id', '=', False)])
|
|
total_qty = sum([quant.quantity for quant in quants])
|
|
self.assertEqual(total_qty, 0, 'Expecting 0 units lot of lotproduct, but we got %.4f on location stock!' % (total_qty))
|
|
|
|
def test_30_check_with_no_incoming_lot(self):
|
|
""" Picking in without lots and picking out with"""
|
|
# Change basic operation type not to get lots
|
|
# Create product with lot tracking
|
|
picking_in = self.env['stock.picking.type'].browse(self.picking_type_in)
|
|
picking_in.use_create_lots = False
|
|
self.productA.tracking = 'lot'
|
|
picking_in = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_in,
|
|
'location_id': self.supplier_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.stock_location})
|
|
self.MoveObj.create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 4,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking_in.id,
|
|
'picking_type_id': self.picking_type_in,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
|
|
# Check incoming shipment move lines state.
|
|
for move in picking_in.move_ids:
|
|
self.assertEqual(move.state, 'draft', 'Wrong state of move line.')
|
|
# Confirm incoming shipment.
|
|
picking_in.action_confirm()
|
|
# Check incoming shipment move lines state.
|
|
for move in picking_in.move_ids:
|
|
self.assertEqual(move.state, 'assigned', 'Wrong state of move line.')
|
|
|
|
picking_in.button_validate()
|
|
picking_out = self.PickingObj.create({
|
|
'name': 'testpicking',
|
|
'picking_type_id': self.picking_type_out,
|
|
'location_id': self.stock_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.customer_location})
|
|
move_out = self.MoveObj.create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 3,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
picking_out.action_confirm()
|
|
picking_out.action_assign()
|
|
pack_opt = self.StockPackObj.search([('picking_id', '=', picking_out.id)], limit=1)
|
|
lot1 = self.LotObj.create({'product_id': self.productA.id, 'name': 'LOT1', 'company_id': self.env.company.id})
|
|
lot2 = self.LotObj.create({'product_id': self.productA.id, 'name': 'LOT2', 'company_id': self.env.company.id})
|
|
lot3 = self.LotObj.create({'product_id': self.productA.id, 'name': 'LOT3', 'company_id': self.env.company.id})
|
|
|
|
pack_opt.write({'lot_id': lot1.id, 'quantity': 1.0})
|
|
self.StockPackObj.create({'product_id': self.productA.id, 'move_id': move_out.id, 'product_uom_id': move_out.product_uom.id, 'lot_id': lot2.id, 'quantity': 1.0, 'location_id': self.stock_location, 'location_dest_id': self.customer_location})
|
|
self.StockPackObj.create({'product_id': self.productA.id, 'move_id': move_out.id, 'product_uom_id': move_out.product_uom.id, 'lot_id': lot3.id, 'quantity': 2.0, 'location_id': self.stock_location, 'location_dest_id': self.customer_location})
|
|
picking_out._action_done()
|
|
quants = self.StockQuantObj.search([('product_id', '=', self.productA.id), ('location_id', '=', self.stock_location)])
|
|
|
|
def test_40_pack_in_pack(self):
|
|
""" Put a pack in pack"""
|
|
self.env['stock.picking.type'].browse(self.picking_type_in).show_reserved = True
|
|
picking_out = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_out,
|
|
'location_id': self.pack_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.customer_location})
|
|
move_out = self.MoveObj.create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 3,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.pack_location,
|
|
'location_dest_id': self.customer_location})
|
|
picking_pack = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_out,
|
|
'location_id': self.stock_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.pack_location})
|
|
move_pack = self.MoveObj.create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 3,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking_pack.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.pack_location,
|
|
'move_dest_ids': [(4, move_out.id, 0)]})
|
|
picking_in = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_in,
|
|
'location_id': self.supplier_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.stock_location})
|
|
move_in = self.MoveObj.create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 3,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking_in.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location,
|
|
'move_dest_ids': [(4, move_pack.id, 0)]})
|
|
|
|
# Check incoming shipment move lines state.
|
|
for move in picking_in.move_ids:
|
|
self.assertEqual(move.state, 'draft', 'Wrong state of move line.')
|
|
# Confirm incoming shipment.
|
|
picking_in.action_confirm()
|
|
# Check incoming shipment move lines state.
|
|
for move in picking_in.move_ids:
|
|
self.assertEqual(move.state, 'assigned', 'Wrong state of move line.')
|
|
|
|
# Check incoming shipment move lines state.
|
|
for move in picking_pack.move_ids:
|
|
self.assertEqual(move.state, 'draft', 'Wrong state of move line.')
|
|
# Confirm incoming shipment.
|
|
picking_pack.action_confirm()
|
|
# Check incoming shipment move lines state.
|
|
for move in picking_pack.move_ids:
|
|
self.assertEqual(move.state, 'waiting', 'Wrong state of move line.')
|
|
|
|
# Check incoming shipment move lines state.
|
|
for move in picking_out.move_ids:
|
|
self.assertEqual(move.state, 'draft', 'Wrong state of move line.')
|
|
# Confirm incoming shipment.
|
|
picking_out.action_confirm()
|
|
# Check incoming shipment move lines state.
|
|
for move in picking_out.move_ids:
|
|
self.assertEqual(move.state, 'waiting', 'Wrong state of move line.')
|
|
|
|
# Set the quantity done on the pack operation
|
|
move_in.move_line_ids.quantity = 3.0
|
|
# Put in a pack
|
|
picking_in.action_put_in_pack()
|
|
# Get the new package
|
|
picking_in_package = move_in.move_line_ids.result_package_id
|
|
# Validate picking
|
|
picking_in.move_ids.picked = True
|
|
picking_in._action_done()
|
|
|
|
# Check first picking state changed to done
|
|
for move in picking_in.move_ids:
|
|
self.assertEqual(move.state, 'done', 'Wrong state of move line.')
|
|
# Check next picking state changed to 'assigned'
|
|
for move in picking_pack.move_ids:
|
|
self.assertEqual(move.state, 'assigned', 'Wrong state of move line.')
|
|
|
|
# Set the quantity done on the pack operation
|
|
move_pack.move_line_ids.quantity = 3.0
|
|
move_pack.picked = True
|
|
# Get the new package
|
|
picking_pack_package = move_pack.move_line_ids.result_package_id
|
|
# Validate picking
|
|
picking_pack._action_done()
|
|
|
|
# Check second picking state changed to done
|
|
for move in picking_pack.move_ids:
|
|
self.assertEqual(move.state, 'done', 'Wrong state of move line.')
|
|
# Check next picking state changed to 'assigned'
|
|
for move in picking_out.move_ids:
|
|
self.assertEqual(move.state, 'assigned', 'Wrong state of move line.')
|
|
|
|
# Validate picking
|
|
picking_out.move_line_ids.quantity = 3.0
|
|
picking_out_package = move_out.move_line_ids.result_package_id
|
|
picking_out.move_ids.picked = True
|
|
picking_out._action_done()
|
|
|
|
# check all pickings are done
|
|
for move in picking_in.move_ids:
|
|
self.assertEqual(move.state, 'done', 'Wrong state of move line.')
|
|
for move in picking_pack.move_ids:
|
|
self.assertEqual(move.state, 'done', 'Wrong state of move line.')
|
|
for move in picking_out.move_ids:
|
|
self.assertEqual(move.state, 'done', 'Wrong state of move line.')
|
|
|
|
# Check picking_in_package is in picking_pack_package
|
|
self.assertEqual(picking_in_package.id, picking_pack_package.id, 'The package created in the picking in is not in the one created in picking pack')
|
|
self.assertEqual(picking_pack_package.id, picking_out_package.id, 'The package created in the picking in is not in the one created in picking pack')
|
|
# Check that we have one quant in customer location.
|
|
quant = self.StockQuantObj.search([('product_id', '=', self.productA.id), ('location_id', '=', self.customer_location)])
|
|
self.assertEqual(len(quant), 1, 'There should be one quant with package for customer location')
|
|
# Check that the parent package of the quant is the picking_in_package
|
|
|
|
def test_50_create_in_out_with_product_pack_lines(self):
|
|
picking_in = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_in,
|
|
'location_id': self.supplier_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.stock_location})
|
|
self.MoveObj.create({
|
|
'name': self.productE.name,
|
|
'product_id': self.productE.id,
|
|
'product_uom_qty': 10,
|
|
'product_uom': self.productE.uom_id.id,
|
|
'picking_id': picking_in.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
picking_in.action_confirm()
|
|
pack_obj = self.env['stock.quant.package']
|
|
pack1 = pack_obj.create({'name': 'PACKINOUTTEST1'})
|
|
pack2 = pack_obj.create({'name': 'PACKINOUTTEST2'})
|
|
picking_in.move_line_ids[0].result_package_id = pack1
|
|
picking_in.move_line_ids[0].quantity = 4
|
|
packop2 = picking_in.move_line_ids[0].copy({'quantity': 0})
|
|
packop2.quantity = 6
|
|
packop2.result_package_id = pack2
|
|
picking_in.move_ids.picked = True
|
|
picking_in._action_done()
|
|
quants = self.env['stock.quant']._gather(self.productE, self.env['stock.location'].browse(self.stock_location))
|
|
self.assertEqual(sum([x.quantity for x in quants]), 10.0, 'Expecting 10 pieces in stock')
|
|
# Check the quants are in the package
|
|
self.assertEqual(sum(x.quantity for x in pack1.quant_ids), 4.0, 'Pack 1 should have 4 pieces')
|
|
self.assertEqual(sum(x.quantity for x in pack2.quant_ids), 6.0, 'Pack 2 should have 6 pieces')
|
|
picking_out = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_out,
|
|
'location_id': self.stock_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.customer_location})
|
|
self.MoveObj.create({
|
|
'name': self.productE.name,
|
|
'product_id': self.productE.id,
|
|
'product_uom_qty': 3,
|
|
'product_uom': self.productE.uom_id.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
picking_out.action_confirm()
|
|
picking_out.action_assign()
|
|
packout1 = picking_out.move_line_ids[0]
|
|
packout2 = picking_out.move_line_ids[0].copy({'quantity': 0})
|
|
packout1.quantity = 2
|
|
packout1.package_id = pack1
|
|
packout2.package_id = pack2
|
|
packout2.quantity = 1
|
|
picking_out.move_ids.picked = True
|
|
picking_out._action_done()
|
|
# Should be only 1 negative quant in supplier location
|
|
neg_quants = self.env['stock.quant'].search([('product_id', '=', self.productE.id), ('quantity', '<', 0.0)])
|
|
self.assertEqual(len(neg_quants), 1, 'There should be 1 negative quants for supplier!')
|
|
self.assertEqual(neg_quants.location_id.id, self.supplier_location, 'There shoud be 1 negative quants for supplier!')
|
|
|
|
quants = self.env['stock.quant']._gather(self.productE, self.env['stock.location'].browse(self.stock_location))
|
|
self.assertEqual(len(quants), 2, 'We should have exactly 2 quants in the end')
|
|
|
|
def test_60_create_in_out_with_product_pack_lines(self):
|
|
picking_in = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_in,
|
|
'location_id': self.supplier_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.stock_location})
|
|
self.MoveObj.create({
|
|
'name': self.productE.name,
|
|
'product_id': self.productE.id,
|
|
'product_uom_qty': 200,
|
|
'product_uom': self.productE.uom_id.id,
|
|
'picking_id': picking_in.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
|
|
picking_in.action_confirm()
|
|
pack_obj = self.env['stock.quant.package']
|
|
pack1 = pack_obj.create({'name': 'PACKINOUTTEST1'})
|
|
pack2 = pack_obj.create({'name': 'PACKINOUTTEST2'})
|
|
picking_in.move_line_ids[0].result_package_id = pack1
|
|
picking_in.move_line_ids[0].quantity = 120
|
|
packop2 = picking_in.move_line_ids[0].copy({'quantity': 0})
|
|
packop2.quantity = 80
|
|
packop2.result_package_id = pack2
|
|
picking_in.move_ids.picked = True
|
|
picking_in._action_done()
|
|
quants = self.env['stock.quant']._gather(self.productE, self.env['stock.location'].browse(self.stock_location))
|
|
self.assertEqual(sum([x.quantity for x in quants]), 200.0, 'Expecting 200 pieces in stock')
|
|
# Check the quants are in the package
|
|
self.assertEqual(sum(x.quantity for x in pack1.quant_ids), 120, 'Pack 1 should have 120 pieces')
|
|
self.assertEqual(sum(x.quantity for x in pack2.quant_ids), 80, 'Pack 2 should have 80 pieces')
|
|
picking_out = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_out,
|
|
'location_id': self.stock_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.customer_location})
|
|
self.MoveObj.create({
|
|
'name': self.productE.name,
|
|
'product_id': self.productE.id,
|
|
'product_uom_qty': 200,
|
|
'product_uom': self.productE.uom_id.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
picking_out.action_confirm()
|
|
picking_out.action_assign()
|
|
# Convert entire packs into taking out of packs
|
|
packout0 = picking_out.move_line_ids[0]
|
|
packout1 = picking_out.move_line_ids[1]
|
|
packout0.write({
|
|
'package_id': pack1.id,
|
|
'product_id': self.productE.id,
|
|
'quantity': 120.0,
|
|
'product_uom_id': self.productE.uom_id.id,
|
|
})
|
|
packout1.write({
|
|
'package_id': pack2.id,
|
|
'product_id': self.productE.id,
|
|
'quantity': 80.0,
|
|
'product_uom_id': self.productE.uom_id.id,
|
|
})
|
|
picking_out.move_ids.picked = True
|
|
picking_out._action_done()
|
|
# Should be only 1 negative quant in supplier location
|
|
neg_quants = self.env['stock.quant'].search([('product_id', '=', self.productE.id), ('quantity', '<', 0.0)])
|
|
self.assertEqual(len(neg_quants), 1, 'There should be 1 negative quants for supplier!')
|
|
self.assertEqual(neg_quants.location_id.id, self.supplier_location, 'There shoud be 1 negative quants for supplier!')
|
|
# We should also make sure that when matching stock moves with pack operations, it takes the correct
|
|
quants = self.env['stock.quant']._gather(self.productE, self.env['stock.location'].browse(self.stock_location))
|
|
self.assertEqual(sum(quants.mapped('quantity')), 0, 'We should have no quants in the end')
|
|
|
|
def test_70_picking_state_all_at_once_reserve(self):
|
|
""" This test will check that the state of the picking is correctly computed according
|
|
to the state of its move lines and its move type.
|
|
"""
|
|
# move_type: direct == partial, one == all at once
|
|
# picking: confirmed == waiting availability
|
|
|
|
# -----------------------------------------------------------
|
|
# "all at once" and "reserve" scenario
|
|
# -----------------------------------------------------------
|
|
# get one product in stock
|
|
inventory_quant = self.env['stock.quant'].create({
|
|
'location_id': self.stock_location,
|
|
'product_id': self.productA.id,
|
|
'inventory_quantity': 1,
|
|
})
|
|
inventory_quant.action_apply_inventory()
|
|
|
|
# create a "all at once" delivery order for two products
|
|
picking_out = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_out,
|
|
'location_id': self.stock_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.customer_location})
|
|
picking_out.move_type = 'one'
|
|
|
|
self.MoveObj.create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 2,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
# validate this delivery order, it should be in the waiting state
|
|
picking_out.action_assign()
|
|
self.assertEqual(picking_out.state, "confirmed")
|
|
|
|
# receive one product in stock
|
|
inventory_quant.inventory_quantity = 2
|
|
inventory_quant.action_apply_inventory()
|
|
# recheck availability of the delivery order, it should be assigned
|
|
picking_out.action_assign()
|
|
self.assertEqual(len(picking_out.move_ids), 1.0)
|
|
self.assertEqual(picking_out.move_ids.product_qty, 2.0)
|
|
self.assertEqual(picking_out.state, "assigned")
|
|
|
|
def test_71_picking_state_all_at_once_force_assign(self):
|
|
""" This test will check that the state of the picking is correctly computed according
|
|
to the state of its move lines and its move type.
|
|
"""
|
|
# move_type: direct == partial, one == all at once
|
|
# picking: confirmed == waiting availability, partially_available = partially available
|
|
|
|
# -----------------------------------------------------------
|
|
# "all at once" and "force assign" scenario
|
|
# -----------------------------------------------------------
|
|
# create a "all at once" delivery order for two products
|
|
picking_out = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_out,
|
|
'location_id': self.stock_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.customer_location})
|
|
picking_out.move_type = 'direct'
|
|
|
|
self.MoveObj.create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 2,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
|
|
# validate this delivery order, it should be in the waiting state
|
|
picking_out.action_assign()
|
|
self.assertEqual(picking_out.state, "confirmed")
|
|
|
|
def test_72_picking_state_partial_reserve(self):
|
|
""" This test will check that the state of the picking is correctly computed according
|
|
to the state of its move lines and its move type.
|
|
"""
|
|
# move_type: direct == partial, one == all at once
|
|
# picking: confirmed == waiting availability, partially_available = partially available
|
|
|
|
# -----------------------------------------------------------
|
|
# "partial" and "reserve" scenario
|
|
# -----------------------------------------------------------
|
|
# get one product in stock
|
|
inventory_quant = self.env['stock.quant'].create({
|
|
'location_id': self.stock_location,
|
|
'product_id': self.productA.id,
|
|
'inventory_quantity': 1,
|
|
})
|
|
inventory_quant.action_apply_inventory()
|
|
|
|
# create a "partial" delivery order for two products
|
|
picking_out = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_out,
|
|
'location_id': self.stock_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.customer_location})
|
|
picking_out.move_type = 'direct'
|
|
|
|
self.MoveObj.create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 2,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
|
|
# validate this delivery order, it should be in partially available
|
|
picking_out.action_assign()
|
|
self.assertEqual(picking_out.state, "assigned")
|
|
|
|
# receive one product in stock
|
|
inventory_quant.inventory_quantity = 2
|
|
inventory_quant.action_apply_inventory()
|
|
|
|
# recheck availability of the delivery order, it should be assigned
|
|
picking_out.action_assign()
|
|
self.assertEqual(picking_out.state, "assigned")
|
|
|
|
def test_73_picking_state_partial_force_assign(self):
|
|
""" This test will check that the state of the picking is correctly computed according
|
|
to the state of its move lines and its move type.
|
|
"""
|
|
# move_type: direct == partial, one == all at once
|
|
# picking: confirmed == waiting availability, partially_available = partially available
|
|
|
|
# -----------------------------------------------------------
|
|
# "partial" and "force assign" scenario
|
|
# -----------------------------------------------------------
|
|
picking_out = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_out,
|
|
'location_id': self.stock_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.customer_location})
|
|
picking_out.move_type = 'direct'
|
|
|
|
self.MoveObj.create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 2,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
|
|
# validate this delivery order, it should be in the waiting state
|
|
picking_out.action_assign()
|
|
self.assertEqual(picking_out.state, "confirmed")
|
|
|
|
def test_74_move_state_waiting_mto(self):
|
|
""" This test will check that when a move is unreserved, its state changes to 'waiting' if
|
|
it has ancestors or if it has a 'procure_method' equal to 'make_to_order' else the state
|
|
changes to 'confirmed'.
|
|
"""
|
|
picking_out = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_out,
|
|
'location_id': self.stock_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.customer_location})
|
|
move_mto_alone = self.MoveObj.create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 2,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location,
|
|
'procure_method': 'make_to_order'})
|
|
move_with_ancestors = self.MoveObj.create({
|
|
'name': self.productB.name,
|
|
'product_id': self.productB.id,
|
|
'product_uom_qty': 2,
|
|
'product_uom': self.productB.uom_id.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
self.MoveObj.create({
|
|
'name': self.productB.name,
|
|
'product_id': self.productB.id,
|
|
'product_uom_qty': 2,
|
|
'product_uom': self.productB.uom_id.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location,
|
|
'move_dest_ids': [(4, move_with_ancestors.id, 0)]})
|
|
other_move = self.MoveObj.create({
|
|
'name': self.productC.name,
|
|
'product_id': self.productC.id,
|
|
'product_uom_qty': 2,
|
|
'product_uom': self.productC.uom_id.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location})
|
|
|
|
move_mto_alone._action_confirm()
|
|
move_with_ancestors._action_confirm()
|
|
other_move._action_confirm()
|
|
|
|
move_mto_alone._do_unreserve()
|
|
move_with_ancestors._do_unreserve()
|
|
other_move._do_unreserve()
|
|
|
|
self.assertEqual(move_mto_alone.state, "waiting")
|
|
self.assertEqual(move_with_ancestors.state, "waiting")
|
|
self.assertEqual(other_move.state, "confirmed")
|
|
|
|
def test_80_partial_picking_without_backorder(self):
|
|
""" This test will create a picking with an initial demand for a product
|
|
then process a lesser quantity than the expected quantity to be processed.
|
|
When the wizard ask for a backorder, the 'NO BACKORDER' option will be selected
|
|
and no backorder should be created afterwards
|
|
"""
|
|
|
|
picking = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_in,
|
|
'location_id': self.supplier_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.stock_location})
|
|
move_a = self.MoveObj.create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 10,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
|
|
picking.action_confirm()
|
|
|
|
# Only 4 items are processed
|
|
move_a.move_line_ids.quantity = 4
|
|
move_a.picked = True
|
|
res_dict = picking.button_validate()
|
|
backorder_wizard = Form(self.env['stock.backorder.confirmation'].with_context(res_dict['context'])).save()
|
|
backorder_wizard.process_cancel_backorder()
|
|
|
|
# Checking that no backorders were attached to the picking
|
|
self.assertFalse(picking.backorder_ids)
|
|
|
|
# Checking that the original move is still in the same picking
|
|
self.assertEqual(move_a.picking_id.id, picking.id)
|
|
|
|
move_ids = picking.move_ids
|
|
move_done = move_ids.browse(move_a.id)
|
|
|
|
# Checking that the original move was set to done
|
|
self.assertEqual(move_done.product_uom_qty, 10)
|
|
self.assertEqual(move_done.state, 'done')
|
|
|
|
def test_backorder_setting(self):
|
|
"""Create 3 pickings with each has different backorder settings on its
|
|
picking type. Check the backorder behavior for each picking.
|
|
"""
|
|
picking_type_ask = self.env['stock.picking.type'].browse(self.picking_type_in)
|
|
picking_type_ask.create_backorder = 'ask'
|
|
picking_type_always = picking_type_ask.copy({
|
|
'sequence_code': 'always',
|
|
'create_backorder': 'always',
|
|
})
|
|
picking_type_never = picking_type_ask.copy({
|
|
'sequence_code': 'never',
|
|
'create_backorder': 'never',
|
|
})
|
|
|
|
picking_ask = self.PickingObj.create({
|
|
'picking_type_id': picking_type_ask.id,
|
|
'location_id': self.supplier_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.stock_location})
|
|
self.MoveObj.create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 10,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking_ask.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
picking_always = self.PickingObj.create({
|
|
'picking_type_id': picking_type_always.id,
|
|
'location_id': self.supplier_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.stock_location})
|
|
self.MoveObj.create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 10,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking_always.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
picking_never = self.PickingObj.create({
|
|
'picking_type_id': picking_type_never.id,
|
|
'location_id': self.supplier_location,
|
|
'state': 'draft',
|
|
'location_dest_id': self.stock_location})
|
|
self.MoveObj.create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 10,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking_never.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
|
|
pickings = picking_ask | picking_always | picking_never
|
|
pickings.action_confirm()
|
|
# Only 4 items are processed in each picking
|
|
pickings.move_ids.move_line_ids.quantity = 4
|
|
pickings.move_ids.picked = True
|
|
res_dict = pickings.button_validate()
|
|
backorder_wizard = Form(self.env['stock.backorder.confirmation'].with_context(res_dict['context'])).save()
|
|
|
|
self.assertEqual(backorder_wizard.pick_ids, picking_ask, "Only ask backorder for picking with setting 'set'")
|
|
backorder_wizard.process_cancel_backorder()
|
|
|
|
self.assertEqual(pickings.mapped('state'), ['done', 'done', 'done'])
|
|
self.assertFalse(picking_ask.backorder_ids)
|
|
self.assertTrue(picking_always.backorder_ids, "Picking with setting 'always' should have backorder created")
|
|
self.assertFalse(picking_never.backorder_ids, "Picking with setting 'never' should have no backorder created")
|
|
|
|
def test_transit_multi_companies(self):
|
|
""" Ensure that inter company rules set the correct company on picking
|
|
and their moves.
|
|
"""
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
grp_multi_routes = self.env.ref('stock.group_adv_location')
|
|
grp_multi_companies = self.env.ref('base.group_multi_company')
|
|
self.env.user.write({'groups_id': [(4, grp_multi_loc.id)]})
|
|
self.env.user.write({'groups_id': [(4, grp_multi_routes.id)]})
|
|
self.env.user.write({'groups_id': [(4, grp_multi_companies.id)]})
|
|
|
|
company_2 = self.company
|
|
# Need to add a new company on user.
|
|
self.env.user.write({'company_ids': [(4, company_2.id)]})
|
|
|
|
warehouse_company_1 = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
|
|
|
|
f = Form(self.env['stock.route'])
|
|
f.name = 'From Company 1 to InterCompany'
|
|
f.company_id = self.env.company
|
|
with f.rule_ids.new() as rule:
|
|
rule.name = 'From Company 1 to InterCompany'
|
|
rule.action = 'pull'
|
|
rule.picking_type_id = warehouse_company_1.in_type_id
|
|
rule.location_src_id = self.env.ref('stock.stock_location_inter_wh')
|
|
rule.procure_method = 'make_to_order'
|
|
route_a = f.save()
|
|
warehouse_company_2 = self.env['stock.warehouse'].search([('company_id', '=', company_2.id)], limit=1)
|
|
f = Form(self.env['stock.route'])
|
|
f.name = 'From InterCompany to Company 2'
|
|
f.company_id = company_2
|
|
with f.rule_ids.new() as rule:
|
|
rule.name = 'From InterCompany to Company 2'
|
|
rule.action = 'pull'
|
|
rule.picking_type_id = warehouse_company_2.out_type_id
|
|
rule.location_dest_id = self.env.ref('stock.stock_location_inter_wh')
|
|
rule.procure_method = 'make_to_stock'
|
|
route_b = f.save()
|
|
|
|
product = self.env['product.product'].create({
|
|
'name': 'The product from the other company that I absolutely want',
|
|
'type': 'product',
|
|
'route_ids': [(4, route_a.id), (4, route_b.id)]
|
|
})
|
|
|
|
replenish_wizard = self.env['product.replenish'].with_context(default_product_tmpl_id=product.product_tmpl_id.id).create({
|
|
'product_id': product.id,
|
|
'product_tmpl_id': product.product_tmpl_id.id,
|
|
'product_uom_id': self.uom_unit.id,
|
|
'quantity': '5',
|
|
'warehouse_id': warehouse_company_1.id,
|
|
})
|
|
replenish_wizard.launch_replenishment()
|
|
incoming_picking = self.env['stock.picking'].search([('product_id', '=', product.id), ('picking_type_id', '=', warehouse_company_1.in_type_id.id)])
|
|
outgoing_picking = self.env['stock.picking'].search([('product_id', '=', product.id), ('picking_type_id', '=', warehouse_company_2.out_type_id.id)])
|
|
|
|
self.assertEqual(incoming_picking.company_id, self.env.company)
|
|
self.assertEqual(incoming_picking.move_ids.company_id, self.env.company)
|
|
self.assertEqual(outgoing_picking.company_id, company_2)
|
|
self.assertEqual(outgoing_picking.move_ids.company_id, company_2)
|
|
|
|
def test_transit_multi_companies_ultimate(self):
|
|
""" Ensure that inter company rules set the correct company on picking
|
|
and their moves. This test validate a picking with make_to_order moves.
|
|
Moves are created in batch with a company-focused environment. This test
|
|
should create moves for company_2 and company_3 at the same time.
|
|
Ensure they are not create in the same batch.
|
|
"""
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
grp_multi_routes = self.env.ref('stock.group_adv_location')
|
|
grp_multi_companies = self.env.ref('base.group_multi_company')
|
|
self.env.user.write({'groups_id': [(4, grp_multi_loc.id)]})
|
|
self.env.user.write({'groups_id': [(4, grp_multi_routes.id)]})
|
|
self.env.user.write({'groups_id': [(4, grp_multi_companies.id)]})
|
|
|
|
company_2 = self.company
|
|
# Need to add a new company on user.
|
|
self.env.user.write({'company_ids': [(4, company_2.id)]})
|
|
|
|
warehouse_company_1 = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
|
|
|
|
f = Form(self.env['stock.route'])
|
|
f.name = 'From Company 1 to InterCompany'
|
|
f.company_id = self.env.company
|
|
with f.rule_ids.new() as rule:
|
|
rule.name = 'From Company 1 to InterCompany'
|
|
rule.action = 'pull'
|
|
rule.picking_type_id = warehouse_company_1.in_type_id
|
|
rule.location_src_id = self.env.ref('stock.stock_location_inter_wh')
|
|
rule.procure_method = 'make_to_order'
|
|
route_a = f.save()
|
|
|
|
warehouse_company_2 = self.env['stock.warehouse'].search([('company_id', '=', company_2.id)], limit=1)
|
|
f = Form(self.env['stock.route'])
|
|
f.name = 'From InterCompany to Company 2'
|
|
f.company_id = company_2
|
|
with f.rule_ids.new() as rule:
|
|
rule.name = 'From InterCompany to Company 2'
|
|
rule.action = 'pull'
|
|
rule.picking_type_id = warehouse_company_2.out_type_id
|
|
rule.location_dest_id = self.env.ref('stock.stock_location_inter_wh')
|
|
rule.procure_method = 'make_to_stock'
|
|
route_b = f.save()
|
|
|
|
company_3 = self.env['res.company'].create({
|
|
'name': 'Alaska Company'
|
|
})
|
|
|
|
warehouse_company_3 = self.env['stock.warehouse'].search([('company_id', '=', company_3.id)], limit=1)
|
|
f = Form(self.env['stock.route'])
|
|
f.name = 'From InterCompany to Company 3'
|
|
f.company_id = company_3
|
|
with f.rule_ids.new() as rule:
|
|
rule.name = 'From InterCompany to Company 3'
|
|
rule.action = 'pull'
|
|
rule.picking_type_id = warehouse_company_3.out_type_id
|
|
rule.location_dest_id = self.env.ref('stock.stock_location_inter_wh')
|
|
rule.procure_method = 'make_to_stock'
|
|
route_c = f.save()
|
|
|
|
product_from_company_2 = self.env['product.product'].create({
|
|
'name': 'The product from the other company that I absolutely want',
|
|
'type': 'product',
|
|
'route_ids': [(4, route_a.id), (4, route_b.id)]
|
|
})
|
|
|
|
product_from_company_3 = self.env['product.product'].create({
|
|
'name': 'Ice',
|
|
'type': 'product',
|
|
'route_ids': [(4, route_a.id), (4, route_c.id)]
|
|
})
|
|
|
|
f = Form(self.env['stock.picking'], view='stock.view_picking_form')
|
|
f.picking_type_id = warehouse_company_1.out_type_id
|
|
with f.move_ids_without_package.new() as move:
|
|
move.product_id = product_from_company_2
|
|
move.product_uom_qty = 5
|
|
with f.move_ids_without_package.new() as move:
|
|
move.product_id = product_from_company_3
|
|
move.product_uom_qty = 5
|
|
picking = f.save()
|
|
|
|
picking.move_ids_without_package.write({'procure_method': 'make_to_order'})
|
|
picking.action_confirm()
|
|
|
|
incoming_picking = self.env['stock.picking'].search([('product_id', '=', product_from_company_2.id), ('picking_type_id', '=', warehouse_company_1.in_type_id.id)])
|
|
outgoing_picking = self.env['stock.picking'].search([('product_id', '=', product_from_company_2.id), ('picking_type_id', '=', warehouse_company_2.out_type_id.id)])
|
|
|
|
self.assertEqual(incoming_picking.company_id, self.env.company)
|
|
self.assertEqual(incoming_picking.move_ids.mapped('company_id'), self.env.company)
|
|
self.assertEqual(outgoing_picking.company_id, company_2)
|
|
self.assertEqual(outgoing_picking.move_ids.company_id, company_2)
|
|
|
|
incoming_picking = self.env['stock.picking'].search([('product_id', '=', product_from_company_3.id), ('picking_type_id', '=', warehouse_company_1.in_type_id.id)])
|
|
outgoing_picking = self.env['stock.picking'].search([('product_id', '=', product_from_company_3.id), ('picking_type_id', '=', warehouse_company_3.out_type_id.id)])
|
|
|
|
self.assertEqual(incoming_picking.company_id, self.env.company)
|
|
self.assertEqual(incoming_picking.move_ids.mapped('company_id'), self.env.company)
|
|
self.assertEqual(outgoing_picking.company_id, company_3)
|
|
self.assertEqual(outgoing_picking.move_ids.company_id, company_3)
|
|
|
|
def test_picking_scheduled_date_readonlyness(self):
|
|
""" As it seems we keep breaking this thing over and over this small
|
|
test ensure the scheduled_date is writable on a picking in state 'draft' or 'confirmed'
|
|
"""
|
|
partner = self.env['res.partner'].create({'name': 'Hubert Bonisseur de la Bath'})
|
|
product = self.env['product.product'].create({'name': 'Un petit coup de polish', 'type': 'product'})
|
|
wh = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
|
|
|
|
picking = self.env['stock.picking'].create({
|
|
'state': 'draft',
|
|
'picking_type_id': wh.out_type_id.id,
|
|
})
|
|
f = Form(picking, view='stock.view_picking_form')
|
|
f.partner_id = partner
|
|
with f.move_ids_without_package.new() as move:
|
|
move.product_id = product
|
|
move.product_uom_qty = 5
|
|
f.scheduled_date = fields.Datetime.now()
|
|
picking = f.save()
|
|
|
|
f = Form(picking, view='stock.view_picking_form')
|
|
f.scheduled_date = fields.Datetime.now()
|
|
picking = f.save()
|
|
|
|
self.assertEqual(f.state, 'draft')
|
|
picking.action_confirm()
|
|
|
|
f = Form(picking, view='stock.view_picking_form')
|
|
f.scheduled_date = fields.Datetime.now()
|
|
picking = f.save()
|
|
|
|
self.assertEqual(f.state, 'confirmed')
|
|
|
|
def test_scrap_location_readonlyness(self):
|
|
""" As it seems we keep breaking this thing over and over this small
|
|
test ensure the location and scrap_location is writable on a scrap order in state 'draft'
|
|
but not in state 'confirmed'.
|
|
"""
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
self.env.user.write({'groups_id': [(4, grp_multi_loc.id, 0)]})
|
|
|
|
product = self.env['product.product'].create({'name': 'Un petit coup de polish', 'type': 'product'})
|
|
wh = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
|
|
|
|
self.env['stock.quant']._update_available_quantity(product, wh.wh_qc_stock_loc_id, 10)
|
|
|
|
f = Form(self.env['stock.scrap'], view='stock.stock_scrap_form_view')
|
|
f.product_id = product
|
|
scrap = f.save()
|
|
|
|
f = Form(scrap, view='stock.stock_scrap_form_view')
|
|
f.location_id = wh.wh_qc_stock_loc_id
|
|
f.scrap_location_id = wh.wh_pack_stock_loc_id
|
|
scrap = f.save()
|
|
|
|
self.assertEqual(scrap.state, 'draft')
|
|
scrap.action_validate()
|
|
|
|
f = Form(scrap, view='stock.stock_scrap_form_view')
|
|
self.assertEqual(f.state, 'done')
|
|
with self.assertRaises(AssertionError, msg="can't write on readonly field location_id"):
|
|
f.location_id = wh.lot_stock_id
|
|
with self.assertRaises(AssertionError, msg="can't write on readonly field scrap_location_id"):
|
|
f.scrap_location_id = wh.wh_input_stock_loc_id
|
|
|
|
def test_validate_multiple_pickings_with_same_lot_names(self):
|
|
""" Checks only one lot is created when the same lot name is used in
|
|
different pickings and those pickings are validated together.
|
|
"""
|
|
# Creates two tracked products (one by lots and one by SN).
|
|
product_lot = self.env['product.product'].create({
|
|
'name': 'Tracked by lot',
|
|
'type': 'product',
|
|
'tracking': 'lot',
|
|
})
|
|
product_serial = self.env['product.product'].create({
|
|
'name': 'Tracked by SN',
|
|
'type': 'product',
|
|
'tracking': 'serial',
|
|
})
|
|
# Creates two receipts using some lot names in common.
|
|
picking_type = self.env['stock.picking.type'].browse(self.picking_type_in)
|
|
picking_type.show_reserved = True
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = picking_type
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = product_lot
|
|
move.product_uom_qty = 8
|
|
receipt_1 = picking_form.save()
|
|
receipt_1.action_confirm()
|
|
|
|
move_form = Form(receipt_1.move_ids, view="stock.view_stock_move_operations")
|
|
with move_form.move_line_ids.edit(0) as line:
|
|
line.lot_name = 'lot-001'
|
|
line.quantity = 3
|
|
with move_form.move_line_ids.new() as line:
|
|
line.lot_name = 'lot-002'
|
|
line.quantity = 3
|
|
with move_form.move_line_ids.new() as line:
|
|
line.lot_name = 'lot-003'
|
|
line.quantity = 2
|
|
move = move_form.save()
|
|
move.picked = True
|
|
|
|
picking_form = Form(self.env['stock.picking'])
|
|
picking_form.picking_type_id = picking_type
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = product_lot
|
|
move.product_uom_qty = 8
|
|
receipt_2 = picking_form.save()
|
|
receipt_2.action_confirm()
|
|
|
|
move_form = Form(receipt_2.move_ids, view="stock.view_stock_move_operations")
|
|
with move_form.move_line_ids.edit(0) as line:
|
|
line.lot_name = 'lot-003'
|
|
line.quantity = 2
|
|
with move_form.move_line_ids.new() as line:
|
|
line.lot_name = 'lot-004'
|
|
line.quantity = 4
|
|
with move_form.move_line_ids.new() as line:
|
|
line.lot_name = 'lot-001'
|
|
line.quantity = 1
|
|
with move_form.move_line_ids.new() as line:
|
|
line.lot_name = 'lot-005'
|
|
line.quantity = 1
|
|
move = move_form.save()
|
|
move.picked = True
|
|
|
|
# Validates the two receipts and checks the move lines' lot.
|
|
(receipt_1 | receipt_2).button_validate()
|
|
lots = self.env['stock.lot'].search([('product_id', '=', product_lot.id)], order='name asc')
|
|
self.assertEqual(len(lots), 5)
|
|
lot1, lot2, lot3, lot4, lot5 = lots
|
|
self.assertEqual(lot1.name, 'lot-001')
|
|
self.assertEqual(lot2.name, 'lot-002')
|
|
self.assertEqual(lot3.name, 'lot-003')
|
|
self.assertEqual(lot4.name, 'lot-004')
|
|
self.assertEqual(lot5.name, 'lot-005')
|
|
self.assertEqual(receipt_1.move_line_ids[0].lot_id.id, lot1.id)
|
|
self.assertEqual(receipt_1.move_line_ids[1].lot_id.id, lot2.id)
|
|
self.assertEqual(receipt_1.move_line_ids[2].lot_id.id, lot3.id)
|
|
self.assertEqual(receipt_2.move_line_ids[0].lot_id.id, lot3.id)
|
|
self.assertEqual(receipt_2.move_line_ids[1].lot_id.id, lot4.id)
|
|
self.assertEqual(receipt_2.move_line_ids[2].lot_id.id, lot1.id)
|
|
self.assertEqual(receipt_2.move_line_ids[3].lot_id.id, lot5.id)
|
|
|
|
# Checks also it still raise an error when it tries to create multiple time
|
|
# the same serial numbers (same scenario but with SN instead of lots).
|
|
picking_type = self.env['stock.picking.type'].browse(self.picking_type_in)
|
|
picking = self.env['stock.picking'].create({
|
|
'state': 'draft',
|
|
'picking_type_id': picking_type.id,
|
|
})
|
|
picking_form = Form(picking)
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = product_serial
|
|
move.product_uom_qty = 2
|
|
receipt_1 = picking_form.save()
|
|
receipt_1.action_confirm()
|
|
|
|
move_form = Form(receipt_1.move_ids, view="stock.view_stock_move_operations")
|
|
with move_form.move_line_ids.edit(0) as line:
|
|
line.lot_name = 'sn-001'
|
|
with move_form.move_line_ids.edit(1) as line:
|
|
line.lot_name = 'sn-002'
|
|
move = move_form.save()
|
|
move.picked = True
|
|
|
|
picking = self.env['stock.picking'].create({
|
|
'state': 'draft',
|
|
'picking_type_id': picking_type.id,
|
|
})
|
|
picking_form = Form(picking)
|
|
with picking_form.move_ids_without_package.new() as move:
|
|
move.product_id = product_serial
|
|
move.product_uom_qty = 2
|
|
receipt_2 = picking_form.save()
|
|
receipt_2.action_confirm()
|
|
|
|
move_form = Form(receipt_2.move_ids, view="stock.view_stock_move_operations")
|
|
with move_form.move_line_ids.edit(0) as line:
|
|
line.lot_name = 'sn-002'
|
|
with move_form.move_line_ids.edit(1) as line:
|
|
line.lot_name = 'sn-001'
|
|
move = move_form.save()
|
|
move.picked = True
|
|
|
|
# Validates the two receipts => It should raise an error as there is duplicate SN.
|
|
with self.assertRaises(ValidationError):
|
|
(receipt_1 | receipt_2).button_validate()
|
|
|
|
def test_assign_qty_to_first_move(self):
|
|
""" Suppose two out picking waiting for an available quantity. When receiving such
|
|
a quantity, the latter should be assign to the picking with the highest priority
|
|
and the earliest scheduled date. """
|
|
def create_picking(picking_type, from_loc, to_loc, sequence=10, delay=0):
|
|
picking = self.PickingObj.create({
|
|
'picking_type_id': picking_type,
|
|
'location_id': from_loc,
|
|
'location_dest_id': to_loc,
|
|
'state': 'draft',
|
|
})
|
|
self.MoveObj.create({
|
|
'name': self.productA.name,
|
|
'sequence': sequence,
|
|
'date': fields.Datetime.add(fields.Datetime.now(), second=delay),
|
|
'reservation_date': fields.Date.today(),
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 1,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking.id,
|
|
'location_id': from_loc,
|
|
'location_dest_id': to_loc,
|
|
})
|
|
picking.action_confirm()
|
|
return picking
|
|
|
|
out01 = create_picking(self.picking_type_out, self.stock_location, self.customer_location)
|
|
out02 = create_picking(self.picking_type_out, self.stock_location, self.customer_location, sequence=2, delay=1)
|
|
in01 = create_picking(self.picking_type_in, self.supplier_location, self.stock_location, delay=2)
|
|
|
|
in01.button_validate()
|
|
self.assertEqual(out01.state, 'assigned')
|
|
self.assertEqual(out02.state, 'confirmed')
|
|
|
|
out01.button_validate()
|
|
|
|
out03 = create_picking(self.picking_type_out, self.stock_location, self.customer_location, delay=3)
|
|
out03.priority = "1"
|
|
in02 = create_picking(self.picking_type_in, self.supplier_location, self.stock_location, delay=4)
|
|
|
|
in02.button_validate()
|
|
self.assertEqual(out02.state, 'confirmed')
|
|
self.assertEqual(out03.state, 'assigned')
|
|
|
|
def test_auto_assign_backorder(self):
|
|
""" When a backorder is created, the quantities should be assigned if the reservation method
|
|
is set on 'At Confirmation' """
|
|
stock_location = self.env['stock.location'].browse(self.stock_location)
|
|
picking_type_out = self.env['stock.picking.type'].browse(self.picking_type_out)
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 10)
|
|
picking_type_out.reservation_method = 'at_confirm'
|
|
|
|
picking_out = self.PickingObj.create({
|
|
'picking_type_id': picking_type_out.id,
|
|
'location_id': stock_location.id,
|
|
'location_dest_id': self.customer_location,
|
|
'state': 'draft',
|
|
})
|
|
move_out = self.MoveObj.create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 10,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking_out.id,
|
|
'location_id': stock_location.id,
|
|
'location_dest_id': self.customer_location
|
|
})
|
|
move_out.quantity = 7
|
|
move_out.picked = True
|
|
action_dict = picking_out.button_validate()
|
|
backorder_wizard = Form(self.env[action_dict['res_model']].with_context(action_dict['context'])).save()
|
|
backorder_wizard.process()
|
|
|
|
bo = self.env['stock.picking'].search([('backorder_id', '=', picking_out.id)])
|
|
self.assertEqual(bo.state, 'assigned')
|
|
|
|
def test_picking_set_clear_qty(self):
|
|
picking_in = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_in,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location,
|
|
'state': 'draft',
|
|
})
|
|
move_a = self.MoveObj.create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 10,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking_in.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
move_b = self.MoveObj.create({
|
|
'name': self.productB.name,
|
|
'product_id': self.productB.id,
|
|
'product_uom_qty': 10,
|
|
'product_uom': self.productB.uom_id.id,
|
|
'picking_id': picking_in.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location})
|
|
picking_in.action_confirm()
|
|
|
|
self.assertEqual(move_a.quantity, 10)
|
|
self.assertEqual(move_b.quantity, 10)
|
|
|
|
# test unreserve button without manual change
|
|
picking_in.do_unreserve()
|
|
self.assertEqual(move_a.quantity, 0)
|
|
self.assertEqual(move_b.quantity, 0)
|
|
|
|
# test set qty button with manual change
|
|
move_a.quantity = 5
|
|
self.assertTrue(picking_in.show_check_availability)
|
|
picking_in.action_assign()
|
|
self.assertEqual(move_a.quantity, 10)
|
|
self.assertEqual(move_b.quantity, 10)
|
|
self.assertFalse(picking_in.show_check_availability)
|
|
|
|
def test_stock_move_with_partner_id(self):
|
|
""" Ensure that the partner_id of the picking entry is
|
|
transmitted to the SM upon object creation.
|
|
"""
|
|
partner_1 = self.env['res.partner'].create({'name': 'Hubert Bonisseur de la Bath'})
|
|
partner_2 = self.env['res.partner'].create({'name': 'Donald Clairvoyant du Bled'})
|
|
product = self.env['product.product'].create({'name': 'Un petit coup de polish', 'type': 'product'})
|
|
wh = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
|
|
|
|
f = Form(self.env['stock.picking'])
|
|
f.partner_id = partner_1
|
|
f.picking_type_id = wh.out_type_id
|
|
with f.move_ids_without_package.new() as move:
|
|
move.product_id = product
|
|
move.product_uom_qty = 5
|
|
picking = f.save()
|
|
|
|
self.assertEqual(picking.move_ids.partner_id, partner_1)
|
|
|
|
picking.write({'partner_id': partner_2.id})
|
|
self.assertEqual(picking.move_ids.partner_id, partner_2)
|
|
|
|
def test_cancel_picking_with_scrapped_products(self):
|
|
"""
|
|
The user scraps some products of a picking, then cancel this picking
|
|
The test ensures that the scrapped SM is not cancelled
|
|
"""
|
|
stock_location = self.env['stock.location'].browse(self.stock_location)
|
|
self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 10)
|
|
|
|
picking = self.env['stock.picking'].create({
|
|
'picking_type_id': self.picking_type_out,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location,
|
|
})
|
|
move = self.env['stock.move'].create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 1,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location,
|
|
})
|
|
|
|
picking.action_confirm()
|
|
picking.action_assign()
|
|
|
|
scrap = self.env['stock.scrap'].create({
|
|
'picking_id': picking.id,
|
|
'product_id': self.productA.id,
|
|
'product_uom_id': self.productA.uom_id.id,
|
|
'scrap_qty': 1.0,
|
|
})
|
|
scrap.do_scrap()
|
|
|
|
picking.action_cancel()
|
|
|
|
self.assertEqual(picking.state, 'cancel')
|
|
self.assertEqual(move.state, 'cancel')
|
|
self.assertEqual(scrap.move_ids[0].state, 'done')
|
|
|
|
def test_receive_tracked_product(self):
|
|
self.productA.tracking = 'serial'
|
|
type_in = self.env['stock.picking.type'].browse(self.picking_type_in)
|
|
|
|
receipt_form = Form(self.env['stock.picking'])
|
|
receipt_form.picking_type_id = type_in
|
|
with receipt_form.move_ids_without_package.new() as move_line:
|
|
move_line.product_id = self.productA
|
|
receipt = receipt_form.save()
|
|
|
|
move_form = Form(receipt.move_ids, view='stock.view_stock_move_operations')
|
|
with move_form.move_line_ids.new() as line:
|
|
line.lot_name = "USN01"
|
|
move_form.save()
|
|
|
|
receipt.move_ids.picked = True
|
|
receipt.button_validate()
|
|
quant = self.productA.stock_quant_ids.filtered(lambda q: q.location_id.id == self.stock_location)
|
|
|
|
self.assertEqual(receipt.state, 'done')
|
|
self.assertEqual(quant.quantity, 1.0)
|
|
self.assertEqual(quant.lot_id.name, 'USN01')
|
|
|
|
def test_assign_sm_to_existing_picking(self):
|
|
"""
|
|
Suppose:
|
|
- Two warehouses WH01, WH02
|
|
- Three products with the route 'WH02 supplied by WH01'
|
|
We trigger an orderpoint for each product
|
|
There should be two pickings (out from WH01 + in to WH02)
|
|
"""
|
|
wh01_address, wh02_address = self.env['res.partner'].create([{
|
|
'name': 'Address %s' % i,
|
|
'parent_id': self.env.company.id,
|
|
'type': 'delivery',
|
|
} for i in [1, 2]])
|
|
|
|
warehouse01 = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
|
|
warehouse01.partner_id = wh01_address
|
|
warehouse02 = self.env['stock.warehouse'].create({
|
|
'name': 'Second Warehouse',
|
|
'code': 'WH02',
|
|
'partner_id': wh02_address.id,
|
|
'resupply_wh_ids': [(6, 0, warehouse01.ids)],
|
|
})
|
|
|
|
wh01_stock_location = warehouse01.lot_stock_id
|
|
wh02_stock_location = warehouse02.lot_stock_id
|
|
products = self.productA + self.productB + self.productC
|
|
|
|
for product in products:
|
|
product.route_ids = [(6, 0, warehouse02.resupply_route_ids.ids)]
|
|
self.env['stock.quant']._update_available_quantity(product, wh01_stock_location, 10)
|
|
self.env['stock.warehouse.orderpoint'].create({
|
|
'name': 'RR for %s' % product.name,
|
|
'warehouse_id': warehouse02.id,
|
|
'location_id': wh02_stock_location.id,
|
|
'product_id': product.id,
|
|
'product_min_qty': 1,
|
|
'product_max_qty': 5,
|
|
})
|
|
|
|
self.env['procurement.group'].run_scheduler()
|
|
|
|
out_moves = self.env['stock.move'].search([('product_id', 'in', products.ids), ('picking_id', '!=', False), ('location_id', '=', wh01_stock_location.id)])
|
|
in_moves = self.env['stock.move'].search([('product_id', 'in', products.ids), ('picking_id', '!=', False), ('location_dest_id', '=', wh02_stock_location.id)])
|
|
|
|
out_picking = out_moves[0].picking_id
|
|
self.assertEqual(len(out_moves), 3)
|
|
self.assertEqual(out_moves.product_id, products)
|
|
self.assertEqual(out_moves.picking_id, out_picking, 'All SM should be part of the same picking')
|
|
self.assertEqual(out_picking.partner_id, wh02_address, 'It should be an outgoing picking to %s' % wh02_address.display_name)
|
|
|
|
in_picking = in_moves[0].picking_id
|
|
self.assertEqual(len(in_moves), 3)
|
|
self.assertEqual(in_moves.product_id, products)
|
|
self.assertEqual(in_moves.picking_id, in_picking, 'All SM should be part of the same picking')
|
|
self.assertEqual(in_picking.partner_id, wh01_address, 'It should be an incoming picking from %s' % wh01_address.display_name)
|
|
|
|
def test_2steps_and_automatic_reservation(self):
|
|
"""
|
|
Suppose the automatic reservation of the picking is enabled. An out-move
|
|
is waiting for one available P in stock. When receiving some P (in 2
|
|
steps), the out-move should be automatically assigned.
|
|
"""
|
|
self.env['ir.config_parameter'].sudo().set_param('stock.picking_no_auto_reserve', False)
|
|
|
|
warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
|
|
warehouse.reception_steps = 'two_steps'
|
|
|
|
out_move = self.env['stock.move'].create({
|
|
'name': 'out',
|
|
'product_id': self.productA.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'product_uom_qty': 1,
|
|
'picking_type_id': self.picking_type_out,
|
|
'reservation_date': fields.Date.today(),
|
|
})
|
|
out_move._action_confirm()
|
|
|
|
in_input_move = self.env['stock.move'].create({
|
|
'name': 'in',
|
|
'product_id': self.productA.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': warehouse.wh_input_stock_loc_id.id,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'product_uom_qty': 1,
|
|
'picking_type_id': self.picking_type_in,
|
|
})
|
|
in_input_move._action_confirm()
|
|
in_input_move.quantity = 1
|
|
in_input_move.picked = True
|
|
in_input_move._action_done()
|
|
|
|
in_stock_move = self.env['stock.move'].search([('product_id', '=', self.productA.id), ('location_id', '=', warehouse.wh_input_stock_loc_id.id)], limit=1)
|
|
in_stock_move.quantity = 1
|
|
in_stock_move.picked = True
|
|
in_stock_picking = in_stock_move.picking_id
|
|
in_stock_picking.button_validate()
|
|
|
|
self.assertEqual(out_move.move_line_ids.quantity_product_uom, 1.0, 'The out move should be reserved')
|
|
|
|
def test_assign_done_sml_and_validate_it(self):
|
|
"""
|
|
From the detailed operations wizard, create a SML that has a
|
|
sub-location as destination location. After its creation, the
|
|
destination location should not changed. Same when marking the picking
|
|
as done
|
|
"""
|
|
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
|
self.env.user.write({'groups_id': [(4, grp_multi_loc.id, 0)]})
|
|
|
|
warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
|
|
stock_location = warehouse.lot_stock_id
|
|
sub_loc = stock_location.child_ids[0]
|
|
|
|
self.productA.tracking = 'lot'
|
|
|
|
receipt_form = Form(self.env['stock.picking'])
|
|
receipt_form.picking_type_id = self.env.ref('stock.picking_type_in')
|
|
with receipt_form.move_ids_without_package.new() as move:
|
|
move.product_id = self.productA
|
|
receipt = receipt_form.save()
|
|
receipt.action_confirm()
|
|
|
|
with Form(receipt.move_ids, view='stock.view_stock_move_operations') as move_form:
|
|
with move_form.move_line_ids.new() as sml:
|
|
sml.location_dest_id = sub_loc
|
|
sml.lot_name = '123'
|
|
sml.quantity = 10
|
|
|
|
done_sml = receipt.move_ids.move_line_ids.filtered(lambda sml: sml.quantity > 0)
|
|
self.assertEqual(done_sml.location_dest_id, sub_loc)
|
|
|
|
receipt.button_validate()
|
|
|
|
self.assertEqual(receipt.move_ids.move_line_ids.location_dest_id, sub_loc)
|
|
|
|
def test_multi_picking_validation(self):
|
|
""" This test ensures that the validation of 2 pickings is successfull even if they have different operation types """
|
|
|
|
self.env.user.write({'groups_id': [(4, self.env.ref('stock.group_reception_report').id)]})
|
|
picking_A, picking_B = self.PickingObj.create([{
|
|
'picking_type_id': self.picking_type_in,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location
|
|
}, {
|
|
'picking_type_id': self.picking_type_out,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location,
|
|
}])
|
|
self.MoveObj.create([{
|
|
'name': self.DozA.name,
|
|
'product_id': self.DozA.id,
|
|
'product_uom_qty': 10,
|
|
'product_uom': self.DozA.uom_id.id,
|
|
'quantity': 10,
|
|
'picking_id': picking_A.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location
|
|
}, {
|
|
'name': self.DozA.name,
|
|
'product_id': self.DozA.id,
|
|
'product_uom_qty': 10,
|
|
'product_uom': self.DozA.uom_id.id,
|
|
'quantity': 10,
|
|
'picking_id': picking_B.id,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location
|
|
}])
|
|
pickings = picking_A | picking_B
|
|
pickings.move_ids.picked = True
|
|
pickings.button_validate()
|
|
self.assertTrue(all(pickings.mapped(lambda p: p.state == 'done')), "Pickings should be set as done")
|
|
|
|
def test_empty_picking_draft(self):
|
|
""" test an empty still can be reset to draft """
|
|
picking = self.PickingObj.create({
|
|
'picking_type_id': self.picking_type_in,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.stock_location,
|
|
})
|
|
self.assertFalse(picking.move_ids)
|
|
self.assertEqual(picking.state, 'draft')
|
|
|
|
def test_validate_backorder(self):
|
|
""" Test That a backorder can be validated:
|
|
- Create a picking with 2 moves (A and B)
|
|
- Set the quantity done of A to 2 and B to 0
|
|
- Validate the picking and create a backorder
|
|
- Check that the backorder has 2 moves (A with 8 and B with 10)
|
|
- Validate the backorder
|
|
"""
|
|
picking = self.env['stock.picking'].create({
|
|
'picking_type_id': self.picking_type_in,
|
|
'location_id': self.customer_location,
|
|
'location_dest_id': self.stock_location,
|
|
})
|
|
move1 = self.env['stock.move'].create({
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 10,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'picking_id': picking.id,
|
|
'location_id': self.customer_location,
|
|
'location_dest_id': self.stock_location,
|
|
})
|
|
move2 = self.env['stock.move'].create({
|
|
'name': self.productB.name,
|
|
'product_id': self.productB.id,
|
|
'product_uom_qty': 10,
|
|
'product_uom': self.productB.uom_id.id,
|
|
'picking_id': picking.id,
|
|
'location_id': self.customer_location,
|
|
'location_dest_id': self.stock_location,
|
|
})
|
|
picking.action_confirm()
|
|
self.assertRecordValues(picking.move_ids[0], [{'product_id': self.productA.id, 'quantity': 10}])
|
|
self.assertRecordValues(picking.move_ids[1], [{'product_id': self.productB.id, 'quantity': 10}])
|
|
# update the quantity recvied
|
|
move1.quantity = 2
|
|
move2.quantity = 0
|
|
res = picking.button_validate()
|
|
wizard = Form(self.env[res['res_model']].with_context(res['context'])).save()
|
|
wizard.process()
|
|
backorder = picking.backorder_ids
|
|
self.assertRecordValues(backorder.move_ids[0], [{'product_id': self.productB.id, 'quantity': 10}])
|
|
self.assertRecordValues(backorder.move_ids[1], [{'product_id': self.productA.id, 'quantity': 8}])
|
|
backorder.button_validate()
|
|
self.assertEqual(backorder.state, 'done')
|
|
|
|
|
|
@tagged('-at_install', 'post_install')
|
|
class TestStockFlowPostInstall(TestStockCommon):
|
|
|
|
def test_last_delivery_partner_field_on_lot(self):
|
|
stock_location = self.env['stock.location'].browse(self.stock_location)
|
|
|
|
partner = self.env['res.partner'].create({'name': 'Super Partner'})
|
|
|
|
product = self.env['product.product'].create({
|
|
'name': 'Super Product',
|
|
'type': 'product',
|
|
'tracking': 'serial',
|
|
})
|
|
sn = self.env['stock.lot'].create({
|
|
'name': 'super_sn',
|
|
'product_id': product.id,
|
|
'company_id': self.env.company.id,
|
|
})
|
|
self.env['stock.quant']._update_available_quantity(product, stock_location, 1, lot_id=sn)
|
|
|
|
delivery = self.env['stock.picking'].create({
|
|
'partner_id': partner.id,
|
|
'picking_type_id': self.picking_type_out,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location,
|
|
'move_ids': [(0, 0, {
|
|
'name': product.name,
|
|
'product_id': product.id,
|
|
'product_uom': product.uom_id.id,
|
|
'product_uom_qty': 1.0,
|
|
'location_id': self.stock_location,
|
|
'location_dest_id': self.customer_location,
|
|
})],
|
|
})
|
|
delivery.action_confirm()
|
|
delivery.action_assign()
|
|
delivery.move_ids.move_line_ids.quantity = 1
|
|
delivery.move_ids.picked = True
|
|
delivery.button_validate()
|
|
|
|
self.assertEqual(sn.last_delivery_partner_id, partner)
|
|
|
|
def test_several_sm_with_same_product_and_backorders(self):
|
|
picking = self.env['stock.picking'].create({
|
|
'picking_type_id': self.picking_type_in,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location,
|
|
})
|
|
move01, move02 = self.env['stock.move'].create([{
|
|
'name': self.productA.name,
|
|
'product_id': self.productA.id,
|
|
'product_uom_qty': 10,
|
|
'product_uom': self.productA.uom_id.id,
|
|
'description_picking': desc,
|
|
'picking_id': picking.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location,
|
|
} for desc in ['Lorem', 'Ipsum']])
|
|
|
|
picking.action_confirm()
|
|
|
|
move01.quantity = 12
|
|
move02.quantity = 8
|
|
|
|
res = picking.button_validate()
|
|
self.assertIsInstance(res, dict)
|
|
self.assertEqual(res.get('res_model'), 'stock.backorder.confirmation')
|
|
|
|
wizard = Form(self.env[res['res_model']].with_context(res['context'])).save()
|
|
wizard.process()
|
|
|
|
backorder = picking.backorder_ids
|
|
self.assertEqual(backorder.move_ids.product_uom_qty, 2)
|
|
self.assertEqual(backorder.move_ids.description_picking, 'Ipsum')
|
|
|
|
def test_onchange_picking_type_id_and_name(self):
|
|
"""
|
|
when changing picking_type_id of a stock.picking, should change the name too
|
|
"""
|
|
picking_type_1 = self.env['stock.picking.type'].create({
|
|
'name': 'new_picking_type_1',
|
|
'code': 'internal',
|
|
'sequence_code': 'PT1/',
|
|
})
|
|
picking_type_2 = self.env['stock.picking.type'].create({
|
|
'name': 'new_picking_type_2',
|
|
'code': 'internal',
|
|
'sequence_code': 'PT2/',
|
|
})
|
|
picking = self.env['stock.picking'].create({
|
|
'picking_type_id': picking_type_1.id,
|
|
'location_id': self.supplier_location,
|
|
'location_dest_id': self.stock_location,
|
|
})
|
|
self.assertEqual(picking.name, "PT1/00001")
|
|
picking.picking_type_id = picking_type_2
|
|
self.assertEqual(picking.name, "PT2/00001")
|
|
picking.picking_type_id = picking_type_1
|
|
self.assertEqual(picking.name, "PT1/00002")
|
|
picking.picking_type_id = picking_type_1
|
|
self.assertEqual(picking.name, "PT1/00002")
|