mirror of
https://github.com/bringout/oca-technical.git
synced 2026-04-18 20:51:59 +02:00
549 lines
20 KiB
Python
549 lines
20 KiB
Python
# Copyright 2016-20 ForgeFlow S.L. (http://www.forgeflow.com)
|
|
# Copyright 2016 Aleph Objects, Inc. (https://www.alephobjects.com/)
|
|
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
|
|
|
from datetime import datetime
|
|
|
|
import odoo.tests.common as common
|
|
|
|
|
|
class TestDdmrpCommon(common.TransactionCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
|
|
cls.env = cls.env(
|
|
context=dict(
|
|
cls.env.context,
|
|
tracking_disable=True,
|
|
# compatibility with ddmrp_cron_actions_as_job,
|
|
# that would delay calls to "cron_actions" in these tests
|
|
test_queue_job_no_delay=True,
|
|
)
|
|
)
|
|
|
|
# Models
|
|
cls.productModel = cls.env["product.product"]
|
|
cls.templateModel = cls.env["product.template"]
|
|
cls.bomModel = cls.env["mrp.bom"]
|
|
cls.bomlineModel = cls.env["mrp.bom.line"]
|
|
cls.bufferModel = cls.env["stock.buffer"]
|
|
cls.pickingModel = cls.env["stock.picking"]
|
|
cls.moveModel = cls.env["stock.move"]
|
|
cls.quantModel = cls.env["stock.quant"]
|
|
cls.estimateModel = cls.env["stock.demand.estimate"]
|
|
cls.aducalcmethodModel = cls.env["product.adu.calculation.method"]
|
|
cls.locationModel = cls.env["stock.location"]
|
|
cls.make_procurement_wiz = cls.env["make.procurement.buffer"]
|
|
cls.user_model = cls.env["res.users"]
|
|
cls.partner_model = cls.env["res.partner"]
|
|
cls.supinfo_model = cls.env["product.supplierinfo"]
|
|
cls.pol_model = cls.env["purchase.order.line"]
|
|
cls.wh_model = cls.env["stock.warehouse"]
|
|
cls.orderpoint_model = cls.env["stock.warehouse.orderpoint"]
|
|
|
|
# Refs
|
|
cls.main_company = cls.env.ref("base.main_company")
|
|
cls.second_company = cls.env.ref("stock.res_company_1")
|
|
cls.warehouse = cls.env.ref("stock.warehouse0")
|
|
cls.warehouse2 = cls.wh_model.create(
|
|
{
|
|
"partner_id": cls.env.ref("base.main_partner").id,
|
|
"name": "Warehouse 2",
|
|
"code": "WH2",
|
|
}
|
|
)
|
|
cls.warehouse_sc = cls.env.ref("stock.stock_warehouse_shop0")
|
|
cls.stock_location = cls.env.ref("stock.stock_location_stock")
|
|
cls.stock_location_sc = cls.warehouse_sc.lot_stock_id
|
|
cls.location_shelf1 = cls.env.ref("stock.stock_location_components")
|
|
cls.supplier_location = cls.env.ref("stock.stock_location_suppliers")
|
|
cls.customer_location = cls.env.ref("stock.stock_location_customers")
|
|
cls.inter_wh = cls.env.ref("stock.stock_location_inter_wh")
|
|
cls.inventory_location = cls.env["stock.location"].search(
|
|
[("usage", "=", "inventory"), ("company_id", "=", cls.main_company.id)],
|
|
limit=1,
|
|
)
|
|
cls.picking_type_out = cls.env.ref("stock.picking_type_out").copy(
|
|
{
|
|
"reservation_method": "manual",
|
|
"sequence_code": "DDMRP-OUT",
|
|
}
|
|
)
|
|
cls.picking_type_in = cls.env.ref("stock.picking_type_in")
|
|
cls.picking_type_internal = cls.env.ref("stock.picking_type_internal").copy(
|
|
{
|
|
"reservation_method": "manual",
|
|
"sequence_code": "DDMRP-INTERNAL",
|
|
}
|
|
)
|
|
cls.uom_unit = cls.env.ref("uom.product_uom_unit")
|
|
cls.dozen_unit = cls.env.ref("uom.product_uom_dozen")
|
|
cls.buffer_profile_pur = cls.env.ref(
|
|
"ddmrp.stock_buffer_profile_replenish_purchased_medium_medium"
|
|
)
|
|
cls.buffer_profile_mmm = cls.env.ref(
|
|
"ddmrp.stock_buffer_profile_replenish_manufactured_medium_medium"
|
|
)
|
|
cls.buffer_profile_distr = cls.env.ref(
|
|
"ddmrp.stock_buffer_profile_replenish_distributed_medium_medium"
|
|
)
|
|
cls.buffer_profile_override = cls.env.ref(
|
|
"ddmrp.stock_buffer_profile_replenish_override_purchased_short_low"
|
|
)
|
|
cls.adu_fixed = cls.env.ref("ddmrp.adu_calculation_method_fixed")
|
|
cls.group_stock_manager = cls.env.ref("stock.group_stock_manager")
|
|
cls.group_mrp_user = cls.env.ref("mrp.group_mrp_user")
|
|
cls.group_change_procure_qty = cls.env.ref(
|
|
"ddmrp.group_change_buffer_procure_qty"
|
|
)
|
|
cls.group_buffer_manager = cls.env.ref("ddmrp.group_stock_buffer_maintainer")
|
|
cls.calendar = cls.env.ref("resource.resource_calendar_std")
|
|
cls.warehouse.calendar_id = cls.calendar
|
|
|
|
# Create users
|
|
cls.user = cls._create_user(
|
|
"user_1",
|
|
[
|
|
cls.group_stock_manager,
|
|
cls.group_mrp_user,
|
|
cls.group_change_procure_qty,
|
|
cls.group_buffer_manager,
|
|
],
|
|
)
|
|
# Create Partners:
|
|
vendor = cls.partner_model.create({"name": "Test Vendor 1"})
|
|
|
|
# Create products and BoM's:
|
|
manufacture_route = cls.env.ref("mrp.route_warehouse0_manufacture")
|
|
cls.productA = cls.productModel.create(
|
|
{
|
|
"name": "product A",
|
|
"standard_price": 1,
|
|
"type": "product",
|
|
"uom_id": cls.uom_unit.id,
|
|
"default_code": "A",
|
|
"route_ids": [(6, 0, manufacture_route.ids)],
|
|
"produce_delay": 10.0,
|
|
}
|
|
)
|
|
cls.component_a1 = cls.productModel.create(
|
|
{
|
|
"name": "Component A-1",
|
|
"standard_price": 1,
|
|
"type": "product",
|
|
"uom_id": cls.uom_unit.id,
|
|
"default_code": "RM-test01",
|
|
}
|
|
)
|
|
cls.bom_a = cls.bomModel.create(
|
|
{
|
|
"product_tmpl_id": cls.productA.product_tmpl_id.id,
|
|
"product_id": cls.productA.id,
|
|
}
|
|
)
|
|
cls.bomlineModel.create(
|
|
{
|
|
"product_id": cls.component_a1.id,
|
|
"product_qty": 2.0,
|
|
"bom_id": cls.bom_a.id,
|
|
}
|
|
)
|
|
# Create locations:
|
|
cls.binA = cls.locationModel.create(
|
|
{
|
|
"usage": "internal",
|
|
"name": "Bin A",
|
|
"location_id": cls.location_shelf1.id,
|
|
"company_id": cls.main_company.id,
|
|
}
|
|
)
|
|
|
|
cls.binB = cls.locationModel.create(
|
|
{
|
|
"usage": "internal",
|
|
"name": "Bin B",
|
|
"location_id": cls.location_shelf1.id,
|
|
"company_id": cls.main_company.id,
|
|
}
|
|
)
|
|
cls.locationModel._parent_store_compute()
|
|
|
|
cls.quant = cls.quantModel.create(
|
|
{
|
|
"location_id": cls.binA.id,
|
|
"company_id": cls.main_company.id,
|
|
"product_id": cls.productA.id,
|
|
"inventory_quantity": 200.0,
|
|
}
|
|
)
|
|
cls.quant.action_apply_inventory()
|
|
|
|
# Product B (purchased):
|
|
buy_route = cls.env.ref("purchase_stock.route_warehouse0_buy")
|
|
cls.product_purchased = cls.productModel.create(
|
|
{
|
|
"name": "product Purchased",
|
|
"standard_price": 1,
|
|
"type": "product",
|
|
"uom_id": cls.uom_unit.id,
|
|
"default_code": "B",
|
|
"route_ids": [(6, 0, buy_route.ids)],
|
|
}
|
|
)
|
|
cls.product_purchased_2 = cls.productModel.create(
|
|
{
|
|
"name": "product Purchased 2",
|
|
"standard_price": 1,
|
|
"type": "product",
|
|
"uom_id": cls.uom_unit.id,
|
|
"default_code": "B",
|
|
"route_ids": [(6, 0, buy_route.ids)],
|
|
}
|
|
)
|
|
cls.supinfo_model.create(
|
|
{
|
|
"product_tmpl_id": cls.product_purchased.product_tmpl_id.id,
|
|
"partner_id": vendor.id,
|
|
"delay": 20.0,
|
|
}
|
|
)
|
|
|
|
# Product C (purchased and with variants):
|
|
cls.template_c = cls.templateModel.create(
|
|
{
|
|
"name": "Product C",
|
|
"type": "product",
|
|
"uom_id": cls.uom_unit.id,
|
|
"default_code": "C",
|
|
"route_ids": [(6, 0, buy_route.ids)],
|
|
}
|
|
)
|
|
cls.color_attribute = cls.env["product.attribute"].create(
|
|
{"name": "Color", "sequence": 1}
|
|
)
|
|
cls.color_blue = cls.env["product.attribute.value"].create(
|
|
{"name": "Blue", "attribute_id": cls.color_attribute.id, "sequence": 1}
|
|
)
|
|
cls.color_orange = cls.env["product.attribute.value"].create(
|
|
{"name": "Orange", "attribute_id": cls.color_attribute.id, "sequence": 2}
|
|
)
|
|
cls.color_green = cls.env["product.attribute.value"].create(
|
|
{"name": "Green", "attribute_id": cls.color_attribute.id, "sequence": 3}
|
|
)
|
|
cls.p_c_color_attribute_line = cls.env[
|
|
"product.template.attribute.line"
|
|
].create(
|
|
{
|
|
"product_tmpl_id": cls.template_c.id,
|
|
"attribute_id": cls.color_attribute.id,
|
|
"value_ids": [
|
|
(6, 0, [cls.color_blue.id, cls.color_orange.id, cls.color_green.id])
|
|
],
|
|
}
|
|
)
|
|
cls.product_c_blue = cls.template_c.product_variant_ids[0]
|
|
cls.product_c_orange = cls.template_c.product_variant_ids[1]
|
|
cls.product_c_green = cls.template_c.product_variant_ids[2]
|
|
cls.p_c_supinfo_blue = cls.supinfo_model.create(
|
|
{
|
|
"product_tmpl_id": cls.template_c.id,
|
|
"product_id": cls.product_c_blue.id,
|
|
"partner_id": vendor.id,
|
|
"delay": 5.0,
|
|
}
|
|
)
|
|
cls.p_c_supinfo_orange = cls.supinfo_model.create(
|
|
{
|
|
"product_tmpl_id": cls.template_c.id,
|
|
"product_id": cls.product_c_orange.id,
|
|
"partner_id": vendor.id,
|
|
"delay": 10.0,
|
|
}
|
|
)
|
|
cls.p_c_supinfo_no_variant = cls.supinfo_model.create(
|
|
{
|
|
"product_tmpl_id": cls.template_c.id,
|
|
"partner_id": vendor.id,
|
|
"delay": 8.0,
|
|
}
|
|
)
|
|
# Product D (distributed):
|
|
cls.product_distributed = cls.productModel.create(
|
|
{
|
|
"name": "product Distributed",
|
|
"standard_price": 1,
|
|
"type": "product",
|
|
"uom_id": cls.uom_unit.id,
|
|
"default_code": "D",
|
|
# TODO: "route_ids": [(6, 0, distribution_route.id)],
|
|
}
|
|
)
|
|
|
|
# Create buffers:
|
|
cls.buffer_a = cls.bufferModel.create(
|
|
{
|
|
"buffer_profile_id": cls.buffer_profile_mmm.id,
|
|
"product_id": cls.productA.id,
|
|
"location_id": cls.stock_location.id,
|
|
"warehouse_id": cls.warehouse.id,
|
|
"qty_multiple": 1.0,
|
|
"adu_calculation_method": cls.adu_fixed.id,
|
|
"adu_fixed": 4.0,
|
|
"lead_days": 10.0,
|
|
"order_spike_horizon": 10.0,
|
|
}
|
|
)
|
|
cls.buffer_purchase = cls.bufferModel.create(
|
|
{
|
|
"buffer_profile_id": cls.buffer_profile_pur.id,
|
|
"product_id": cls.product_purchased.id,
|
|
"location_id": cls.stock_location.id,
|
|
"warehouse_id": cls.warehouse.id,
|
|
"qty_multiple": 1.0,
|
|
"adu_calculation_method": cls.adu_fixed.id,
|
|
"adu_fixed": 5.0,
|
|
}
|
|
)
|
|
cls.buffer_c_blue = cls.bufferModel.create(
|
|
{
|
|
"buffer_profile_id": cls.buffer_profile_pur.id,
|
|
"product_id": cls.product_c_blue.id,
|
|
"location_id": cls.stock_location.id,
|
|
"warehouse_id": cls.warehouse.id,
|
|
"qty_multiple": 1.0,
|
|
"adu_calculation_method": cls.adu_fixed.id,
|
|
"adu_fixed": 5.0,
|
|
}
|
|
)
|
|
cls.buffer_c_orange = cls.bufferModel.create(
|
|
{
|
|
"buffer_profile_id": cls.buffer_profile_pur.id,
|
|
"product_id": cls.product_c_orange.id,
|
|
"location_id": cls.stock_location.id,
|
|
"warehouse_id": cls.warehouse.id,
|
|
"qty_multiple": 1.0,
|
|
"adu_calculation_method": cls.adu_fixed.id,
|
|
"adu_fixed": 5.0,
|
|
}
|
|
)
|
|
cls.buffer_distributed = cls.bufferModel.create(
|
|
{
|
|
"buffer_profile_id": cls.buffer_profile_distr.id,
|
|
"product_id": cls.product_distributed.id,
|
|
"location_id": cls.stock_location.id,
|
|
"warehouse_id": cls.warehouse.id,
|
|
"qty_multiple": 1.0,
|
|
"adu_calculation_method": cls.adu_fixed.id,
|
|
"adu_fixed": 5.0,
|
|
"lead_days": 20,
|
|
}
|
|
)
|
|
|
|
# dates for a period of 120 days for estimates.
|
|
cls.estimate_date_from = cls.calendar.plan_days(1, datetime.today()).date()
|
|
days = 119
|
|
dt = cls.calendar.plan_days(+1 * days + 1, datetime.today())
|
|
cls.estimate_date_to = dt.date()
|
|
|
|
# Run cron jobs:
|
|
cls.bufferModel.cron_ddmrp_adu()
|
|
cls.bufferModel.cron_ddmrp()
|
|
|
|
@classmethod
|
|
def _create_user(cls, login, groups):
|
|
"""Create a user."""
|
|
group_ids = [group.id for group in groups]
|
|
user = cls.user_model.with_context(no_reset_password=True).create(
|
|
{
|
|
"name": "Test User",
|
|
"login": login,
|
|
"password": "demo",
|
|
"email": "test@yourcompany.com",
|
|
"groups_id": [(6, 0, group_ids)],
|
|
}
|
|
)
|
|
return user
|
|
|
|
def create_pickingoutA(self, date_move, qty, uom=False, source_location=None):
|
|
if not uom:
|
|
uom = self.productA.uom_id
|
|
if not source_location:
|
|
source_location = self.binA
|
|
picking = self.pickingModel.with_user(self.user).create(
|
|
{
|
|
"picking_type_id": self.picking_type_out.id,
|
|
"location_id": source_location.id,
|
|
"location_dest_id": self.customer_location.id,
|
|
"scheduled_date": date_move,
|
|
"move_ids": [
|
|
(
|
|
0,
|
|
0,
|
|
{
|
|
"name": "Test move",
|
|
"product_id": self.productA.id,
|
|
"date": date_move,
|
|
"product_uom": uom.id,
|
|
"product_uom_qty": qty,
|
|
"location_id": source_location.id,
|
|
"location_dest_id": self.customer_location.id,
|
|
},
|
|
)
|
|
],
|
|
}
|
|
)
|
|
picking.action_confirm()
|
|
return picking
|
|
|
|
def create_pickinginA(self, date_move, qty):
|
|
picking = self.pickingModel.with_user(self.user).create(
|
|
{
|
|
"picking_type_id": self.picking_type_in.id,
|
|
"location_id": self.supplier_location.id,
|
|
"location_dest_id": self.binA.id,
|
|
"scheduled_date": date_move,
|
|
"move_ids": [
|
|
(
|
|
0,
|
|
0,
|
|
{
|
|
"name": "Test move",
|
|
"product_id": self.productA.id,
|
|
"date": date_move,
|
|
"date_deadline": date_move,
|
|
"product_uom": self.productA.uom_id.id,
|
|
"product_uom_qty": qty,
|
|
"location_id": self.supplier_location.id,
|
|
"location_dest_id": self.binA.id,
|
|
},
|
|
)
|
|
],
|
|
}
|
|
)
|
|
picking.action_confirm()
|
|
return picking
|
|
|
|
def create_pickinginternalA(self, date_move, qty):
|
|
picking = self.pickingModel.with_user(self.user).create(
|
|
{
|
|
"picking_type_id": self.picking_type_internal.id,
|
|
"location_id": self.binA.id,
|
|
"location_dest_id": self.binB.id,
|
|
"scheduled_date": date_move,
|
|
"move_ids": [
|
|
(
|
|
0,
|
|
0,
|
|
{
|
|
"name": "Test move",
|
|
"product_id": self.productA.id,
|
|
"date": date_move,
|
|
"product_uom": self.productA.uom_id.id,
|
|
"product_uom_qty": qty,
|
|
"location_id": self.binA.id,
|
|
"location_dest_id": self.binB.id,
|
|
},
|
|
)
|
|
],
|
|
}
|
|
)
|
|
picking.action_confirm()
|
|
return picking
|
|
|
|
def create_picking_out(self, product, date_move, qty, source_location=None):
|
|
if not source_location:
|
|
source_location = self.binA
|
|
picking = self.pickingModel.with_user(self.user).create(
|
|
{
|
|
"picking_type_id": self.picking_type_out.id,
|
|
"location_id": source_location.id,
|
|
"location_dest_id": self.customer_location.id,
|
|
"scheduled_date": date_move,
|
|
"move_ids": [
|
|
(
|
|
0,
|
|
0,
|
|
{
|
|
"name": "Test move",
|
|
"product_id": product.id,
|
|
"date": date_move,
|
|
"product_uom": product.uom_id.id,
|
|
"product_uom_qty": qty,
|
|
"location_id": source_location.id,
|
|
"location_dest_id": self.customer_location.id,
|
|
},
|
|
)
|
|
],
|
|
}
|
|
)
|
|
picking.action_confirm()
|
|
return picking
|
|
|
|
def create_picking_in(self, product, date_move, qty):
|
|
picking = self.pickingModel.with_user(self.user).create(
|
|
{
|
|
"picking_type_id": self.picking_type_in.id,
|
|
"location_id": self.supplier_location.id,
|
|
"location_dest_id": self.binA.id,
|
|
"scheduled_date": date_move,
|
|
"move_ids": [
|
|
(
|
|
0,
|
|
0,
|
|
{
|
|
"name": "Test move",
|
|
"product_id": product.id,
|
|
"date": date_move,
|
|
"product_uom": product.uom_id.id,
|
|
"product_uom_qty": qty,
|
|
"location_id": self.supplier_location.id,
|
|
"location_dest_id": self.binA.id,
|
|
},
|
|
)
|
|
],
|
|
}
|
|
)
|
|
picking.action_confirm()
|
|
return picking
|
|
|
|
def _do_picking(self, picking, date):
|
|
"""Do picking with only one move on the given date."""
|
|
picking.action_confirm()
|
|
picking.move_ids.quantity_done = picking.move_ids.product_uom_qty
|
|
picking._action_done()
|
|
for move in picking.move_ids:
|
|
move.date = date
|
|
|
|
def create_orderpoint_procurement(self, buffer, make_procurement=True):
|
|
"""Make Procurement from Reordering Rule"""
|
|
wizard = (
|
|
self.make_procurement_wiz.with_user(self.user)
|
|
.with_context(
|
|
active_model="stock.buffer", active_ids=buffer.ids, active_id=buffer.id
|
|
)
|
|
.create({})
|
|
)
|
|
if make_procurement:
|
|
wizard.make_procurement()
|
|
return wizard
|
|
|
|
def create_inventorylossA(self, date_move, qty):
|
|
move = self.moveModel.with_user(self.user).create(
|
|
{
|
|
"name": "Test inventory move",
|
|
"product_id": self.productA.id,
|
|
"date": date_move,
|
|
"product_uom": self.productA.uom_id.id,
|
|
"product_uom_qty": qty,
|
|
"location_id": self.binA.id,
|
|
"location_dest_id": self.inventory_location.id,
|
|
},
|
|
)
|
|
return move
|
|
|
|
def _do_move(self, move, date):
|
|
move._action_confirm()
|
|
move.move_line_ids.qty_done = move.move_line_ids.reserved_uom_qty
|
|
move._action_done()
|
|
move.date = date
|