[ADD] stock_picking_batch_custom: product summary
This commit is contained in:
parent
9c14e1dc1a
commit
ad8b759643
11 changed files with 348 additions and 10 deletions
159
stock_picking_batch_custom/models/stock_picking_batch.py
Normal file
159
stock_picking_batch_custom/models/stock_picking_batch.py
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
# Copyright 2026 Criptomart
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from odoo import api
|
||||
from odoo import fields
|
||||
from odoo import models
|
||||
|
||||
|
||||
class StockPickingBatch(models.Model):
|
||||
_inherit = "stock.picking.batch"
|
||||
|
||||
summary_line_ids = fields.One2many(
|
||||
comodel_name="stock.picking.batch.summary.line",
|
||||
inverse_name="batch_id",
|
||||
string="Product Summary",
|
||||
compute="_compute_summary_line_ids",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
"move_line_ids.move_id.product_id",
|
||||
"move_line_ids.move_id.product_uom_qty",
|
||||
"move_line_ids.move_id.quantity",
|
||||
"move_line_ids.move_id.product_uom",
|
||||
"move_line_ids.move_id.state",
|
||||
)
|
||||
def _compute_summary_line_ids(self):
|
||||
"""Aggregate move quantities per product and keep collected flag.
|
||||
|
||||
- Demand: move.product_uom_qty converted to product UoM.
|
||||
- Done: move.quantity converted to product UoM.
|
||||
- Pending: demand - done.
|
||||
- Keep is_collected value if product already present.
|
||||
- Skip cancelled moves.
|
||||
"""
|
||||
|
||||
for batch in self:
|
||||
previous_collected = {
|
||||
line.product_id.id: line.is_collected for line in batch.summary_line_ids
|
||||
}
|
||||
aggregates = defaultdict(
|
||||
lambda: {
|
||||
"product_id": False,
|
||||
"product_uom_id": False,
|
||||
"qty_demanded": 0.0,
|
||||
"qty_done": 0.0,
|
||||
}
|
||||
)
|
||||
|
||||
for move in batch.move_ids:
|
||||
if move.state == "cancel" or not move.product_id:
|
||||
continue
|
||||
|
||||
product = move.product_id
|
||||
product_uom = product.uom_id
|
||||
demand = move.product_uom._compute_quantity(
|
||||
move.product_uom_qty, product_uom, rounding_method="HALF-UP"
|
||||
)
|
||||
done = move.product_uom._compute_quantity(
|
||||
move.quantity, product_uom, rounding_method="HALF-UP"
|
||||
)
|
||||
|
||||
entry = aggregates[product.id]
|
||||
entry["product_id"] = product.id
|
||||
entry["product_uom_id"] = product_uom.id
|
||||
entry["qty_demanded"] += demand
|
||||
entry["qty_done"] += done
|
||||
|
||||
commands = []
|
||||
products_in_totals = set()
|
||||
existing_by_product = {
|
||||
line.product_id.id: line for line in batch.summary_line_ids
|
||||
}
|
||||
|
||||
for product_id, values in aggregates.items():
|
||||
products_in_totals.add(product_id)
|
||||
existing_line = existing_by_product.get(product_id)
|
||||
values["is_collected"] = previous_collected.get(product_id, False)
|
||||
if existing_line:
|
||||
commands.append(
|
||||
fields.Command.update(
|
||||
existing_line.id,
|
||||
{
|
||||
"product_uom_id": values["product_uom_id"],
|
||||
"qty_demanded": values["qty_demanded"],
|
||||
"qty_done": values["qty_done"],
|
||||
"is_collected": values["is_collected"],
|
||||
},
|
||||
)
|
||||
)
|
||||
else:
|
||||
commands.append(fields.Command.create(values))
|
||||
|
||||
obsolete_lines = [
|
||||
fields.Command.unlink(line.id)
|
||||
for line in batch.summary_line_ids
|
||||
if line.product_id.id not in products_in_totals
|
||||
]
|
||||
|
||||
batch.summary_line_ids = commands + obsolete_lines
|
||||
|
||||
|
||||
class StockPickingBatchSummaryLine(models.Model):
|
||||
_name = "stock.picking.batch.summary.line"
|
||||
_description = "Batch Product Summary Line"
|
||||
_order = "product_categ_id, product_id"
|
||||
|
||||
batch_id = fields.Many2one(
|
||||
comodel_name="stock.picking.batch",
|
||||
string="Batch",
|
||||
ondelete="cascade",
|
||||
required=True,
|
||||
index=True,
|
||||
)
|
||||
product_id = fields.Many2one(
|
||||
comodel_name="product.product",
|
||||
string="Product",
|
||||
required=True,
|
||||
index=True,
|
||||
)
|
||||
product_categ_id = fields.Many2one(
|
||||
comodel_name="product.category",
|
||||
string="Product Category",
|
||||
related="product_id.categ_id",
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
product_uom_id = fields.Many2one(
|
||||
comodel_name="uom.uom",
|
||||
string="Unit of Measure",
|
||||
required=True,
|
||||
)
|
||||
qty_demanded = fields.Float(
|
||||
string="Demanded Quantity",
|
||||
digits="Product Unit of Measure",
|
||||
required=True,
|
||||
default=0.0,
|
||||
)
|
||||
qty_done = fields.Float(
|
||||
string="Done Quantity",
|
||||
digits="Product Unit of Measure",
|
||||
required=True,
|
||||
default=0.0,
|
||||
)
|
||||
qty_pending = fields.Float(
|
||||
string="Pending Quantity",
|
||||
digits="Product Unit of Measure",
|
||||
compute="_compute_qty_pending",
|
||||
store=True,
|
||||
)
|
||||
is_collected = fields.Boolean(string="Collected", default=False)
|
||||
|
||||
@api.depends("qty_demanded", "qty_done")
|
||||
def _compute_qty_pending(self):
|
||||
for line in self:
|
||||
line.qty_pending = line.qty_demanded - line.qty_done
|
||||
Loading…
Add table
Add a link
Reference in a new issue