flectra/addons/sale_stock/tests/test_sale_stock.py
2018-07-09 15:53:28 +05:30

424 lines
22 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra.addons.sale.tests.test_sale_common import TestSale
from flectra.exceptions import UserError
class TestSaleStock(TestSale):
def test_00_sale_stock_invoice(self):
"""
Test SO's changes when playing around with stock moves, quants, pack operations, pickings
and whatever other model there is in stock with "invoice on delivery" products
"""
inv_obj = self.env['account.invoice']
self.so = self.env['sale.order'].create({
'partner_id': self.partner.id,
'partner_invoice_id': self.partner.id,
'partner_shipping_id': self.partner.id,
'order_line': [(0, 0, {'name': p.name, 'product_id': p.id, 'product_uom_qty': 2, 'product_uom': p.uom_id.id, 'price_unit': p.list_price}) for p in self.products.values()],
'pricelist_id': self.env.ref('product.list0').id,
'picking_policy': 'direct',
})
# confirm our standard so, check the picking
self.so.action_confirm()
self.assertTrue(self.so.picking_ids, 'Sale Stock: no picking created for "invoice on delivery" stockable products')
# invoice on order
self.so.action_invoice_create()
# deliver partially, check the so's invoice_status and delivered quantities
self.assertEqual(self.so.invoice_status, 'no', 'Sale Stock: so invoice_status should be "nothing to invoice" after invoicing')
pick = self.so.picking_ids
pick.force_assign()
pick.move_lines.write({'quantity_done': 1})
wiz_act = pick.button_validate()
wiz = self.env[wiz_act['res_model']].browse(wiz_act['res_id'])
wiz.process()
self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale Stock: so invoice_status should be "to invoice" after partial delivery')
del_qties = [sol.qty_delivered for sol in self.so.order_line]
del_qties_truth = [1.0 if sol.product_id.type in ['product', 'consu'] else 0.0 for sol in self.so.order_line]
self.assertEqual(del_qties, del_qties_truth, 'Sale Stock: delivered quantities are wrong after partial delivery')
# invoice on delivery: only stockable products
inv_id = self.so.action_invoice_create()
inv_1 = inv_obj.browse(inv_id)
self.assertTrue(all([il.product_id.invoice_policy == 'delivery' for il in inv_1.invoice_line_ids]),
'Sale Stock: invoice should only contain "invoice on delivery" products')
# complete the delivery and check invoice_status again
self.assertEqual(self.so.invoice_status, 'no',
'Sale Stock: so invoice_status should be "nothing to invoice" after partial delivery and invoicing')
self.assertEqual(len(self.so.picking_ids), 2, 'Sale Stock: number of pickings should be 2')
pick_2 = self.so.picking_ids[0]
pick_2.force_assign()
pick_2.move_lines.write({'quantity_done': 1})
self.assertTrue(pick_2.button_validate(), 'Sale Stock: second picking should be final without need for a backorder')
self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale Stock: so invoice_status should be "to invoice" after complete delivery')
del_qties = [sol.qty_delivered for sol in self.so.order_line]
del_qties_truth = [2.0 if sol.product_id.type in ['product', 'consu'] else 0.0 for sol in self.so.order_line]
self.assertEqual(del_qties, del_qties_truth, 'Sale Stock: delivered quantities are wrong after complete delivery')
# Without timesheet, we manually set the delivered qty for the product serv_del
self.so.order_line[1]['qty_delivered'] = 2.0
inv_id = self.so.action_invoice_create()
self.assertEqual(self.so.invoice_status, 'invoiced',
'Sale Stock: so invoice_status should be "fully invoiced" after complete delivery and invoicing')
def test_01_sale_stock_order(self):
"""
Test SO's changes when playing around with stock moves, quants, pack operations, pickings
and whatever other model there is in stock with "invoice on order" products
"""
# let's cheat and put all our products to "invoice on order"
self.so = self.env['sale.order'].create({
'partner_id': self.partner.id,
'partner_invoice_id': self.partner.id,
'partner_shipping_id': self.partner.id,
'order_line': [(0, 0, {'name': p.name, 'product_id': p.id, 'product_uom_qty': 2, 'product_uom': p.uom_id.id, 'price_unit': p.list_price}) for p in self.products.values()],
'pricelist_id': self.env.ref('product.list0').id,
'picking_policy': 'direct',
})
for sol in self.so.order_line:
sol.product_id.invoice_policy = 'order'
# confirm our standard so, check the picking
self.so.order_line._compute_product_updatable()
self.assertTrue(self.so.order_line[0].product_updatable)
self.so.action_confirm()
self.so.order_line._compute_product_updatable()
self.assertFalse(self.so.order_line[0].product_updatable)
self.assertTrue(self.so.picking_ids, 'Sale Stock: no picking created for "invoice on order" stockable products')
# let's do an invoice for a deposit of 5%
adv_wiz = self.env['sale.advance.payment.inv'].with_context(active_ids=[self.so.id]).create({
'advance_payment_method': 'percentage',
'amount': 5.0,
'product_id': self.env.ref('sale.advance_product_0').id,
})
act = adv_wiz.with_context(open_invoices=True).create_invoices()
inv = self.env['account.invoice'].browse(act['res_id'])
self.assertEqual(inv.amount_untaxed, self.so.amount_untaxed * 5.0 / 100.0, 'Sale Stock: deposit invoice is wrong')
self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale Stock: so should be to invoice after invoicing deposit')
# invoice on order: everything should be invoiced
self.so.action_invoice_create(final=True)
self.assertEqual(self.so.invoice_status, 'invoiced', 'Sale Stock: so should be fully invoiced after second invoice')
# deliver, check the delivered quantities
pick = self.so.picking_ids
pick.force_assign()
pick.move_lines.write({'quantity_done': 2})
self.assertTrue(pick.button_validate(), 'Sale Stock: complete delivery should not need a backorder')
del_qties = [sol.qty_delivered for sol in self.so.order_line]
del_qties_truth = [2.0 if sol.product_id.type in ['product', 'consu'] else 0.0 for sol in self.so.order_line]
self.assertEqual(del_qties, del_qties_truth, 'Sale Stock: delivered quantities are wrong after partial delivery')
# invoice on delivery: nothing to invoice
with self.assertRaises(UserError):
self.so.action_invoice_create()
def test_02_sale_stock_return(self):
"""
Test a SO with a product invoiced on delivery. Deliver and invoice the SO, then do a return
of the picking. Check that a refund invoice is well generated.
"""
# intial so
self.partner = self.env.ref('base.res_partner_1')
self.product = self.env.ref('product.product_delivery_01')
so_vals = {
'partner_id': self.partner.id,
'partner_invoice_id': self.partner.id,
'partner_shipping_id': self.partner.id,
'order_line': [(0, 0, {
'name': self.product.name,
'product_id': self.product.id,
'product_uom_qty': 5.0,
'product_uom': self.product.uom_id.id,
'price_unit': self.product.list_price})],
'pricelist_id': self.env.ref('product.list0').id,
}
self.so = self.env['sale.order'].create(so_vals)
# confirm our standard so, check the picking
self.so.action_confirm()
self.assertTrue(self.so.picking_ids, 'Sale Stock: no picking created for "invoice on delivery" stockable products')
# invoice in on delivery, nothing should be invoiced
self.assertEqual(self.so.invoice_status, 'no', 'Sale Stock: so invoice_status should be "no" instead of "%s".' % self.so.invoice_status)
# deliver completely
pick = self.so.picking_ids
pick.force_assign()
pick.move_lines.write({'quantity_done': 5})
pick.button_validate()
# Check quantity delivered
del_qty = sum(sol.qty_delivered for sol in self.so.order_line)
self.assertEqual(del_qty, 5.0, 'Sale Stock: delivered quantity should be 5.0 instead of %s after complete delivery' % del_qty)
# Check invoice
self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale Stock: so invoice_status should be "to invoice" instead of "%s" before invoicing' % self.so.invoice_status)
inv_1_id = self.so.action_invoice_create()
self.assertEqual(self.so.invoice_status, 'invoiced', 'Sale Stock: so invoice_status should be "invoiced" instead of "%s" after invoicing' % self.so.invoice_status)
self.assertEqual(len(inv_1_id), 1, 'Sale Stock: only one invoice instead of "%s" should be created' % len(inv_1_id))
self.inv_1 = self.env['account.invoice'].browse(inv_1_id)
self.assertEqual(self.inv_1.amount_untaxed, self.inv_1.amount_untaxed, 'Sale Stock: amount in SO and invoice should be the same')
self.inv_1.action_invoice_open()
# Create return picking
StockReturnPicking = self.env['stock.return.picking']
default_data = StockReturnPicking.with_context(active_ids=pick.ids, active_id=pick.ids[0]).default_get(['move_dest_exists', 'original_location_id', 'product_return_moves', 'parent_location_id', 'location_id'])
return_wiz = StockReturnPicking.with_context(active_ids=pick.ids, active_id=pick.ids[0]).create(default_data)
return_wiz.product_return_moves.quantity = 2.0 # Return only 2
return_wiz.product_return_moves.to_refund = True # Refund these 2
res = return_wiz.create_returns()
return_pick = self.env['stock.picking'].browse(res['res_id'])
# Validate picking
return_pick.force_assign()
return_pick.move_lines.write({'quantity_done': 2})
return_pick.button_validate()
# Check invoice
self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale Stock: so invoice_status should be "to invoice" instead of "%s" after picking return' % self.so.invoice_status)
self.assertAlmostEqual(self.so.order_line[0].qty_delivered, 3.0, 'Sale Stock: delivered quantity should be 3.0 instead of "%s" after picking return' % self.so.order_line[0].qty_delivered)
# let's do an invoice with refunds
adv_wiz = self.env['sale.advance.payment.inv'].with_context(active_ids=[self.so.id]).create({
'advance_payment_method': 'all',
})
adv_wiz.with_context(open_invoices=True).create_invoices()
self.inv_2 = self.so.invoice_ids.filtered(lambda r: r.state == 'draft')
self.assertAlmostEqual(self.inv_2.invoice_line_ids[0].quantity, 2.0, 'Sale Stock: refund quantity on the invoice should be 2.0 instead of "%s".' % self.inv_2.invoice_line_ids[0].quantity)
self.assertEqual(self.so.invoice_status, 'no', 'Sale Stock: so invoice_status should be "no" instead of "%s" after invoicing the return' % self.so.invoice_status)
def test_03_sale_stock_delivery_partial(self):
"""
Test a SO with a product invoiced on delivery. Deliver partially and invoice the SO, when
the SO is set on 'done', the SO should be fully invoiced.
"""
# intial so
self.partner = self.env.ref('base.res_partner_1')
self.product = self.env.ref('product.product_delivery_01')
so_vals = {
'partner_id': self.partner.id,
'partner_invoice_id': self.partner.id,
'partner_shipping_id': self.partner.id,
'order_line': [(0, 0, {
'name': self.product.name,
'product_id': self.product.id,
'product_uom_qty': 5.0,
'product_uom': self.product.uom_id.id,
'price_unit': self.product.list_price})],
'pricelist_id': self.env.ref('product.list0').id,
}
self.so = self.env['sale.order'].create(so_vals)
# confirm our standard so, check the picking
self.so.action_confirm()
self.assertTrue(self.so.picking_ids, 'Sale Stock: no picking created for "invoice on delivery" stockable products')
# invoice in on delivery, nothing should be invoiced
self.assertEqual(self.so.invoice_status, 'no', 'Sale Stock: so invoice_status should be "nothing to invoice"')
# deliver partially
pick = self.so.picking_ids
pick.force_assign()
pick.move_lines.write({'quantity_done': 4})
res_dict = pick.button_validate()
wizard = self.env[(res_dict.get('res_model'))].browse(res_dict.get('res_id'))
wizard.process_cancel_backorder()
# Check quantity delivered
del_qty = sum(sol.qty_delivered for sol in self.so.order_line)
self.assertEqual(del_qty, 4.0, 'Sale Stock: delivered quantity should be 4.0 after partial delivery')
# Check invoice
self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale Stock: so invoice_status should be "to invoice" before invoicing')
inv_1_id = self.so.action_invoice_create()
self.assertEqual(self.so.invoice_status, 'no', 'Sale Stock: so invoice_status should be "no" after invoicing')
self.assertEqual(len(inv_1_id), 1, 'Sale Stock: only one invoice should be created')
self.inv_1 = self.env['account.invoice'].browse(inv_1_id)
self.assertEqual(self.inv_1.amount_untaxed, self.inv_1.amount_untaxed, 'Sale Stock: amount in SO and invoice should be the same')
self.so.action_done()
self.assertEqual(self.so.invoice_status, 'invoiced', 'Sale Stock: so invoice_status should be "invoiced" when set to done')
def test_04_create_picking_update_saleorderline(self):
"""
Test that updating multiple sale order lines after a succesful delivery creates a single picking containing
the new move lines.
"""
# sell two products
item1 = self.products['prod_order'] # consumable
item2 = self.products['prod_del'] # stockable
self.so = self.env['sale.order'].create({
'partner_id': self.partner.id,
'order_line': [
(0, 0, {'name': item1.name, 'product_id': item1.id, 'product_uom_qty': 1, 'product_uom': item1.uom_id.id, 'price_unit': item1.list_price}),
(0, 0, {'name': item2.name, 'product_id': item2.id, 'product_uom_qty': 1, 'product_uom': item2.uom_id.id, 'price_unit': item2.list_price}),
],
})
self.so.action_confirm()
# deliver them
# One of the move is for a consumable product, thus is assigned. The second one is for a
# stockable product, thus is unavailable. Hitting `button_validate` will first ask to
# process all the reserved quantities and, if the user chose to process, a second wizard
# will ask to create a backorder for the unavailable product.
self.assertEquals(len(self.so.picking_ids), 1)
res_dict = self.so.picking_ids[0].button_validate()
wizard = self.env[(res_dict.get('res_model'))].browse(res_dict.get('res_id'))
self.assertEqual(wizard._name, 'stock.immediate.transfer')
res_dict = wizard.process()
wizard = self.env[(res_dict.get('res_model'))].browse(res_dict.get('res_id'))
self.assertEqual(wizard._name, 'stock.backorder.confirmation')
wizard.process()
# Now, the original picking is done and there is a new one (the backorder).
self.assertEquals(len(self.so.picking_ids), 2)
for picking in self.so.picking_ids:
move = picking.move_lines
if picking.backorder_id:
self.assertEqual(move.product_id.id, item2.id)
self.assertEqual(move.state, 'confirmed')
else:
self.assertEqual(picking.move_lines.product_id.id, item1.id)
self.assertEqual(move.state, 'done')
# update the two original sale order lines
self.so.write({
'order_line': [
(1, self.so.order_line[0].id, {'product_uom_qty': 2}),
(1, self.so.order_line[1].id, {'product_uom_qty': 2}),
]
})
# a single picking should be created for the new delivery
self.assertEquals(len(self.so.picking_ids), 2)
backorder = self.so.picking_ids.filtered(lambda p: p.backorder_id)
self.assertEqual(len(backorder.move_lines), 2)
for backorder_move in backorder.move_lines:
if backorder_move.product_id.id == item1.id:
self.assertEqual(backorder_move.product_qty, 1)
elif backorder_move.product_id.id == item2.id:
self.assertEqual(backorder_move.product_qty, 2)
def test_05_create_picking_update_saleorderline(self):
""" Same test than test_04 but only with enough products in stock so that the reservation
is successful.
"""
# sell two products
item1 = self.products['prod_order'] # consumable
item2 = self.products['prod_del'] # stockable
self.env['stock.quant']._update_available_quantity(item2, self.env.ref('stock.stock_location_stock'), 2)
self.so = self.env['sale.order'].create({
'partner_id': self.partner.id,
'order_line': [
(0, 0, {'name': item1.name, 'product_id': item1.id, 'product_uom_qty': 1, 'product_uom': item1.uom_id.id, 'price_unit': item1.list_price}),
(0, 0, {'name': item2.name, 'product_id': item2.id, 'product_uom_qty': 1, 'product_uom': item2.uom_id.id, 'price_unit': item2.list_price}),
],
})
self.so.action_confirm()
# deliver them
self.assertEquals(len(self.so.picking_ids), 1)
self.so.picking_ids[0].force_assign()
res_dict = self.so.picking_ids[0].button_validate()
wizard = self.env[(res_dict.get('res_model'))].browse(res_dict.get('res_id'))
wizard.process()
self.assertEquals(self.so.picking_ids[0].state, "done")
# update the two original sale order lines
self.so.write({
'order_line': [
(1, self.so.order_line[0].id, {'product_uom_qty': 2}),
(1, self.so.order_line[1].id, {'product_uom_qty': 2}),
]
})
# a single picking should be created for the new delivery
self.assertEquals(len(self.so.picking_ids), 2)
def test_05_confirm_cancel_confirm(self):
""" Confirm a sale order, cancel it, set to quotation, change the
partner, confirm it again: the second delivery order should have
the new partner.
"""
item1 = self.products['prod_order']
partner1 = self.partner.id
partner2 = self.env.ref('base.res_partner_2').id
so1 = self.env['sale.order'].create({
'partner_id': partner1,
'order_line': [(0, 0, {
'name': item1.name,
'product_id': item1.id,
'product_uom_qty': 1,
'product_uom': item1.uom_id.id,
'price_unit': item1.list_price,
})],
})
so1.action_confirm()
self.assertEqual(len(so1.picking_ids), 1)
self.assertEqual(so1.picking_ids.partner_id.id, partner1)
so1.action_cancel()
so1.action_draft()
so1.partner_id = partner2
so1.partner_shipping_id = partner2 # set by an onchange
so1.action_confirm()
self.assertEqual(len(so1.picking_ids), 2)
picking2 = so1.picking_ids.filtered(lambda p: p.state != 'cancel')
self.assertEqual(picking2.partner_id.id, partner2)
def test_06_uom(self):
""" Sell a dozen of products stocked in units. Check that the quantities on the sale order
lines as well as the delivered quantities are handled in dozen while the moves themselves
are handled in units. Edit the ordered quantities, check that the quantites are correctly
updated on the moves. Edit the ir.config_parameter to propagate the uom of the sale order
lines to the moves and edit a last time the ordered quantities. Deliver, check the
quantities.
"""
uom_unit = self.env.ref('product.product_uom_unit')
uom_dozen = self.env.ref('product.product_uom_dozen')
item1 = self.products['prod_order']
self.assertEqual(item1.uom_id.id, uom_unit.id)
# sell a dozen
so1 = self.env['sale.order'].create({
'partner_id': self.partner.id,
'order_line': [(0, 0, {
'name': item1.name,
'product_id': item1.id,
'product_uom_qty': 1,
'product_uom': uom_dozen.id,
'price_unit': item1.list_price,
})],
})
so1.action_confirm()
# the move should be 12 units
# note: move.product_qty = computed field, always in the uom of the quant
# move.product_uom_qty = stored field representing the initial demand in move.product_uom
move1 = so1.picking_ids.move_lines[0]
self.assertEqual(move1.product_uom_qty, 12)
self.assertEqual(move1.product_uom.id, uom_unit.id)
self.assertEqual(move1.product_qty, 12)
# edit the so line, sell 2 dozen, the move should now be 24 units
so1.order_line.product_uom_qty = 2
self.assertEqual(move1.product_uom_qty, 24)
self.assertEqual(move1.product_uom.id, uom_unit.id)
self.assertEqual(move1.product_qty, 24)
# force the propagation of the uom, sell 3 dozen
self.env['ir.config_parameter'].sudo().set_param('stock.propagate_uom', '1')
so1.order_line.product_uom_qty = 3
move2 = so1.picking_ids.move_lines.filtered(lambda m: m.product_uom.id == uom_dozen.id)
self.assertEqual(move2.product_uom_qty, 1)
self.assertEqual(move2.product_uom.id, uom_dozen.id)
self.assertEqual(move2.product_qty, 12)
# deliver everything
move1.quantity_done = 24
move2.quantity_done = 1
so1.picking_ids.button_validate()
# check the delivered quantity
self.assertEqual(so1.order_line.qty_delivered, 3.0)