Initial commit: OCA Technical packages (595 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:03 +02:00
commit 2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions

View file

@ -0,0 +1,3 @@
from . import account_move
from . import rma
from . import sale_order

View file

@ -0,0 +1,25 @@
# Copyright 2021 Tecnativa - David Vidal
# Copyright 2024 Michael Tietz (MT Software) <mtietz@mt-software.de>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import models
from odoo.tools import float_compare
class AccountMove(models.Model):
_inherit = "account.move"
def _check_rma_invoice_lines_qty(self):
"""For those with differences, check if the kit quantity is the same"""
precision = self.env["decimal.precision"].precision_get(
"Product Unit of Measure"
)
lines = super()._check_rma_invoice_lines_qty()
if lines:
return lines.sudo().filtered(
lambda r: (
not r.rma_id.phantom_bom_product
or r.rma_id.phantom_bom_product
and float_compare(r.quantity, r.rma_id.kit_qty, precision) < 0
)
)
return lines

View file

@ -0,0 +1,77 @@
# Copyright 2020 Tecnativa - David Vidal
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, fields, models
from odoo.exceptions import UserError
class Rma(models.Model):
_inherit = "rma"
phantom_bom_product = fields.Many2one(
comodel_name="product.product",
string="Related kit product",
readonly=True,
)
kit_qty = fields.Float(
string="Kit quantity",
digits="Product Unit of Measure",
readonly=True,
help="To how many kits this components corresponds to. Used mainly "
"for refunding the right quantity",
)
rma_kit_register = fields.Char(readonly=True)
def _get_refund_line_quantity(self):
"""Refund the kit, not the component"""
if self.phantom_bom_product:
uom = self.sale_line_id.product_uom or self.phantom_bom_product.uom_id
return (self.kit_qty, uom)
return (self.product_uom_qty, self.product_uom)
def action_refund(self):
"""We want to process them altogether"""
phantom_rmas = self.filtered("phantom_bom_product")
phantom_rmas |= self.search(
[
("rma_kit_register", "in", phantom_rmas.mapped("rma_kit_register")),
("id", "not in", phantom_rmas.ids),
]
)
self -= phantom_rmas
for rma_kit_register in phantom_rmas.mapped("rma_kit_register"):
# We want to avoid refunding kits that aren't completely processed
rmas_by_register = phantom_rmas.filtered(
lambda x: x.rma_kit_register == rma_kit_register
)
if any(rmas_by_register.filtered(lambda x: x.state != "received")):
raise UserError(
_("You can't refund a kit in wich some RMAs aren't received")
)
self |= rmas_by_register[0]
res = super().action_refund()
# We can just link the line to an RMA but we can link several RMAs
# to one invoice line.
for rma_kit_register in set(phantom_rmas.mapped("rma_kit_register")):
grouped_rmas = phantom_rmas.filtered(
lambda x: x.rma_kit_register == rma_kit_register
)
lead_rma = grouped_rmas.filtered("refund_line_id")
grouped_rmas -= lead_rma
grouped_rmas.write(
{
"refund_line_id": lead_rma.refund_line_id.id,
"refund_id": lead_rma.refund_id.id,
"state": "refunded",
}
)
return res
def action_draft(self):
if self.filtered(lambda r: r.state == "cancelled" and r.phantom_bom_product):
raise UserError(
_(
"To avoid kit quantities inconsistencies it is not possible to convert "
"to draft a cancelled RMA. You should do a new one from the sales order."
)
)
return super().action_draft()

View file

@ -0,0 +1,132 @@
# Copyright 2020 Tecnativa - David Vidal
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import models
class SaleOrder(models.Model):
_inherit = "sale.order"
def _prepare_rma_wizard_line_vals(self, data):
"""Set the real kit product"""
vals = super()._prepare_rma_wizard_line_vals(data)
if data.get("phantom_bom_product"):
vals["phantom_bom_product"] = data.get("phantom_bom_product").id
vals["per_kit_quantity"] = data.get("per_kit_quantity", 0)
vals["phantom_kit_line"] = data.get("phantom_kit_line", False)
return vals
def get_delivery_rma_data(self):
"""Get the phantom lines we'll be showing in the wizard"""
data_list = super().get_delivery_rma_data()
kit_products = {
(x.get("phantom_bom_product"), x.get("sale_line_id"))
for x in data_list
if x.get("phantom_bom_product")
}
# For every unique phantom product we'll create a phantom line wich
# will be using as the control in frontend and for display purposes
# in backend
for product, sale_line_id in kit_products:
order_line_obj = self.env["sale.order.line"]
product_obj = self.env["product.product"]
first_component_dict = next(
x
for x in data_list
if x.get("phantom_bom_product", product_obj) == product
and x.get("sale_line_id", order_line_obj) == sale_line_id
)
component_index = data_list.index(first_component_dict)
# Prevent miscalculation if there partial deliveries
quantity = sum(
x.get("quantity", 0)
for x in data_list
if x.get("sale_line_id")
and x.get("product") == first_component_dict.get("product")
and x.get("sale_line_id") == first_component_dict.get("sale_line_id")
)
data_list.insert(
component_index,
{
"product": product,
"quantity": (
first_component_dict.get("per_kit_quantity")
and (quantity / first_component_dict.get("per_kit_quantity"))
),
"uom": first_component_dict.get(
"sale_line_id", order_line_obj
).product_uom,
"phantom_kit_line": True,
"picking": False,
"sale_line_id": first_component_dict.get(
"sale_line_id", order_line_obj
),
},
)
return data_list
class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
def get_delivery_move(self):
self.ensure_one()
if self.product_id and not self._rma_is_kit_product():
return super().get_delivery_move()
return self.move_ids.filtered(
lambda m: (
m.state == "done"
and not m.scrapped
and m.location_dest_id.usage == "customer"
and (
not m.origin_returned_move_id
or (m.origin_returned_move_id and m.to_refund)
)
)
)
def prepare_sale_rma_data(self):
"""We'll take both the sale order product and the phantom one so we
can play with them when filtering or showing to the customer"""
self.ensure_one()
data = super().prepare_sale_rma_data()
if self.product_id and self._rma_is_kit_product():
for d in data:
d.update(
{
"phantom_bom_product": self.product_id,
"per_kit_quantity": self._get_kit_qty(d.get("product")),
}
)
return data
def _get_kit_qty(self, product_id):
"""Compute how many kit components were demanded from this line. We
rely on the matching of sale order and pickings demands, but if those
were manually changed, it could lead to inconsistencies"""
self.ensure_one()
if (
self.product_id
and not self._rma_is_kit_product()
or not self.product_uom_qty
):
return 0
component_demand = sum(
self.move_ids.filtered(
lambda x: x.product_id == product_id and not x.origin_returned_move_id
).mapped("product_uom_qty")
)
return component_demand / self.product_uom_qty
def _rma_is_kit_product(self):
"""The method _is_phantom_bom isn't available anymore. We wan't to use
the same rule Odoo does in core"""
bom = (
self.env["mrp.bom"]
.sudo()
._bom_find(
products=self.product_id,
company_id=self.company_id.id,
bom_type="phantom",
)
)
return bom and bom.get(self.product_id).type == "phantom"