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,6 @@
from . import multi_level_mrp
from . import make_procurement_buffer
from . import mrp_bom_change_location
from . import ddmrp_duplicate_buffer
from . import ddmrp_run
from . import res_config_settings

View file

@ -0,0 +1,98 @@
# Copyright 2023 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class DdmrpDuplicateBuffer(models.TransientModel):
_name = "ddmrp.duplicate.buffer"
_description = "DDMRP Duplicate Buffer"
@api.model
def default_get(self, fields_list):
res = super().default_get(fields_list)
buffer_obj = self.env["stock.buffer"]
buffer_ids = self.env.context.get("active_ids", [])
active_model = self.env.context["active_model"]
if not buffer_ids:
return
assert active_model == "stock.buffer", "Bad context propagation"
if len(buffer_ids) > 1:
res.update(
{
"type": "location",
}
)
else:
buffer = buffer_obj.browse(buffer_ids)
res.update(
{
"type": "both",
"product_id": buffer.product_id.id,
"location_id": buffer.location_id.id,
}
)
return res
type = fields.Selection(
string="Duplication Type",
selection=[
("both", "Change both values"),
("product", "Change Product"),
("location", "Change Location"),
],
required=True,
)
product_id = fields.Many2one(
comodel_name="product.product",
string="New Product",
)
location_id = fields.Many2one(
comodel_name="stock.location",
string="New Location",
)
def action_duplicate_buffer(self):
buffer_obj = self.env["stock.buffer"]
buffer_ids = self.env.context["active_ids"] or []
if self.type in ["product", "both"] and not self.product_id:
raise UserError(_("Please select a New Product."))
if self.type in ["location", "both"] and not self.location_id:
raise UserError(_("Please select a New Location."))
copy_buffers = self.env["stock.buffer"]
for buffer in buffer_obj.browse(buffer_ids):
default = {}
if self.type in ["product", "both"]:
default["product_id"] = self.product_id.id
if self.type in ["location", "both"]:
default["location_id"] = self.location_id.id
default["warehouse_id"] = self.location_id.warehouse_id.id
default["company_id"] = self.location_id.company_id.id
copy_buffers |= buffer.copy(default)
if len(copy_buffers) == 1:
view_id = self.env.ref("ddmrp.stock_buffer_view_form").id
return {
"name": _("Duplicated Buffers"),
"type": "ir.actions.act_window",
"res_model": "stock.buffer",
"res_id": copy_buffers.id,
"view_mode": "form",
"view_id": view_id,
}
else:
xmlid = "ddmrp.action_stock_buffer"
action = self.env["ir.actions.act_window"]._for_xml_id(xmlid)
action.update(
{
"name": _("Duplicated Buffers"),
"res_model": "stock.buffer",
"view_mode": "tree,form",
"domain": [("id", "in", copy_buffers.ids)],
}
)
return action

View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="ddmrp_duplicate_buffer_wizard" model="ir.ui.view">
<field name="name">Ddmrp Duplicate Buffer Wizard</field>
<field name="model">ddmrp.duplicate.buffer</field>
<field name="arch" type="xml">
<form string="Copy Ddmrp Stock Buffer">
<group>
<group>
<field name="type" />
<field
name="product_id"
attrs="{'invisible':[('type','=', 'location')]}"
/>
<field
name="location_id"
attrs="{'invisible':[('type','=', 'product')]}"
/>
</group>
</group>
<footer>
<button
name="action_duplicate_buffer"
string="Duplicate"
type="object"
class="btn-primary"
/>
</footer>
</form>
</field>
</record>
<record id="action_server_ddmrp_duplicate_buffer" model="ir.actions.act_window">
<field name="name">Duplicate Buffer</field>
<field name="res_model">ddmrp.duplicate.buffer</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="binding_model_id" ref="ddmrp.model_stock_buffer" />
<field name="view_id" ref="ddmrp.ddmrp_duplicate_buffer_wizard" />
</record>
</odoo>

View file

@ -0,0 +1,15 @@
# Copyright 2018-20 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import models
class DdmrpRun(models.TransientModel):
_name = "ddmrp.run"
_description = "DDMRP Manual Run Wizard"
def run_cron_ddmrp_adu(self):
self.env["stock.buffer"].cron_ddmrp_adu(True)
def run_cron_ddmrp(self):
self.env["stock.buffer"].cron_ddmrp(True)

View file

@ -0,0 +1,41 @@
<?xml version="1.0" ?>
<odoo>
<record id="view_run_ddmrp_wizard" model="ir.ui.view">
<field name="name">ddmrp.run.form</field>
<field name="model">ddmrp.run</field>
<field name="arch" type="xml">
<form string="Run DDMRP">
<footer>
<button
name="run_cron_ddmrp_adu"
string="Run ADU calculation"
type="object"
class="oe_highlight"
/>
or
<button
name="run_cron_ddmrp"
string="Run DDMRP Buffer calculation"
type="object"
class="oe_highlight"
/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="action_ddmrp_run" model="ir.actions.act_window">
<field name="name">Run DDMRP</field>
<field name="res_model">ddmrp.run</field>
<field name="type">ir.actions.act_window</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem
id="ddmrp_run_menu"
parent="ddmrp.menu_ddmrp_config"
action="action_ddmrp_run"
sequence="90"
/>
</odoo>

View file

@ -0,0 +1,201 @@
# Copyright 2019-20 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.tools import float_compare
class MakeProcurementBuffer(models.TransientModel):
_name = "make.procurement.buffer"
_description = "Make Procurements from Stock Buffers"
partner_id = fields.Many2one(
comodel_name="res.partner",
string="Vendor",
help="If set, will be used as preferred vendor for purchase routes.",
)
item_ids = fields.One2many(
comodel_name="make.procurement.buffer.item",
inverse_name="wiz_id",
string="Items",
)
@api.model
def _prepare_item(self, buffer, qty_override=None):
qty = (
qty_override if qty_override is not None else buffer.procure_recommended_qty
)
return {
"recommended_qty": buffer.procure_recommended_qty,
"qty": qty,
"uom_id": buffer.procure_uom_id.id or buffer.product_uom.id,
"date_planned": buffer._get_date_planned(),
"buffer_id": buffer.id,
"product_id": buffer.product_id.id,
"warehouse_id": buffer.warehouse_id.id,
"location_id": buffer.location_id.id,
}
@api.model
def default_get(self, fields):
res = super().default_get(fields)
buffer_obj = self.env["stock.buffer"]
buffer_ids = self.env.context["active_ids"] or []
active_model = self.env.context["active_model"]
if not buffer_ids:
return res
assert active_model == "stock.buffer", "Bad context propagation"
items = []
for line in buffer_obj.browse(buffer_ids):
max_order = line.procure_max_qty
qty_to_order = line._procure_qty_to_order()
if max_order and max_order < qty_to_order:
# split the procurement in batches:
while qty_to_order > 0.0:
if qty_to_order > max_order:
qty = max_order
else:
rounding = (
line.procure_uom_id.rounding or line.product_uom.rounding
)
profile = line.buffer_profile_id
limit_to_free_qty = (
line.item_type == "distributed"
and profile.replenish_distributed_limit_to_free_qty
)
if limit_to_free_qty:
if (
float_compare(
qty_to_order,
line.procure_min_qty,
precision_rounding=rounding,
)
< 0
):
# not enough in stock to have a batch
# respecting the min qty!
break
# not more that what we have in stock!
qty = qty_to_order
else:
# FIXME it will apply a second time the unit conversion
qty = line._adjust_procure_qty(qty_to_order)
items.append([0, 0, self._prepare_item(line, qty)])
qty_to_order -= qty
else:
items.append([0, 0, self._prepare_item(line, qty_to_order)])
res["item_ids"] = items
return res
def _get_procurement_location(self, item):
return item.buffer_id.location_id
def _get_procurement_name(self, item):
return item.buffer_id.name
def _get_procurement_origin(self, item):
return item.buffer_id.name
def make_procurement(self):
self.ensure_one()
errors = []
pg_obj = self.env["procurement.group"]
procurements = []
for item in self.item_ids:
# As procurement is processed with SUPERUSER_ID (see _run_buy and
# _run_manufacture), ensure the current user has write access on
# the buffer to make a procurement. Otherwise, any user can request
# a procurement on any buffer
item.buffer_id.check_access_rights("write")
item.buffer_id.check_access_rule("write")
if item.qty <= 0.0:
raise ValidationError(_("Quantity must be positive."))
if not item.buffer_id:
raise ValidationError(_("No stock buffer found."))
values = item._prepare_values_make_procurement()
proc_location = self._get_procurement_location(item)
proc_name = self._get_procurement_name(item)
proc_origin = self._get_procurement_origin(item)
procurements.append(
pg_obj.Procurement(
item.buffer_id.product_id,
item.qty,
item.uom_id,
proc_location,
proc_name,
proc_origin,
item.buffer_id.company_id,
values,
)
)
# Run procurements
try:
pg_obj.run(procurements)
except UserError as error:
errors.append(error.name)
if errors:
raise UserError("\n".join(errors))
# Update buffer computed fields:
buffers = self.mapped("item_ids.buffer_id")
buffers.invalidate_recordset()
self.env.add_to_compute(buffers._fields["procure_recommended_qty"], buffers)
buffers.flush_recordset()
return {"type": "ir.actions.act_window_close"}
class MakeProcurementBufferItem(models.TransientModel):
_name = "make.procurement.buffer.item"
_description = "Make Procurements from Stock Buffer Item"
wiz_id = fields.Many2one(
comodel_name="make.procurement.buffer",
string="Wizard",
required=True,
ondelete="cascade",
readonly=True,
)
recommended_qty = fields.Float(readonly=True)
qty = fields.Float()
uom_id = fields.Many2one(
string="Unit of Measure",
comodel_name="uom.uom",
)
date_planned = fields.Datetime(string="Planned Date", required=False)
buffer_id = fields.Many2one(
string="Stock Buffer",
comodel_name="stock.buffer",
readonly=False,
)
product_id = fields.Many2one(
string="Product",
comodel_name="product.product",
readonly=True,
)
warehouse_id = fields.Many2one(
string="Warehouse",
comodel_name="stock.warehouse",
readonly=True,
)
location_id = fields.Many2one(
string="Location",
comodel_name="stock.location",
readonly=True,
)
@api.onchange("uom_id")
def onchange_uom_id(self):
for rec in self:
rec.qty = rec.buffer_id.product_uom._compute_quantity(
rec.buffer_id.procure_recommended_qty, rec.uom_id
)
def _prepare_values_make_procurement(self):
values = self.buffer_id._prepare_procurement_values(self.qty)
values.update(
{"date_planned": self.date_planned, "supplier_id": self.wiz_id.partner_id}
)
return values

View file

@ -0,0 +1,84 @@
<?xml version="1.0" ?>
<odoo>
<!-- Make Procurement with security access right -->
<record id="view_make_procurement_buffer_wizard" model="ir.ui.view">
<field name="name">Request Procurement</field>
<field name="model">make.procurement.buffer</field>
<field name="arch" type="xml">
<form string="Procurement Request">
<p class="oe_gray">
Use this assistant to generate a procurement request for this
stock buffer. According to the product configuration,
this may trigger a draft purchase order, a manufacturing
order or a transfer picking.
</p>
<p class="oe_gray">
By default, the qty is equal to the recommended quantity.
For distributed buffers, when the option on the profile is active,
the quantity is limited to the free quantity.
</p>
<group>
<field
name="partner_id"
readonly="1"
attrs="{'invisible': [('partner_id','=',False)]}"
/>
</group>
<field name="item_ids" nolabel="1">
<tree nocreate="1" editable="top">
<field name="buffer_id" invisible="True" />
<field
name="warehouse_id"
invisible="1"
groups="!stock.group_stock_multi_locations"
/>
<field
name="location_id"
invisible="1"
groups="!stock.group_stock_multi_locations"
/>
<field
name="warehouse_id"
groups="stock.group_stock_multi_locations"
/>
<field
name="location_id"
groups="stock.group_stock_multi_locations"
/>
<field name="product_id" />
<field name="recommended_qty" />
<field
name="qty"
readonly="1"
force_save="1"
groups="!ddmrp.group_change_buffer_procure_qty"
/>
<field
name="qty"
groups="ddmrp.group_change_buffer_procure_qty"
/>
<field name="uom_id" groups="uom.group_uom" />
<field name="date_planned" />
</tree>
</field>
<footer>
<button
string="Execute"
name="make_procurement"
type="object"
class="btn-primary"
/>
<button string="Cancel" class="btn-default" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="act_make_procurement_from_buffer" model="ir.actions.act_window">
<field name="name">Request Procurement</field>
<field name="res_model">make.procurement.buffer</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="binding_model_id" ref="ddmrp.model_stock_buffer" />
<field name="view_id" ref="ddmrp.view_make_procurement_buffer_wizard" />
</record>
</odoo>

View file

@ -0,0 +1,45 @@
# Copyright 2023 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import api, fields, models
class MrpBomChangeLocation(models.TransientModel):
_name = "mrp.bom.change.location"
_description = "MRP Bom Change Location"
@api.model
def default_get(self, fields_list):
res = super().default_get(fields_list)
bom_obj = self.env["mrp.bom"]
bom_id = self.env.context.get("active_id", False)
active_model = self.env.context["active_model"]
if not bom_id:
return
assert active_model == "mrp.bom", "Bad context propagation"
bom = bom_obj.browse(bom_id)
res.update(
{
"location_id": bom.context_location_id.id,
}
)
return res
location_id = fields.Many2one(
comodel_name="stock.location",
)
def action_change_location(self):
bom_id = self.env.context.get("active_id", False)
return {
"type": "ir.actions.act_window",
"res_model": "mrp.bom",
"view_mode": "form",
"res_id": bom_id,
"context": {
"location_id": self.location_id.id,
"warehouse_id": self.location_id.warehouse_id.id,
},
}

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="mrp_bom_change_location_wizard" model="ir.ui.view">
<field name="name">Mrp Bom Change Location Wizard</field>
<field name="model">mrp.bom.change.location</field>
<field name="arch" type="xml">
<form string="Change Bom Location">
<p class="oe_grey">
Change the inventory location that it is considered to compute when a product is buffered or not.
This can potentially change the Decoupled Lead Time (DLT) value.
This change is only informative and won't affect any process or buffer size or recommendations.
</p>
<group>
<group>
<field name="location_id" />
</group>
</group>
<footer>
<button
name="action_change_location"
string="Change"
type="object"
class="btn-primary"
/>
</footer>
</form>
</field>
</record>
</odoo>

View file

@ -0,0 +1,22 @@
# Copyright 2018 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import api, models
class MultiLevelMrp(models.TransientModel):
_inherit = "mrp.multi.level"
@api.model
def _exclude_from_mrp(self, product, mrp_area):
"""Exclude from MRP scheduler products that are buffered."""
res = super()._exclude_from_mrp(product, mrp_area)
ddmrp = self.env["stock.buffer"].search(
[
("location_id", "=", mrp_area.location_id.id), # child of?
("product_id", "=", product.id),
]
)
if ddmrp and not self.env.context.get("mrp_explosion"):
return True
return res

View file

@ -0,0 +1,43 @@
# Copyright 2020 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"
module_ddmrp_history = fields.Boolean(
string="Store historical data from stock buffers",
)
module_ddmrp_adjustment = fields.Boolean(
string="apply adjustments to dynamically alter buffers",
)
module_ddmrp_coverage_days = fields.Boolean(
string="Shows the current on-hand for stock buffers expressed "
"as coverage days.",
)
module_ddmrp_packaging = fields.Boolean(
string="Use packagings on stock buffers.",
)
module_stock_buffer_capacity_limit = fields.Boolean(
string="Storage Capacity Limits",
)
module_ddmrp_warning = fields.Boolean(
string="Configuration Warnings in Stock Buffers",
)
module_ddmrp_chatter = fields.Boolean(
string="Chatter in Stock Buffers",
)
module_ddmrp_purchase_hide_onhand_status = fields.Boolean(
string="Hide Purchase On-Hand Status",
)
ddmrp_auto_update_nfp = fields.Boolean(
related="company_id.ddmrp_auto_update_nfp", readonly=False
)
ddmrp_adu_calc_include_scrap = fields.Boolean(
related="company_id.ddmrp_adu_calc_include_scrap", readonly=False
)
ddmrp_qty_multiple_tolerance = fields.Float(
related="company_id.ddmrp_qty_multiple_tolerance", readonly=False
)

View file

@ -0,0 +1,189 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright 2020 ForgeFlow S.L. (http://www.forgeflow.com)
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
-->
<odoo>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.ddmrp</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base.res_config_settings_view_form" />
<field name="arch" type="xml">
<xpath expr="//div[hasclass('settings')]" position="inside">
<div
class="app_settings_block"
data-string="DDMRP"
string="DDMRP"
data-key="ddmrp"
groups="ddmrp.group_ddmrp_manager"
>
<h2>Extensions</h2>
<div class="row mt16 o_settings_container" id="ddmrp_extension">
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_ddmrp_history" />
</div>
<div class="o_setting_right_pane">
<label
string="DDMRP History"
for="module_ddmrp_history"
/>
<div class="text-muted">
Store historical data from stock buffers.
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_ddmrp_adjustment" />
</div>
<div class="o_setting_right_pane">
<label
string="DDMRP Adjustments"
for="module_ddmrp_adjustment"
/>
<div class="text-muted">
Apply adjustments to dynamically alter stock buffers to respond to planned or anticipated events.
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_ddmrp_coverage_days" />
</div>
<div class="o_setting_right_pane">
<label
string="Show Coverage Days in Stock Buffers"
for="module_ddmrp_coverage_days"
/>
<div class="text-muted">
Shows the current on-hand for stock buffers expressed as coverage days.
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_ddmrp_packaging" />
</div>
<div class="o_setting_right_pane">
<label
string="Stock Buffers &amp; Packagings"
for="module_ddmrp_packaging"
/>
<div class="text-muted">
Use packagings on stock buffers.
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_stock_buffer_capacity_limit" />
</div>
<div class="o_setting_right_pane">
<label for="module_stock_buffer_capacity_limit" />
<div class="text-muted">
Set an storage capacity limit on stock buffers.
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_ddmrp_chatter" />
</div>
<div class="o_setting_right_pane">
<label for="module_ddmrp_chatter" />
<div class="text-muted">
Adds chatter and activities to stock buffers.
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field
name="module_ddmrp_purchase_hide_onhand_status"
/>
</div>
<div class="o_setting_right_pane">
<label for="module_ddmrp_purchase_hide_onhand_status" />
<div class="text-muted">
Hides on-hand status from purchase order line.
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_ddmrp_warning" />
</div>
<div class="o_setting_right_pane">
<label for="module_ddmrp_warning" />
<div class="text-muted">
Get configuration warnings for stock buffers.
</div>
</div>
</div>
</div>
<h2>Company Settings</h2>
<div class="row mt16 o_settings_container" id="ddmrp_settings">
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="ddmrp_auto_update_nfp" />
</div>
<div class="o_setting_right_pane">
<label
string="Auto update Net Flow Position"
for="ddmrp_auto_update_nfp"
/>
<span
class="fa fa-lg fa-building-o"
title="Values set here are company-specific."
role="img"
aria-label="Values set here are company-specific."
groups="base.group_multi_company"
/>
<div class="text-muted">
Transfer status changes can trigger the update of relevant buffer's NFP.
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="ddmrp_adu_calc_include_scrap" />
</div>
<div class="o_setting_right_pane">
<label for="ddmrp_adu_calc_include_scrap" />
</div>
</div>
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane" />
<div class="o_setting_right_pane">
<label for="ddmrp_qty_multiple_tolerance" />
<span
class="fa fa-lg fa-building-o"
title="Values set here are company-specific."
role="img"
aria-label="Values set here are company-specific."
groups="base.group_multi_company"
/>
<div class="text-muted">
Set a tolerance value to apply to quantity multiple in stock buffers.
If the quantity needed is below this tolerance threshold, the recommended
quantity will be reduced a bit instead of adding another bucket.
The value is a percentage of the quantity multiple.
<field name="ddmrp_qty_multiple_tolerance" />%
</div>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
<record id="action_ddmrp_config" model="ir.actions.act_window">
<field name="name">Settings</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.config.settings</field>
<field name="view_mode">form</field>
<field name="target">inline</field>
<field name="context">{'module' : 'ddmrp'}</field>
</record>
</odoo>