Initial commit: Odoo 18.0-20251222 extra-addons
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
tests / Detect unreleased dependencies (push) Has been cancelled
tests / test with OCB (push) Has been cancelled
tests / test with Odoo (push) Has been cancelled

This commit is contained in:
tocmo0nlord
2026-03-13 20:43:25 +00:00
parent 36e847a7df
commit adbe430761
9472 changed files with 1265727 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import procurement_group
from . import sale_order_line
from . import stock_move

View File

@@ -0,0 +1,95 @@
# Copyright 2025 360ERP (<https://www.360erp.com>)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import api, models
class ProcurementGroup(models.Model):
_inherit = "procurement.group"
@api.model
def run(self, procurements, raise_user_error=True):
"""
Handle phantom BoM (kit) procurements linked to sale order lines.
If a procurement is for a kit product associated with a sale order line,
this method explodes the kit into its components and generates new
procurements for those components. Original procurements that are not
kits, or not linked to a sale order line with a phantom BoM, are passed through.
:param procurements: A list of Procurement namedtuples
:param raise_user_error: Whether to raise UserError on failure
:return: Result of the original run method
"""
# Collect unique sale_line_ids from procurements that have them
sale_line_ids = list(
set(
p.values.get("sale_line_id")
for p in procurements
if p.values.get("sale_line_id")
)
)
# Pre-fetch sale lines and create a mapping for quick access
sale_lines = self.env["sale.order.line"].browse(sale_line_ids)
sale_lines_map = {sl.id: sl for sl in sale_lines}
procurements_without_kit = []
for procurement in procurements:
sale_line_id = procurement.values.get("sale_line_id")
sale_line = sale_lines_map.get(sale_line_id)
bom_kit = (
sale_line.bom_id.filtered(
lambda bm, pr=procurement: bm.type == "phantom"
and (
# If BoM has product_id, match the procurement's product_id
(bm.product_id and bm.product_id == pr.product_id)
or
# Otherwise (if BoM has no product_id), match the template_id
(
not bm.product_id
and bm.product_tmpl_id == pr.product_id.product_tmpl_id
)
)
)
if sale_line
else False
)
if bom_kit:
order_qty = procurement.product_uom._compute_quantity(
procurement.product_qty, bom_kit.product_uom_id, round=False
)
qty_to_produce = order_qty / bom_kit.product_qty
_dummy, bom_sub_lines = bom_kit.explode(
procurement.product_id,
qty_to_produce,
never_attribute_values=procurement.values.get(
"never_product_template_attribute_value_ids"
),
)
for bom_line, bom_line_data in bom_sub_lines:
bom_line_uom = bom_line.product_uom_id
quant_uom = bom_line.product_id.uom_id
# recreate dict of values since each child has its own bom_line_id
values = dict(procurement.values, bom_line_id=bom_line.id)
component_qty, procurement_uom = (
bom_line_uom._adjust_uom_quantities(
bom_line_data["qty"], quant_uom
)
)
procurements_without_kit.append(
self.env["procurement.group"].Procurement(
bom_line.product_id,
component_qty,
procurement_uom,
procurement.location_id,
procurement.name,
procurement.origin,
procurement.company_id,
values,
)
)
else:
procurements_without_kit.append(procurement)
return super().run(procurements_without_kit, raise_user_error=raise_user_error)

View File

@@ -0,0 +1,36 @@
# Copyright 2020 Akretion Renato Lima <renato.lima@akretion.com.br>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
bom_id = fields.Many2one(
comodel_name="mrp.bom",
string="BoM",
domain="[('product_tmpl_id.product_variant_ids', '=', product_id),"
"'|', ('product_id', '=', product_id), "
"('product_id', '=', False)]",
)
@api.constrains("bom_id", "product_id")
def _check_match_product_variant_ids(self):
for line in self:
if not line.bom_id:
continue
bom_product = line.bom_id.product_id
bom_product_tmpl = line.bom_id.product_tmpl_id
if bom_product and bom_product == line.product_id:
continue
if not bom_product and bom_product_tmpl == line.product_template_id:
continue
raise ValidationError(
_(
"Please select a BoM that matches the product %(product)s",
product=line.product_id.display_name,
)
)

View File

@@ -0,0 +1,14 @@
# Copyright 2020 Akretion Renato Lima <renato.lima@akretion.com.br>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import models
class StockMove(models.Model):
_inherit = "stock.move"
def _prepare_procurement_values(self):
values = super()._prepare_procurement_values()
if self.sale_line_id and self.sale_line_id.bom_id:
values["bom_id"] = self.sale_line_id.bom_id
return values