mirror of
https://github.com/bringout/oca-ocb-mrp.git
synced 2026-04-25 17:32:04 +02:00
Initial commit: Mrp packages
This commit is contained in:
commit
50d736b3bd
739 changed files with 538193 additions and 0 deletions
|
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_purchase_subcontracting
|
||||
from . import test_sale_dropshipping
|
||||
from . import test_anglo_saxon_valuation
|
||||
|
|
@ -0,0 +1,290 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.stock_account.tests.test_anglo_saxon_valuation_reconciliation_common import ValuationReconciliationTestCommon
|
||||
from odoo import Command
|
||||
from odoo.tests import tagged, Form
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestSubcontractingDropshippingValuation(ValuationReconciliationTestCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
|
||||
categ_form = Form(cls.env['product.category'])
|
||||
categ_form.name = 'fifo auto'
|
||||
categ_form.parent_id = cls.env.ref('product.product_category_all')
|
||||
categ_form.property_cost_method = 'fifo'
|
||||
categ_form.property_valuation = 'real_time'
|
||||
cls.categ_fifo_auto = categ_form.save()
|
||||
|
||||
categ_form = Form(cls.env['product.category'])
|
||||
categ_form.name = 'avco auto'
|
||||
categ_form.parent_id = cls.env.ref('product.product_category_all')
|
||||
categ_form.property_cost_method = 'average'
|
||||
categ_form.property_valuation = 'real_time'
|
||||
cls.categ_avco_auto = categ_form.save()
|
||||
|
||||
cls.dropship_route = cls.env.ref('stock_dropshipping.route_drop_shipping')
|
||||
cls.dropship_subcontractor_route = cls.env.ref('mrp_subcontracting_dropshipping.route_subcontracting_dropshipping')
|
||||
|
||||
(cls.product_a | cls.product_b).type = 'product'
|
||||
|
||||
cls.bom_a = cls.env['mrp.bom'].create({
|
||||
'product_tmpl_id': cls.product_a.product_tmpl_id.id,
|
||||
'type': 'subcontract',
|
||||
'subcontractor_ids': [(6, 0, cls.partner_a.ids)],
|
||||
'bom_line_ids': [
|
||||
(0, 0, {'product_id': cls.product_b.id, 'product_qty': 1.0}),
|
||||
],
|
||||
})
|
||||
|
||||
def test_valuation_subcontracted_and_dropshipped(self):
|
||||
"""
|
||||
Product:
|
||||
- FIFO + Auto
|
||||
- Subcontracted
|
||||
Purchase 2 from Subcontractor to a customer (dropship).
|
||||
Then return 1 to subcontractor and one to stock
|
||||
It should generate the correct valuations AMLs
|
||||
"""
|
||||
# pylint: disable=bad-whitespace
|
||||
all_amls_ids = self.env['account.move.line'].search_read([], ['id'])
|
||||
|
||||
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
||||
self.env.user.write({'groups_id': [(4, grp_multi_loc.id)]})
|
||||
|
||||
(self.product_a | self.product_b).categ_id = self.categ_fifo_auto
|
||||
self.product_b.standard_price = 10
|
||||
|
||||
dropship_picking_type = self.env['stock.picking.type'].search([
|
||||
('company_id', '=', self.env.company.id),
|
||||
('default_location_src_id.usage', '=', 'supplier'),
|
||||
('default_location_dest_id.usage', '=', 'customer'),
|
||||
], limit=1, order='sequence')
|
||||
|
||||
po = self.env['purchase.order'].create({
|
||||
"partner_id": self.partner_a.id,
|
||||
"picking_type_id": dropship_picking_type.id,
|
||||
"dest_address_id": self.partner_b.id,
|
||||
"order_line": [(0, 0, {
|
||||
'product_id': self.product_a.id,
|
||||
'name': self.product_a.name,
|
||||
'product_qty': 2.0,
|
||||
'price_unit': 100,
|
||||
'taxes_id': False,
|
||||
})],
|
||||
})
|
||||
po.button_confirm()
|
||||
|
||||
delivery = po.picking_ids
|
||||
res = delivery.button_validate()
|
||||
Form(self.env['stock.immediate.transfer'].with_context(res['context'])).save().process()
|
||||
|
||||
stock_in_acc_id = self.categ_fifo_auto.property_stock_account_input_categ_id.id
|
||||
stock_out_acc_id = self.categ_fifo_auto.property_stock_account_output_categ_id.id
|
||||
stock_valu_acc_id = self.categ_fifo_auto.property_stock_valuation_account_id.id
|
||||
|
||||
amls = self.env['account.move.line'].search([('id', 'not in', all_amls_ids)])
|
||||
all_amls_ids += amls.ids
|
||||
self.assertRecordValues(amls, [
|
||||
# Compensation of dropshipping value
|
||||
{'account_id': stock_valu_acc_id, 'product_id': self.product_a.id, 'debit': 0.0, 'credit': 20.0},
|
||||
{'account_id': stock_out_acc_id, 'product_id': self.product_a.id, 'debit': 20.0, 'credit': 0.0},
|
||||
# Receipt from subcontractor
|
||||
{'account_id': stock_in_acc_id, 'product_id': self.product_a.id, 'debit': 0.0, 'credit': 220.0},
|
||||
{'account_id': stock_valu_acc_id, 'product_id': self.product_a.id, 'debit': 220.0, 'credit': 0.0},
|
||||
# Delivery to subcontractor
|
||||
{'account_id': stock_valu_acc_id, 'product_id': self.product_b.id, 'debit': 0.0, 'credit': 20.0},
|
||||
{'account_id': stock_out_acc_id, 'product_id': self.product_b.id, 'debit': 20.0, 'credit': 0.0},
|
||||
# Initial dropshipped value
|
||||
{'account_id': stock_valu_acc_id, 'product_id': self.product_a.id, 'debit': 0.0, 'credit': 200.0},
|
||||
{'account_id': stock_out_acc_id, 'product_id': self.product_a.id, 'debit': 200.0, 'credit': 0.0},
|
||||
])
|
||||
|
||||
# return to subcontracting location
|
||||
sbc_location = self.env.company.subcontracting_location_id
|
||||
return_form = Form(self.env['stock.return.picking'].with_context(active_id=delivery.id, active_model='stock.picking'))
|
||||
return_form.location_id = sbc_location
|
||||
with return_form.product_return_moves.edit(0) as line:
|
||||
line.quantity = 1
|
||||
return_wizard = return_form.save()
|
||||
return_id, _ = return_wizard._create_returns()
|
||||
return_picking = self.env['stock.picking'].browse(return_id)
|
||||
return_picking.move_ids.quantity_done = 1
|
||||
return_picking.button_validate()
|
||||
|
||||
amls = self.env['account.move.line'].search([('id', 'not in', all_amls_ids)])
|
||||
all_amls_ids += amls.ids
|
||||
self.assertRecordValues(amls, [
|
||||
{'account_id': stock_valu_acc_id, 'product_id': self.product_a.id, 'debit': 0.0, 'credit': 110.0},
|
||||
{'account_id': stock_in_acc_id, 'product_id': self.product_a.id, 'debit': 110.0, 'credit': 0.0},
|
||||
])
|
||||
|
||||
# return to stock location
|
||||
warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
|
||||
stock_location = warehouse.lot_stock_id
|
||||
stock_location.return_location = True
|
||||
return_form = Form(self.env['stock.return.picking'].with_context(active_id=delivery.id, active_model='stock.picking'))
|
||||
return_form.location_id = stock_location
|
||||
with return_form.product_return_moves.edit(0) as line:
|
||||
line.quantity = 1
|
||||
return_wizard = return_form.save()
|
||||
return_id, _ = return_wizard._create_returns()
|
||||
return_picking = self.env['stock.picking'].browse(return_id)
|
||||
return_picking.move_ids.quantity_done = 1
|
||||
return_picking.button_validate()
|
||||
|
||||
amls = self.env['account.move.line'].search([('id', 'not in', all_amls_ids)])
|
||||
all_amls_ids += amls.ids
|
||||
self.assertRecordValues(amls, [
|
||||
{'account_id': stock_out_acc_id, 'product_id': self.product_a.id, 'debit': 0.0, 'credit': 110.0},
|
||||
{'account_id': stock_valu_acc_id, 'product_id': self.product_a.id, 'debit': 110.0, 'credit': 0.0},
|
||||
])
|
||||
|
||||
def test_avco_valuation_subcontract_and_dropshipped_and_backorder(self):
|
||||
""" Splitting a dropship transfer via backorder and invoicing for delivered quantities
|
||||
should result in SVL records which have accurate values based on the portion of the total
|
||||
order-picking sequence for which they were generated.
|
||||
"""
|
||||
final_product = self.product_a
|
||||
final_product.write({
|
||||
'categ_id': self.categ_avco_auto.id,
|
||||
'invoice_policy': 'delivery',
|
||||
})
|
||||
comp_product = self.product_b
|
||||
comp_product.write({
|
||||
'categ_id': self.categ_avco_auto.id,
|
||||
'route_ids': [(4, self.dropship_subcontractor_route.id)],
|
||||
})
|
||||
|
||||
self.env['product.supplierinfo'].create({
|
||||
'product_tmpl_id': final_product.product_tmpl_id.id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'price': 10,
|
||||
})
|
||||
self.env['product.supplierinfo'].create({
|
||||
'product_tmpl_id': comp_product.product_tmpl_id.id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'price': 1,
|
||||
})
|
||||
|
||||
sale_order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [(0, 0, {
|
||||
'product_id': final_product.id,
|
||||
'route_id': self.dropship_route.id,
|
||||
'product_uom_qty': 100,
|
||||
})],
|
||||
})
|
||||
sale_order.action_confirm()
|
||||
purchase_order = sale_order._get_purchase_orders()[0]
|
||||
purchase_order.button_confirm()
|
||||
dropship_transfer = purchase_order.picking_ids[0]
|
||||
dropship_transfer.move_ids[0].quantity_done = 50
|
||||
dropship_transfer.with_context(cancel_backorder=False)._action_done()
|
||||
account_move_1 = sale_order._create_invoices()
|
||||
account_move_1.action_post()
|
||||
dropship_backorder = dropship_transfer.backorder_ids[0]
|
||||
dropship_backorder.action_set_quantities_to_reservation()
|
||||
dropship_backorder._action_done()
|
||||
account_move_2 = sale_order._create_invoices()
|
||||
account_move_2.action_post()
|
||||
|
||||
self.assertRecordValues(
|
||||
self.env['stock.valuation.layer'].search([('product_id', '=', final_product.id)]),
|
||||
[
|
||||
# DS/01
|
||||
{'reference': dropship_transfer.name, 'quantity': -50, 'value': -500},
|
||||
{'reference': dropship_transfer.move_ids.move_orig_ids[0].name, 'quantity': 50, 'value': 8500},
|
||||
{'reference': dropship_transfer.name, 'quantity': 0, 'value': -8000},
|
||||
# DS/02 - backorder
|
||||
{'reference': dropship_backorder.name, 'quantity': -50, 'value': -500},
|
||||
{'reference': dropship_backorder.move_ids.move_orig_ids[1].name, 'quantity': 50, 'value': 8500},
|
||||
{'reference': dropship_backorder.name, 'quantity': 0, 'value': -8000},
|
||||
]
|
||||
)
|
||||
|
||||
def test_account_line_entry_kit_bom_dropship(self):
|
||||
""" An order delivered via dropship for some kit bom product variant should result in
|
||||
accurate journal entries in the expense and stock output accounts if the cost on the
|
||||
purchase order line has been manually edited.
|
||||
"""
|
||||
kit_final_prod = self.product_a
|
||||
product_c = self.env['product.product'].create({
|
||||
'name': 'product_c',
|
||||
'uom_id': self.env.ref('uom.product_uom_dozen').id,
|
||||
'uom_po_id': self.env.ref('uom.product_uom_dozen').id,
|
||||
'lst_price': 120.0,
|
||||
'standard_price': 100.0,
|
||||
'property_account_income_id': self.copy_account(self.company_data['default_account_revenue']).id,
|
||||
'property_account_expense_id': self.copy_account(self.company_data['default_account_expense']).id,
|
||||
'taxes_id': [Command.set((self.tax_sale_a + self.tax_sale_b).ids)],
|
||||
'supplier_taxes_id': [Command.set((self.tax_purchase_a + self.tax_purchase_b).ids)],
|
||||
})
|
||||
kit_bom = self.env['mrp.bom'].create({
|
||||
'product_tmpl_id': kit_final_prod.product_tmpl_id.id,
|
||||
'product_uom_id': kit_final_prod.uom_id.id,
|
||||
'product_qty': 1.0,
|
||||
'type': 'phantom',
|
||||
})
|
||||
kit_bom.bom_line_ids = [(0, 0, {
|
||||
'product_id': self.product_b.id,
|
||||
'product_qty': 4,
|
||||
}), (0, 0, {
|
||||
'product_id': product_c.id,
|
||||
'product_qty': 2,
|
||||
})]
|
||||
|
||||
self.env['product.supplierinfo'].create({
|
||||
'product_id': self.product_b.id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'price': 160,
|
||||
})
|
||||
self.env['product.supplierinfo'].create({
|
||||
'product_id': product_c.id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'price': 100,
|
||||
})
|
||||
|
||||
(kit_final_prod + self.product_b).categ_id.write({
|
||||
'property_cost_method': 'fifo',
|
||||
'property_valuation': 'real_time',
|
||||
})
|
||||
|
||||
sale_order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_b.id,
|
||||
'order_line': [(0, 0, {
|
||||
'price_unit': 900,
|
||||
'product_id': kit_final_prod.id,
|
||||
'route_id': self.dropship_route.id,
|
||||
'product_uom_qty': 2.0,
|
||||
})],
|
||||
})
|
||||
sale_order.action_confirm()
|
||||
purchase_order = sale_order._get_purchase_orders()[0]
|
||||
purchase_order.button_confirm()
|
||||
dropship_transfer = purchase_order.picking_ids[0]
|
||||
dropship_transfer.move_ids[0].quantity_done = 2.0
|
||||
dropship_transfer.button_validate()
|
||||
|
||||
account_move = sale_order._create_invoices()
|
||||
account_move.action_post()
|
||||
|
||||
# Each product_a should cost:
|
||||
# 4x product_b = 160 * 4 = 640 +
|
||||
# 2x product_c = 100 * 2 = 200
|
||||
# = 840
|
||||
self.assertRecordValues(
|
||||
account_move.line_ids,
|
||||
[
|
||||
{'name': 'product_a', 'debit': 0.0, 'credit': 1800.0},
|
||||
{'name': 'Tax 15% (Copy)', 'debit': 0.0, 'credit': 270.0},
|
||||
{'name': account_move.name, 'debit': 621.0, 'credit': 0.0},
|
||||
{'name': account_move.name, 'debit': 1449.0, 'credit': 0.0},
|
||||
{'name': 'product_a', 'debit': 0.0, 'credit': 840 * 2},
|
||||
{'name': 'product_a', 'debit': 840 * 2, 'credit': 0.0},
|
||||
]
|
||||
)
|
||||
|
|
@ -0,0 +1,495 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import Command
|
||||
from odoo.tests import Form
|
||||
from odoo.addons.mrp_subcontracting.tests.common import TestMrpSubcontractingCommon
|
||||
|
||||
|
||||
class TestSubcontractingDropshippingFlows(TestMrpSubcontractingCommon):
|
||||
|
||||
def test_mrp_subcontracting_dropshipping_1(self):
|
||||
""" Mark the subcontracted product with the route dropship and add the
|
||||
subcontractor as seller. The component has the routes 'MTO', 'Replenish
|
||||
on order' and 'Buy'. Also another partner is set as vendor on the comp.
|
||||
Create a SO and check that:
|
||||
- Delivery between subcontractor and customer for subcontracted product.
|
||||
- Delivery for the component to the subcontractor for the specified wh.
|
||||
- Po created for the component.
|
||||
"""
|
||||
self.env.ref('stock.route_warehouse0_mto').active = True
|
||||
mto_route = self.env['stock.route'].search([('name', '=', 'Replenish on Order (MTO)')])
|
||||
resupply_route = self.env['stock.route'].search([('name', '=', 'Resupply Subcontractor on Order')])
|
||||
buy_route = self.env['stock.route'].search([('name', '=', 'Buy')])
|
||||
dropship_route = self.env['stock.route'].search([('name', '=', 'Dropship')])
|
||||
self.comp2.write({'route_ids': [(4, buy_route.id), (4, mto_route.id), (4, resupply_route.id)]})
|
||||
self.finished.write({'route_ids': [(4, dropship_route.id)]})
|
||||
|
||||
warehouse = self.env['stock.warehouse'].create({
|
||||
'name': 'Warehouse For subcontract',
|
||||
'code': 'WFS'
|
||||
})
|
||||
|
||||
self.env['product.supplierinfo'].create({
|
||||
'product_tmpl_id': self.finished.product_tmpl_id.id,
|
||||
'partner_id': self.subcontractor_partner1.id
|
||||
})
|
||||
|
||||
partner = self.env['res.partner'].create({
|
||||
'name': 'Toto'
|
||||
})
|
||||
self.env['product.supplierinfo'].create({
|
||||
'product_tmpl_id': self.comp2.product_tmpl_id.id,
|
||||
'partner_id': partner.id
|
||||
})
|
||||
|
||||
# Create a receipt picking from the subcontractor
|
||||
so_form = Form(self.env['sale.order'])
|
||||
so_form.partner_id = partner
|
||||
so_form.warehouse_id = warehouse
|
||||
with so_form.order_line.new() as line:
|
||||
line.product_id = self.finished
|
||||
line.product_uom_qty = 1
|
||||
so = so_form.save()
|
||||
so.action_confirm()
|
||||
|
||||
# Pickings should directly be created
|
||||
po = self.env['purchase.order'].search([('origin', 'ilike', so.name)])
|
||||
self.assertTrue(po)
|
||||
|
||||
po.button_approve()
|
||||
|
||||
picking_finished = po.picking_ids
|
||||
self.assertEqual(len(picking_finished), 1.0)
|
||||
self.assertEqual(picking_finished.location_dest_id, partner.property_stock_customer)
|
||||
self.assertEqual(picking_finished.location_id, self.subcontractor_partner1.property_stock_supplier)
|
||||
self.assertEqual(picking_finished.state, 'assigned')
|
||||
|
||||
picking_delivery = self.env['stock.move'].search([
|
||||
('product_id', '=', self.comp2.id),
|
||||
('location_id', '=', warehouse.lot_stock_id.id),
|
||||
('location_dest_id', '=', self.subcontractor_partner1.property_stock_subcontractor.id),
|
||||
]).picking_id
|
||||
self.assertTrue(picking_delivery)
|
||||
self.assertEqual(picking_delivery.state, 'waiting')
|
||||
|
||||
po = self.env['purchase.order.line'].search([
|
||||
('product_id', '=', self.comp2.id),
|
||||
('partner_id', '=', partner.id),
|
||||
]).order_id
|
||||
self.assertTrue(po)
|
||||
|
||||
def test_mrp_subcontracting_purchase_2(self):
|
||||
"""Let's consider a subcontracted BOM with 1 component. Tick "Resupply Subcontractor on Order" on the component and set a supplier on it.
|
||||
Purchase 1 BOM to the subcontractor. Confirm the purchase and change the purchased quantity to 2.
|
||||
Check that 2 components are delivered to the subcontractor
|
||||
"""
|
||||
# Tick "resupply subconractor on order on component"
|
||||
self.bom.bom_line_ids = [(5, 0, 0)]
|
||||
self.bom.bom_line_ids = [(0, 0, {'product_id': self.comp1.id, 'product_qty': 1})]
|
||||
resupply_sub_on_order_route = self.env['stock.route'].search([('name', '=', 'Resupply Subcontractor on Order')])
|
||||
(self.comp1).write({'route_ids': [(4, resupply_sub_on_order_route.id, None)]})
|
||||
# Create a supplier and set it to component
|
||||
vendor = self.env['res.partner'].create({'name': 'AAA', 'email': 'from.test@example.com'})
|
||||
self.env['product.supplierinfo'].create({
|
||||
'partner_id': vendor.id,
|
||||
'price': 50,
|
||||
})
|
||||
self.comp1.write({'seller_ids': [(0, 0, {'partner_id': vendor.id, 'product_code': 'COMP1'})]})
|
||||
# Purchase 1 BOM to the subcontractor
|
||||
po = Form(self.env['purchase.order'])
|
||||
po.partner_id = self.subcontractor_partner1
|
||||
with po.order_line.new() as po_line:
|
||||
po_line.product_id = self.finished
|
||||
po_line.product_qty = 1
|
||||
po_line.price_unit = 100
|
||||
po = po.save()
|
||||
# Confirm the purchase
|
||||
po.button_confirm()
|
||||
# Check one delivery order with the component has been created for the subcontractor
|
||||
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
|
||||
self.assertEqual(mo.state, 'confirmed')
|
||||
# Check that 1 delivery with 1 component for the subcontractor has been created
|
||||
picking_delivery = mo.picking_ids
|
||||
wh = picking_delivery.picking_type_id.warehouse_id
|
||||
origin = picking_delivery.origin
|
||||
self.assertEqual(len(picking_delivery), 1)
|
||||
self.assertEqual(len(picking_delivery.move_ids_without_package), 1)
|
||||
self.assertEqual(picking_delivery.picking_type_id, wh.subcontracting_resupply_type_id)
|
||||
self.assertEqual(picking_delivery.partner_id, self.subcontractor_partner1)
|
||||
|
||||
# Change the purchased quantity to 2
|
||||
po.order_line.write({'product_qty': 2})
|
||||
# Check that a single delivery with the two components for the subcontractor have been created
|
||||
picking_deliveries = self.env['stock.picking'].search([('origin', '=', origin)])
|
||||
self.assertEqual(len(picking_deliveries), 1)
|
||||
self.assertEqual(picking_deliveries.picking_type_id, wh.subcontracting_resupply_type_id)
|
||||
self.assertEqual(picking_deliveries.partner_id, self.subcontractor_partner1)
|
||||
self.assertTrue(picking_deliveries.state != 'cancel')
|
||||
move1 = picking_deliveries.move_ids_without_package
|
||||
self.assertEqual(move1.product_id, self.comp1)
|
||||
self.assertEqual(move1.product_uom_qty, 2)
|
||||
|
||||
def test_dropshipped_component_and_sub_location(self):
|
||||
"""
|
||||
Suppose:
|
||||
- a subcontracted product and a component dropshipped to the subcontractor
|
||||
- the location of the subcontractor is a sub-location of the main subcontrating location
|
||||
This test ensures that the PO that brings the component to the subcontractor has a correct
|
||||
destination address
|
||||
"""
|
||||
subcontract_location = self.env.company.subcontracting_location_id
|
||||
sub_location = self.env['stock.location'].create({
|
||||
'name': 'Super Location',
|
||||
'location_id': subcontract_location.id,
|
||||
'is_subcontracting_location': True,
|
||||
})
|
||||
|
||||
dropship_subcontractor_route = self.env['stock.route'].search([('name', '=', 'Dropship Subcontractor on Order')])
|
||||
|
||||
subcontractor, vendor = self.env['res.partner'].create([
|
||||
{'name': 'SuperSubcontractor', 'property_stock_subcontractor': sub_location.id},
|
||||
{'name': 'SuperVendor'},
|
||||
])
|
||||
|
||||
p_finished, p_compo = self.env['product.product'].create([{
|
||||
'name': 'Finished Product',
|
||||
'type': 'product',
|
||||
'seller_ids': [(0, 0, {'partner_id': subcontractor.id})],
|
||||
}, {
|
||||
'name': 'Component',
|
||||
'type': 'consu',
|
||||
'seller_ids': [(0, 0, {'partner_id': vendor.id})],
|
||||
'route_ids': [(6, 0, dropship_subcontractor_route.ids)]
|
||||
}])
|
||||
|
||||
self.env['mrp.bom'].create({
|
||||
'product_tmpl_id': p_finished.product_tmpl_id.id,
|
||||
'product_qty': 1,
|
||||
'type': 'subcontract',
|
||||
'subcontractor_ids': [(6, 0, subcontractor.ids)],
|
||||
'bom_line_ids': [
|
||||
(0, 0, {'product_id': p_compo.id, 'product_qty': 1}),
|
||||
],
|
||||
})
|
||||
|
||||
subcontract_po = self.env['purchase.order'].create({
|
||||
"partner_id": subcontractor.id,
|
||||
"order_line": [(0, 0, {
|
||||
'product_id': p_finished.id,
|
||||
'name': p_finished.name,
|
||||
'product_qty': 1.0,
|
||||
})],
|
||||
})
|
||||
subcontract_po.button_confirm()
|
||||
|
||||
dropship_po = self.env['purchase.order'].search([('partner_id', '=', vendor.id)])
|
||||
self.assertEqual(dropship_po.dest_address_id, subcontractor)
|
||||
|
||||
def test_po_to_customer(self):
|
||||
"""
|
||||
Create and confirm a PO with a subcontracted move. The picking type of
|
||||
the PO is 'Dropship' and the delivery address a customer. Then, process
|
||||
a return with the stock location as destination and another return with
|
||||
the supplier as destination
|
||||
"""
|
||||
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
||||
self.env.user.write({'groups_id': [(4, grp_multi_loc.id)]})
|
||||
|
||||
subcontractor, client = self.env['res.partner'].create([
|
||||
{'name': 'SuperSubcontractor'},
|
||||
{'name': 'SuperClient'},
|
||||
])
|
||||
|
||||
p_finished, p_compo = self.env['product.product'].create([{
|
||||
'name': 'Finished Product',
|
||||
'type': 'product',
|
||||
'seller_ids': [(0, 0, {'partner_id': subcontractor.id})],
|
||||
}, {
|
||||
'name': 'Component',
|
||||
'type': 'consu',
|
||||
}])
|
||||
|
||||
bom = self.env['mrp.bom'].create({
|
||||
'product_tmpl_id': p_finished.product_tmpl_id.id,
|
||||
'product_qty': 1,
|
||||
'type': 'subcontract',
|
||||
'subcontractor_ids': [(6, 0, subcontractor.ids)],
|
||||
'bom_line_ids': [
|
||||
(0, 0, {'product_id': p_compo.id, 'product_qty': 1}),
|
||||
],
|
||||
})
|
||||
|
||||
dropship_picking_type = self.env['stock.picking.type'].search([
|
||||
('company_id', '=', self.env.company.id),
|
||||
('default_location_src_id.usage', '=', 'supplier'),
|
||||
('default_location_dest_id.usage', '=', 'customer'),
|
||||
], limit=1, order='sequence')
|
||||
|
||||
po = self.env['purchase.order'].create({
|
||||
"partner_id": subcontractor.id,
|
||||
"picking_type_id": dropship_picking_type.id,
|
||||
"dest_address_id": client.id,
|
||||
"order_line": [(0, 0, {
|
||||
'product_id': p_finished.id,
|
||||
'name': p_finished.name,
|
||||
'product_qty': 2.0,
|
||||
})],
|
||||
})
|
||||
po.button_confirm()
|
||||
|
||||
mo = self.env['mrp.production'].search([('bom_id', '=', bom.id)])
|
||||
self.assertEqual(mo.picking_type_id, self.warehouse.subcontracting_type_id)
|
||||
|
||||
delivery = po.picking_ids
|
||||
delivery.move_line_ids.qty_done = 2.0
|
||||
delivery.button_validate()
|
||||
|
||||
self.assertEqual(delivery.state, 'done')
|
||||
self.assertEqual(mo.state, 'done')
|
||||
self.assertEqual(po.order_line.qty_received, 2)
|
||||
|
||||
# return 1 x P_finished to the stock location
|
||||
stock_location = self.warehouse.lot_stock_id
|
||||
stock_location.return_location = True
|
||||
return_form = Form(self.env['stock.return.picking'].with_context(active_ids=delivery.ids, active_id=delivery.id, active_model='stock.picking'))
|
||||
with return_form.product_return_moves.edit(0) as line:
|
||||
line.quantity = 1.0
|
||||
return_form.location_id = stock_location
|
||||
return_wizard = return_form.save()
|
||||
return_picking_id, _pick_type_id = return_wizard._create_returns()
|
||||
|
||||
delivery_return01 = self.env['stock.picking'].browse(return_picking_id)
|
||||
delivery_return01.move_line_ids.qty_done = 1.0
|
||||
delivery_return01.button_validate()
|
||||
|
||||
self.assertEqual(delivery_return01.state, 'done')
|
||||
self.assertEqual(p_finished.qty_available, 1, 'One product has been returned to the stock location, so it should be available')
|
||||
self.assertEqual(po.order_line.qty_received, 2, 'One product has been returned to the stock location, so we should still consider it as received')
|
||||
|
||||
# return 1 x P_finished to the supplier location
|
||||
supplier_location = dropship_picking_type.default_location_src_id
|
||||
return_form = Form(self.env['stock.return.picking'].with_context(active_ids=delivery.ids, active_id=delivery.id, active_model='stock.picking'))
|
||||
with return_form.product_return_moves.edit(0) as line:
|
||||
line.quantity = 1.0
|
||||
return_form.location_id = supplier_location
|
||||
return_wizard = return_form.save()
|
||||
return_picking_id, _pick_type_id = return_wizard._create_returns()
|
||||
|
||||
delivery_return02 = self.env['stock.picking'].browse(return_picking_id)
|
||||
delivery_return02.move_line_ids.qty_done = 1.0
|
||||
delivery_return02.button_validate()
|
||||
|
||||
self.assertEqual(delivery_return02.state, 'done')
|
||||
self.assertEqual(po.order_line.qty_received, 1)
|
||||
|
||||
def test_po_to_subcontractor(self):
|
||||
"""
|
||||
Create and confirm a PO with a subcontracted move. The bought product is
|
||||
also a component of another subcontracted product. The picking type of
|
||||
the PO is 'Dropship' and the delivery address is the other subcontractor
|
||||
"""
|
||||
subcontractor, super_subcontractor = self.env['res.partner'].create([
|
||||
{'name': 'Subcontractor'},
|
||||
{'name': 'SuperSubcontractor'},
|
||||
])
|
||||
|
||||
super_product, product, component = self.env['product.product'].create([{
|
||||
'name': 'Super Product',
|
||||
'type': 'product',
|
||||
'seller_ids': [(0, 0, {'partner_id': super_subcontractor.id})],
|
||||
}, {
|
||||
'name': 'Product',
|
||||
'type': 'product',
|
||||
'seller_ids': [(0, 0, {'partner_id': subcontractor.id})],
|
||||
}, {
|
||||
'name': 'Component',
|
||||
'type': 'consu',
|
||||
}])
|
||||
|
||||
_, bom_product = self.env['mrp.bom'].create([{
|
||||
'product_tmpl_id': super_product.product_tmpl_id.id,
|
||||
'product_qty': 1,
|
||||
'type': 'subcontract',
|
||||
'subcontractor_ids': [(6, 0, super_subcontractor.ids)],
|
||||
'bom_line_ids': [
|
||||
(0, 0, {'product_id': product.id, 'product_qty': 1}),
|
||||
],
|
||||
}, {
|
||||
'product_tmpl_id': product.product_tmpl_id.id,
|
||||
'product_qty': 1,
|
||||
'type': 'subcontract',
|
||||
'subcontractor_ids': [(6, 0, subcontractor.ids)],
|
||||
'bom_line_ids': [
|
||||
(0, 0, {'product_id': component.id, 'product_qty': 1}),
|
||||
],
|
||||
}])
|
||||
|
||||
po = self.env['purchase.order'].create({
|
||||
"partner_id": subcontractor.id,
|
||||
"picking_type_id": self.env.company.dropship_subcontractor_pick_type_id.id,
|
||||
"dest_address_id": super_subcontractor.id,
|
||||
"order_line": [(0, 0, {
|
||||
'product_id': product.id,
|
||||
'name': product.name,
|
||||
'product_qty': 1.0,
|
||||
})],
|
||||
})
|
||||
po.button_confirm()
|
||||
|
||||
mo = self.env['mrp.production'].search([('bom_id', '=', bom_product.id)])
|
||||
self.assertEqual(mo.picking_type_id, self.warehouse.subcontracting_type_id)
|
||||
|
||||
delivery = po.picking_ids
|
||||
self.assertEqual(delivery.location_dest_id, super_subcontractor.property_stock_subcontractor)
|
||||
self.assertTrue(delivery.is_dropship)
|
||||
|
||||
delivery.move_line_ids.qty_done = 1.0
|
||||
delivery.button_validate()
|
||||
|
||||
self.assertEqual(po.order_line.qty_received, 1.0)
|
||||
|
||||
def test_two_boms_same_component_supplier(self):
|
||||
"""
|
||||
The "Dropship Subcontractor" route is modified: the propagation of the
|
||||
buy rule is set to "Leave Empty".
|
||||
Two subcontracted products (different subcontractor) that use the same
|
||||
component. The component has its own supplier. Confirm one PO for each
|
||||
subcontrated product. It should generate two PO from component's
|
||||
supplier to each subcontractor.
|
||||
"""
|
||||
dropship_subcontractor_route = self.env.ref('mrp_subcontracting_dropshipping.route_subcontracting_dropshipping')
|
||||
dropship_subcontractor_route.rule_ids.filtered(lambda r: r.action == 'buy').group_propagation_option = 'none'
|
||||
|
||||
subcontractor01, subcontractor02, component_supplier = self.env['res.partner'].create([{
|
||||
'name': 'Super Partner %d' % i
|
||||
} for i in range(3)])
|
||||
|
||||
product01, product02, component = self.env['product.product'].create([{
|
||||
'name': name,
|
||||
'type': 'product',
|
||||
'seller_ids': [(0, 0, {'partner_id': vendor.id})],
|
||||
'route_ids': [(6, 0, routes)],
|
||||
} for name, vendor, routes in [
|
||||
('SuperProduct 01', subcontractor01, []),
|
||||
('SuperProduct 02', subcontractor02, []),
|
||||
('Component', component_supplier, dropship_subcontractor_route.ids),
|
||||
]])
|
||||
|
||||
self.env['mrp.bom'].create([{
|
||||
'product_tmpl_id': finished.product_tmpl_id.id,
|
||||
'type': 'subcontract',
|
||||
'subcontractor_ids': [(4, subcontractor.id)],
|
||||
'bom_line_ids': [(0, 0, {'product_id': component.id})]
|
||||
} for finished, subcontractor in [
|
||||
(product01, subcontractor01),
|
||||
(product02, subcontractor02),
|
||||
]])
|
||||
|
||||
for (partner, product) in [(subcontractor01, product01), (subcontractor02, product02)]:
|
||||
po_form = Form(self.env['purchase.order'])
|
||||
po_form.partner_id = partner
|
||||
with po_form.order_line.new() as line:
|
||||
line.product_id = product
|
||||
po = po_form.save()
|
||||
po.button_confirm()
|
||||
|
||||
supplier_orders = self.env['purchase.order'].search([('partner_id', '=', component_supplier.id)])
|
||||
self.assertEqual(supplier_orders.dest_address_id, subcontractor01 | subcontractor02)
|
||||
self.assertRecordValues(supplier_orders.order_line, [
|
||||
{'product_id': component.id, 'product_qty': 1.0},
|
||||
{'product_id': component.id, 'product_qty': 1.0},
|
||||
])
|
||||
|
||||
def test_subcontracted_bom_routes(self):
|
||||
"""
|
||||
Take two BoM having those components. One being subcontracted and the other not.
|
||||
- Compo RR : Buy & Reordering rule to resupply subcontractor.
|
||||
- Compo DROP : Buy & Dropship subcontractor on order.
|
||||
Check that depending on the context, the right route is shown on the report.
|
||||
"""
|
||||
route_buy = self.env.ref('purchase_stock.route_warehouse0_buy')
|
||||
route_dropship = self.env['stock.route'].search([('name', '=', 'Dropship Subcontractor on Order')], limit=1)
|
||||
warehouse = self.env['stock.warehouse'].search([], limit=1)
|
||||
|
||||
compo_drop, compo_rr = self.env['product.product'].create([{
|
||||
'name': name,
|
||||
'type': 'product',
|
||||
'seller_ids': [Command.create({'partner_id': self.subcontractor_partner1.parent_id.id})],
|
||||
'route_ids': [Command.set(routes)],
|
||||
} for name, routes in [
|
||||
('Compo DROP', [route_buy.id, route_dropship.id]),
|
||||
('Compo RR', [route_buy.id]),
|
||||
]])
|
||||
|
||||
route_resupply = self.env['stock.route'].search([('name', '=like', '%Resupply Subcontractor'), ('warehouse_ids', '=', warehouse.id)])
|
||||
route_resupply.product_selectable = True
|
||||
self.env['stock.warehouse.orderpoint'].create({
|
||||
'name': 'Resupply Subcontractor',
|
||||
'location_id': self.subcontractor_partner1.property_stock_subcontractor.id,
|
||||
'route_id': route_resupply.id,
|
||||
'product_id': compo_rr.id,
|
||||
'product_min_qty': 0,
|
||||
'product_max_qty': 0,
|
||||
})
|
||||
compo_rr.route_ids = [Command.link(route_resupply.id)]
|
||||
|
||||
bom_subcontract, bom_local = self.env['mrp.bom'].create([{
|
||||
'product_tmpl_id': self.comp1.product_tmpl_id.id,
|
||||
'type': bom_type,
|
||||
'subcontractor_ids': partner_id,
|
||||
'bom_line_ids': [
|
||||
Command.create({'product_id': compo_drop.id, 'product_qty': 1}),
|
||||
Command.create({'product_id': compo_rr.id, 'product_qty': 1}),
|
||||
]
|
||||
} for bom_type, partner_id in [
|
||||
('subcontract', [Command.link(self.subcontractor_partner1.id)]),
|
||||
('normal', False),
|
||||
]])
|
||||
# Need to add the subcontractor as Vendor to have the bom read as subcontracted.
|
||||
self.comp1.write({'seller_ids': [Command.create({'partner_id': self.subcontractor_partner1.id})]})
|
||||
|
||||
report = self.env['report.mrp.report_bom_structure'].with_context(warehouse=warehouse.id)._get_report_data(bom_subcontract.id)
|
||||
component_lines = report.get('lines', []).get('components', [])
|
||||
self.assertEqual(component_lines[0]['product_id'], compo_drop.id)
|
||||
self.assertEqual(component_lines[0]['route_name'], 'Dropship Subcontractor on Order')
|
||||
self.assertEqual(component_lines[1]['product_id'], compo_rr.id)
|
||||
self.assertEqual(component_lines[1]['route_name'], 'Buy', 'Despite the RR linked to it, it should still display the Buy route')
|
||||
|
||||
report = self.env['report.mrp.report_bom_structure'].with_context(warehouse=warehouse.id)._get_report_data(bom_local.id)
|
||||
component_lines = report.get('lines', []).get('components', [])
|
||||
self.assertEqual(component_lines[0]['product_id'], compo_drop.id)
|
||||
self.assertEqual(component_lines[0]['route_name'], 'Buy', 'Outside of the subcontracted context, it should try to resupply stock.')
|
||||
self.assertEqual(component_lines[1]['product_id'], compo_rr.id)
|
||||
self.assertEqual(component_lines[1]['route_name'], 'Buy')
|
||||
|
||||
def test_partner_id_no_overwrite(self):
|
||||
subcontract_location = self.env.company.subcontracting_location_id
|
||||
p1, p2 = self.env['res.partner'].create([
|
||||
{'name': 'partner 1', 'property_stock_subcontractor': subcontract_location.id},
|
||||
{'name': 'partner 2', 'property_stock_subcontractor': subcontract_location.id},
|
||||
])
|
||||
route_resupply = self.env['stock.route'].create({
|
||||
'name': 'Resupply Subcontractor',
|
||||
'rule_ids': [(0, False, {
|
||||
'name': 'Stock -> Subcontractor',
|
||||
'location_src_id': self.env.ref('stock.stock_location_stock').id,
|
||||
'location_dest_id': subcontract_location.id,
|
||||
'company_id': self.env.company.id,
|
||||
'action': 'pull',
|
||||
'auto': 'manual',
|
||||
'picking_type_id': self.env.ref('stock.picking_type_out').id,
|
||||
'partner_address_id': p1.id,
|
||||
})],
|
||||
})
|
||||
self.env['stock.warehouse.orderpoint'].create({
|
||||
'name': 'Resupply Subcontractor',
|
||||
'location_id': subcontract_location.id,
|
||||
'route_id': route_resupply.id,
|
||||
'product_id': self.comp1.id,
|
||||
'product_min_qty': 2,
|
||||
'product_max_qty': 2,
|
||||
})
|
||||
self.env['procurement.group'].run_scheduler()
|
||||
delivery = self.env["stock.move"].search([("product_id", "=", self.comp1.id)]).picking_id
|
||||
self.assertEqual(delivery.partner_id, p1)
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import Form
|
||||
|
||||
from odoo.addons.mrp_subcontracting.tests.common import TestMrpSubcontractingCommon
|
||||
|
||||
|
||||
class TestSaleDropshippingFlows(TestMrpSubcontractingCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.supplier = cls.env["res.partner"].create({"name": "Supplier"})
|
||||
cls.customer = cls.env["res.partner"].create({"name": "Customer"})
|
||||
cls.dropship_route = cls.env.ref('stock_dropshipping.route_drop_shipping')
|
||||
|
||||
def test_dropship_with_different_suppliers(self):
|
||||
"""
|
||||
Suppose a kit with 3 components supplied by 3 vendors
|
||||
When dropshipping this kit, if 2 components are delivered and if the last
|
||||
picking is cancelled, we should consider the kit as fully delivered.
|
||||
"""
|
||||
partners = self.env['res.partner'].create([{'name': 'Vendor %s' % i} for i in range(4)])
|
||||
compo01, compo02, compo03, kit = self.env['product.product'].create([{
|
||||
'name': name,
|
||||
'type': 'consu',
|
||||
'route_ids': [(6, 0, [self.dropship_route.id])],
|
||||
'seller_ids': [(0, 0, {'partner_id': seller.id})],
|
||||
} for name, seller in zip(['Compo01', 'Compo02', 'Compo03', 'Kit'], partners)])
|
||||
|
||||
self.env['mrp.bom'].create({
|
||||
'product_tmpl_id': kit.product_tmpl_id.id,
|
||||
'product_qty': 1,
|
||||
'type': 'phantom',
|
||||
'bom_line_ids': [
|
||||
(0, 0, {'product_id': compo01.id, 'product_qty': 1}),
|
||||
(0, 0, {'product_id': compo02.id, 'product_qty': 1}),
|
||||
(0, 0, {'product_id': compo03.id, 'product_qty': 1}),
|
||||
],
|
||||
})
|
||||
|
||||
sale_order = self.env['sale.order'].create({
|
||||
'partner_id': self.customer.id,
|
||||
'picking_policy': 'direct',
|
||||
'order_line': [
|
||||
(0, 0, {'name': kit.name, 'product_id': kit.id, 'product_uom_qty': 1}),
|
||||
],
|
||||
})
|
||||
sale_order.action_confirm()
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 0)
|
||||
|
||||
purchase_orders = self.env['purchase.order'].search([('partner_id', 'in', partners.ids)])
|
||||
purchase_orders.button_confirm()
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 0)
|
||||
|
||||
# Deliver the first one
|
||||
picking = sale_order.picking_ids.filtered(lambda p: p.partner_id == partners[0])
|
||||
action = picking.button_validate()
|
||||
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
|
||||
wizard.process()
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 0)
|
||||
|
||||
# Deliver the third one
|
||||
picking = sale_order.picking_ids.filtered(lambda p: p.partner_id == partners[2])
|
||||
action = picking.button_validate()
|
||||
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
|
||||
wizard.process()
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 0)
|
||||
|
||||
# Cancel the second one
|
||||
sale_order.picking_ids[1].action_cancel()
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 1)
|
||||
|
||||
def test_return_kit_and_delivered_qty(self):
|
||||
"""
|
||||
Sell a kit thanks to the dropshipping route, return it then deliver it again
|
||||
The delivered quantity should be correctly computed
|
||||
"""
|
||||
compo, kit = self.env['product.product'].create([{
|
||||
'name': n,
|
||||
'type': 'consu',
|
||||
'route_ids': [(6, 0, [self.dropship_route.id])],
|
||||
'seller_ids': [(0, 0, {'partner_id': self.supplier.id})],
|
||||
} for n in ['Compo', 'Kit']])
|
||||
|
||||
self.env['mrp.bom'].create({
|
||||
'product_tmpl_id': kit.product_tmpl_id.id,
|
||||
'product_qty': 1,
|
||||
'type': 'phantom',
|
||||
'bom_line_ids': [
|
||||
(0, 0, {'product_id': compo.id, 'product_qty': 1}),
|
||||
],
|
||||
})
|
||||
|
||||
sale_order = self.env['sale.order'].create({
|
||||
'partner_id': self.customer.id,
|
||||
'picking_policy': 'direct',
|
||||
'order_line': [
|
||||
(0, 0, {'name': kit.name, 'product_id': kit.id, 'product_uom_qty': 1}),
|
||||
],
|
||||
})
|
||||
sale_order.action_confirm()
|
||||
self.env['purchase.order'].search([], order='id desc', limit=1).button_confirm()
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 0.0)
|
||||
|
||||
picking = sale_order.picking_ids
|
||||
action = picking.button_validate()
|
||||
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
|
||||
wizard.process()
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 1.0)
|
||||
|
||||
for case in ['return', 'deliver again']:
|
||||
delivered_before_case = 1.0 if case == 'return' else 0.0
|
||||
delivered_after_case = 0.0 if case == 'return' else 1.0
|
||||
return_form = Form(self.env['stock.return.picking'].with_context(active_ids=[picking.id], active_id=picking.id, active_model='stock.picking'))
|
||||
return_wizard = return_form.save()
|
||||
action = return_wizard.create_returns()
|
||||
picking = self.env['stock.picking'].browse(action['res_id'])
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, delivered_before_case, "Incorrect delivered qty for case '%s'" % case)
|
||||
|
||||
action = picking.button_validate()
|
||||
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
|
||||
wizard.process()
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, delivered_after_case, "Incorrect delivered qty for case '%s'" % case)
|
||||
|
||||
def test_partial_return_kit_and_delivered_qty(self):
|
||||
"""
|
||||
Suppose a kit with 4x the same dropshipped component
|
||||
Suppose a complex delivery process:
|
||||
- Deliver 2 (with backorder)
|
||||
- Return 2
|
||||
- Deliver 1 (with backorder)
|
||||
- Deliver 1 (process "done")
|
||||
- Deliver 1 (from the return)
|
||||
- Deliver 1 (from the return)
|
||||
The test checks the all-or-nothing policy of the delivered quantity
|
||||
This quantity should be 1.0 after the last delivery
|
||||
"""
|
||||
compo, kit = self.env['product.product'].create([{
|
||||
'name': n,
|
||||
'type': 'consu',
|
||||
'route_ids': [(6, 0, [self.dropship_route.id])],
|
||||
'seller_ids': [(0, 0, {'partner_id': self.supplier.id})],
|
||||
} for n in ['Compo', 'Kit']])
|
||||
|
||||
self.env['mrp.bom'].create({
|
||||
'product_tmpl_id': kit.product_tmpl_id.id,
|
||||
'product_qty': 1,
|
||||
'type': 'phantom',
|
||||
'bom_line_ids': [
|
||||
(0, 0, {'product_id': compo.id, 'product_qty': 4}),
|
||||
],
|
||||
})
|
||||
|
||||
sale_order = self.env['sale.order'].create({
|
||||
'partner_id': self.customer.id,
|
||||
'picking_policy': 'direct',
|
||||
'order_line': [
|
||||
(0, 0, {'name': kit.name, 'product_id': kit.id, 'product_uom_qty': 1}),
|
||||
],
|
||||
})
|
||||
sale_order.action_confirm()
|
||||
self.env['purchase.order'].search([], order='id desc', limit=1).button_confirm()
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 0.0, "Delivered components: 0/4")
|
||||
|
||||
picking01 = sale_order.picking_ids
|
||||
picking01.move_ids.quantity_done = 2
|
||||
action = picking01.button_validate()
|
||||
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
|
||||
wizard.process()
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 0.0, "Delivered components: 2/4")
|
||||
|
||||
# Create a return of picking01 (with both components)
|
||||
return_form = Form(self.env['stock.return.picking'].with_context(active_id=picking01.id, active_model='stock.picking'))
|
||||
wizard = return_form.save()
|
||||
wizard.product_return_moves.write({'quantity': 2.0})
|
||||
res = wizard.create_returns()
|
||||
return01 = self.env['stock.picking'].browse(res['res_id'])
|
||||
|
||||
return01.move_ids.quantity_done = 2
|
||||
return01.button_validate()
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 0.0, "Delivered components: 0/4")
|
||||
|
||||
picking02 = picking01.backorder_ids
|
||||
picking02.move_ids.quantity_done = 1
|
||||
action = picking02.button_validate()
|
||||
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
|
||||
wizard.process()
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 0.0, "Delivered components: 1/4")
|
||||
|
||||
picking03 = picking02.backorder_ids
|
||||
picking03.move_ids.quantity_done = 1
|
||||
picking03.button_validate()
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 0.0, "Delivered components: 2/4")
|
||||
|
||||
# Create a return of return01 (with 1 component)
|
||||
return_form = Form(self.env['stock.return.picking'].with_context(active_id=return01.id, active_model='stock.picking'))
|
||||
wizard = return_form.save()
|
||||
wizard.product_return_moves.write({'quantity': 1.0})
|
||||
res = wizard.create_returns()
|
||||
picking04 = self.env['stock.picking'].browse(res['res_id'])
|
||||
|
||||
picking04.move_ids.quantity_done = 1
|
||||
picking04.button_validate()
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 0.0, "Delivered components: 3/4")
|
||||
|
||||
# Create a second return of return01 (with 1 component, the last one)
|
||||
return_form = Form(self.env['stock.return.picking'].with_context(active_id=return01.id, active_model='stock.picking'))
|
||||
wizard = return_form.save()
|
||||
wizard.product_return_moves.write({'quantity': 1.0})
|
||||
res = wizard.create_returns()
|
||||
picking04 = self.env['stock.picking'].browse(res['res_id'])
|
||||
|
||||
picking04.move_ids.quantity_done = 1
|
||||
picking04.button_validate()
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 1, "Delivered components: 4/4")
|
||||
|
||||
def test_cancelled_picking_and_delivered_qty(self):
|
||||
"""
|
||||
The delivered quantity should be zero if all SM are cancelled
|
||||
"""
|
||||
compo, kit = self.env['product.product'].create([{
|
||||
'name': n,
|
||||
'type': 'consu',
|
||||
'route_ids': [(6, 0, [self.dropship_route.id])],
|
||||
'seller_ids': [(0, 0, {'partner_id': self.supplier.id})],
|
||||
} for n in ['Compo', 'Kit']])
|
||||
|
||||
self.env['mrp.bom'].create({
|
||||
'product_tmpl_id': kit.product_tmpl_id.id,
|
||||
'product_qty': 1,
|
||||
'type': 'phantom',
|
||||
'bom_line_ids': [
|
||||
(0, 0, {'product_id': compo.id, 'product_qty': 1}),
|
||||
],
|
||||
})
|
||||
|
||||
sale_order = self.env['sale.order'].create({
|
||||
'partner_id': self.customer.id,
|
||||
'picking_policy': 'direct',
|
||||
'order_line': [
|
||||
(0, 0, {'name': kit.name, 'product_id': kit.id, 'product_uom_qty': 1}),
|
||||
],
|
||||
})
|
||||
sale_order.action_confirm()
|
||||
self.env['purchase.order'].search([], order='id desc', limit=1).button_confirm()
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 0.0)
|
||||
|
||||
sale_order.picking_ids.action_cancel()
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 0.0)
|
||||
|
||||
def test_sale_kit_with_dropshipped_component(self):
|
||||
"""
|
||||
The test checks the delivered quantity of a kit when one of the
|
||||
components is dropshipped
|
||||
"""
|
||||
compo01, compo02, kit = self.env['product.product'].create([{
|
||||
'name': n,
|
||||
'type': 'consu',
|
||||
} for n in ['compo01', 'compo02', 'super kit']])
|
||||
|
||||
compo02.write({
|
||||
'route_ids': [(6, 0, [self.dropship_route.id])],
|
||||
'seller_ids': [(0, 0, {'partner_id': self.supplier.id})],
|
||||
})
|
||||
|
||||
self.env['mrp.bom'].create({
|
||||
'product_tmpl_id': kit.product_tmpl_id.id,
|
||||
'product_qty': 1,
|
||||
'type': 'phantom',
|
||||
'bom_line_ids': [
|
||||
(0, 0, {'product_id': compo01.id, 'product_qty': 1}),
|
||||
(0, 0, {'product_id': compo02.id, 'product_qty': 1}),
|
||||
],
|
||||
})
|
||||
|
||||
sale_order = self.env['sale.order'].create({
|
||||
'partner_id': self.customer.id,
|
||||
'picking_policy': 'direct',
|
||||
'order_line': [
|
||||
(0, 0, {'name': kit.name, 'product_id': kit.id, 'product_uom_qty': 1}),
|
||||
],
|
||||
})
|
||||
sale_order.action_confirm()
|
||||
self.env['purchase.order'].search([], order='id desc', limit=1).button_confirm()
|
||||
|
||||
sale_order.picking_ids.move_ids.quantity_done = 1
|
||||
sale_order.picking_ids[0].button_validate()
|
||||
sale_order.picking_ids[1].button_validate()
|
||||
|
||||
self.assertEqual(sale_order.order_line.qty_delivered, 1.0)
|
||||
Loading…
Add table
Add a link
Reference in a new issue