Initial commit: OCA Workflow Process packages (456 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:00 +02:00
commit d366e42934
18799 changed files with 1284507 additions and 0 deletions

View file

@ -0,0 +1,12 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import product
from . import product_category
from . import product_elaboration
from . import product_elaboration_mixin
from . import product_elaboration_profile
from . import product_template
from . import res_config_settings
from . import sale_order
from . import stock_move
from . import stock_picking
from . import stock_rule

View file

@ -0,0 +1,13 @@
# Copyright 2019 Tecnativa - Sergio Teruel
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ProductProduct(models.Model):
_inherit = "product.product"
elaboration_profile_id = fields.Many2one(
comodel_name="product.elaboration.profile",
ondelete="restrict",
help="Keep this field empty to use the default value from the product category.",
)

View file

@ -0,0 +1,11 @@
# Copyright 2025 Moduon Team S.L. <info@moduon.team>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ProductCategory(models.Model):
_inherit = "product.category"
elaboration_profile_id = fields.Many2one(
comodel_name="product.elaboration.profile", string="Elaboration Profile"
)

View file

@ -0,0 +1,55 @@
# Copyright 2018 Tecnativa - Sergio Teruel
# Copyright 2019 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class Elaboration(models.Model):
_name = "product.elaboration"
_description = "Product elaborations"
name = fields.Char(required=True, translate=True)
code = fields.Char(string="Short Code")
product_id = fields.Many2one(
comodel_name="product.product",
string="Product",
ondelete="restrict",
domain=[("type", "=", "service"), ("is_elaboration", "=", True)],
required=True,
)
active = fields.Boolean(
default=True,
help="If unchecked, it will allow you to hide the product "
"elaborations without removing it.",
)
route_ids = fields.Many2many(
comodel_name="stock.route",
string="Routes",
domain=[("sale_selectable", "=", True)],
ondelete="restrict",
check_company=True,
)
profile_ids = fields.Many2many(
comodel_name="product.elaboration.profile",
relation="product_elaboration_profile_rel",
column1="elaboration_id",
column2="profile_id",
)
_sql_constraints = [
("name_uniq", "unique(name)", "Name must be unique!"),
("code_uniq", "unique(code)", "Code must be unique!"),
]
@api.model
def name_search(self, name, args=None, operator="ilike", limit=100):
"""Give preference to codes on name search, appending
the rest of the results after.
"""
args = args or []
recs = self.browse()
if name:
recs = self.search([("code", "=ilike", name)] + args, limit=limit)
if not recs:
recs = self.search([("name", operator, name)] + args, limit=limit)
return recs.name_get()

View file

@ -0,0 +1,31 @@
# Copyright 2023 Tecnativa - Sergio Teruel
# Copyright 2023 Tecnativa - Carlos Dauden
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class ProductElaborationMixin(models.AbstractModel):
_name = "product.elaboration.mixin"
_description = "Product Elaboration Mixin"
elaboration_ids = fields.Many2many(
comodel_name="product.elaboration",
string="Elaborations",
)
elaboration_note = fields.Char(
store=True,
)
is_elaboration = fields.Boolean(
store=True,
compute="_compute_is_elaboration",
readonly=False,
)
@api.depends("product_id")
def _compute_is_elaboration(self):
"""We use computed instead of a related field because related fields are not
initialized with their value on one2many which related field is the
inverse_name, so with this we get immediately the value on NewIds.
"""
for line in self:
line.is_elaboration = line.product_id.is_elaboration

View file

@ -0,0 +1,20 @@
# Copyright 2023 Tecnativa - Sergio Teruel
# Copyright 2023 Tecnativa - Carlos Dauden
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ProductElaborationProfile(models.Model):
_name = "product.elaboration.profile"
_description = "Product elaboration profiles"
name = fields.Char(required=True, translate=True)
code = fields.Char(string="Short Code")
active = fields.Boolean(default=True)
elaboration_ids = fields.Many2many(
string="Elaborations",
comodel_name="product.elaboration",
relation="product_elaboration_profile_rel",
column1="profile_id",
column2="elaboration_id",
)

View file

@ -0,0 +1,44 @@
# Copyright 2019 Tecnativa - Sergio Teruel
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class ProductTemplate(models.Model):
_inherit = "product.template"
is_elaboration = fields.Boolean()
elaboration_profile_id = fields.Many2one(
comodel_name="product.elaboration.profile",
compute="_compute_elaboration_profile_id",
inverse="_inverse_elaboration_profile_id",
store=True,
help="Keep this field empty to use the default value from the product category.",
)
@api.depends("product_variant_ids", "product_variant_ids.elaboration_profile_id")
def _compute_elaboration_profile_id(self):
unique_variants = self.filtered(lambda tmpl: tmpl.product_variant_count == 1)
for template in unique_variants:
template.elaboration_profile_id = (
template.product_variant_ids.elaboration_profile_id
)
for template in self - unique_variants:
template.elaboration_profile_id = False
def _inverse_elaboration_profile_id(self):
for template in self:
if len(template.product_variant_ids) == 1:
template.product_variant_ids.elaboration_profile_id = (
template.elaboration_profile_id
)
@api.model_create_multi
def create(self, vals_list):
templates = super(ProductTemplate, self).create(vals_list)
# This is needed to set given values to first variant after creation
for template, vals in zip(templates, vals_list):
if vals.get("elaboration_profile_id"):
template.write(
{"elaboration_profile_id": vals["elaboration_profile_id"]}
)
return templates

View file

@ -0,0 +1,17 @@
# Copyright 2022 Tecnativa - Sergio Teruel
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"
group_elaboration_note_on_delivery_slip = fields.Boolean(
"Display Elaboration notes on Delivery Slips",
implied_group="sale_elaboration.group_elaboration_note_on_delivery_slip",
)
group_elaboration_note_on_picking_operations = fields.Boolean(
"Display Elaboration notes on Picking Operations",
implied_group="sale_elaboration.group_elaboration_note_on_picking_operations",
)

View file

@ -0,0 +1,128 @@
# Copyright 2018 Tecnativa - Sergio Teruel
# Copyright 2019 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import UserError
def _execute_onchanges(records, field_name):
"""Helper methods that executes all onchanges associated to a field."""
for onchange in records._onchange_methods.get(field_name, []):
for record in records:
onchange(record)
class SaleOrder(models.Model):
_inherit = "sale.order"
def _create_elaboration_line(self, product, qty):
"""Create a sale order line from a elaboration product, search a line
with the same elaboration product to add qty
:param product:
:param qty:
:return: the sale order line record created
"""
SaleOrderLine = self.env["sale.order.line"]
sol_for_product = self.order_line.filtered(lambda x: x.product_id == product)[
:1
]
if sol_for_product:
sol_for_product.product_uom_qty += qty
return sol_for_product
sol = SaleOrderLine.new(
{"order_id": self.id, "product_id": product.id, "is_elaboration": True}
)
_execute_onchanges(sol, "product_id")
sol.update({"product_uom_qty": qty})
_execute_onchanges(sol, "product_uom_qty")
vals = sol._convert_to_write(sol._cache)
if self.order_line:
vals["sequence"] = self.order_line[-1].sequence + 1
return SaleOrderLine.sudo().create(vals)
class SaleOrderLine(models.Model):
_inherit = ["sale.order.line", "product.elaboration.mixin"]
_name = "sale.order.line"
date_order = fields.Datetime(related="order_id.date_order", string="Date")
route_id = fields.Many2one(compute="_compute_route_id", store=True, readonly=False)
elaboration_profile_id = fields.Many2one(
comodel_name="product.elaboration.profile",
compute="_compute_elaboration_profile_id",
)
elaboration_price_unit = fields.Float(
"Elab. Price", compute="_compute_elaboration_price_unit", store=True
)
is_prepared = fields.Boolean(
compute=lambda self: None,
search="_search_is_prepared",
help=("Dummy field to be able to find prepared lines"),
)
@api.depends("product_id")
def _compute_elaboration_profile_id(self):
"""Order of applicability: product profile > category profile > no profile"""
self.elaboration_profile_id = False
for line in self.filtered("product_id"):
line.elaboration_profile_id = (
line.product_id.elaboration_profile_id
or line.product_id.categ_id.elaboration_profile_id
)
def get_elaboration_stock_route(self):
self.ensure_one()
return self.elaboration_ids.route_ids[:1]
@api.depends("elaboration_ids")
def _compute_route_id(self):
for line in self:
route_id = line.get_elaboration_stock_route()
if route_id:
line.route_id = route_id
@api.depends("elaboration_ids", "order_id.pricelist_id")
def _compute_elaboration_price_unit(self):
for line in self:
if not line.order_id.pricelist_id:
line.elaboration_price_unit = 0
else:
line.elaboration_price_unit = sum(
line.order_id.pricelist_id._get_products_price(
line.elaboration_ids.product_id,
quantity=1,
).values()
)
def _prepare_invoice_line(self, **optional_values):
vals = super()._prepare_invoice_line(**optional_values)
if self.is_elaboration:
vals["name"] = "{} - {}".format(self.order_id.name, self.name)
return vals
def _search_is_prepared(self, operator, value):
if operator != "=":
raise UserError(
_("Unsupported operator %s for searching on is_prepared") % (operator,)
)
moves = self.env["stock.move"].search(
[
(
"state",
"not in" if value else "in",
[
"draft",
"waiting",
"confirmed",
"partially_available",
"assigned",
],
),
(
"location_dest_id",
"=",
self.env.ref("stock.stock_location_customers").id,
),
]
)
return [("move_ids", "in", moves.ids)]

View file

@ -0,0 +1,23 @@
# Copyright 2023 Tecnativa - Sergio Teruel
# Copyright 2023 Tecnativa - Carlos Dauden
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class StockMove(models.Model):
_inherit = ["stock.move", "product.elaboration.mixin"]
_name = "stock.move"
@api.model
def _prepare_merge_moves_distinct_fields(self):
"""Don't merge moves with distinct elaborations"""
distinct_fields = super()._prepare_merge_moves_distinct_fields()
distinct_fields += ["elaboration_ids", "elaboration_note"]
return distinct_fields
class StockMoveLine(models.Model):
_inherit = "stock.move.line"
elaboration_ids = fields.Many2many(related="move_id.elaboration_ids")
elaboration_note = fields.Char(related="move_id.elaboration_note")

View file

@ -0,0 +1,20 @@
# Copyright 2018 Tecnativa - Sergio Teruel
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import models
class StockPicking(models.Model):
_inherit = "stock.picking"
def _action_done(self):
res = super()._action_done()
for pick in self.filtered(lambda x: x.picking_type_code == "outgoing"):
elaboration_lines = pick.move_ids.filtered(
lambda x: x.sale_line_id.elaboration_ids
)
for line in elaboration_lines:
for product in line.sale_line_id.elaboration_ids.product_id:
line.sale_line_id.order_id._create_elaboration_line(
product, line.quantity_done
)
return res

View file

@ -0,0 +1,44 @@
# Copyright 2023 Tecnativa - Sergio Teruel
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import models
class StockRule(models.Model):
_inherit = "stock.rule"
def _get_stock_move_values(
self,
product_id,
product_qty,
product_uom,
location_id,
name,
origin,
company_id,
values,
):
res = super()._get_stock_move_values(
product_id,
product_qty,
product_uom,
location_id,
name,
origin,
company_id,
values,
)
sale_line_id = values.get("sale_line_id", False)
# Record can be a sale order line or a stock move depending of pull
# and push rules
if sale_line_id:
record = self.env["sale.order.line"].browse(sale_line_id)
else:
record = values.get("move_dest_ids", self.env["stock.move"].browse())[:1]
if record and (record.elaboration_ids or record.elaboration_note):
res.update(
{
"elaboration_ids": [(6, 0, record.elaboration_ids.ids)],
"elaboration_note": record.elaboration_note,
}
)
return res