oca-technical/odoo-bringout-oca-ddmrp-ddmrp/ddmrp/wizards/make_procurement_buffer.py
2025-08-29 15:43:03 +02:00

201 lines
7.3 KiB
Python

# 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