- The collected checks also apply to transfers outside a batch, opt-in per
operation type ("Require Collected Lines"), with the batch restrictions of
the settings. A per-product toggle in the transfer's Operations tab collects
all its lines.
- Read-only ordered quantity from the sale line on every batch and transfer
view, since the weighed demand overwrites it. Adds a dependency on
sale_stock; a migration fills it in the summary of open batches.
- "Delivery Slips" report on the batch: all its delivery slips in one PDF.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
69 lines
2.1 KiB
Python
69 lines
2.1 KiB
Python
# Copyright 2026 Criptomart
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
|
|
from odoo import api
|
|
from odoo import fields
|
|
from odoo import models
|
|
|
|
|
|
class StockMoveLine(models.Model):
|
|
_inherit = "stock.move.line"
|
|
_order = "product_categ_id, product_id, picking_partner_id, id"
|
|
|
|
product_categ_id = fields.Many2one(
|
|
comodel_name="product.category",
|
|
string="Product Category (Batch)",
|
|
related="product_id.categ_id",
|
|
store=True,
|
|
readonly=True,
|
|
)
|
|
|
|
product_default_code = fields.Char(
|
|
string="Product Code",
|
|
related="product_id.default_code",
|
|
readonly=True,
|
|
)
|
|
|
|
picking_partner_id = fields.Many2one(
|
|
related="picking_id.partner_id",
|
|
store=True,
|
|
readonly=True,
|
|
)
|
|
|
|
is_collected = fields.Boolean(
|
|
string="Collected",
|
|
default=False,
|
|
copy=False,
|
|
)
|
|
|
|
sale_ordered_qty = fields.Float(
|
|
string="Ordered Quantity",
|
|
digits="Product Unit of Measure",
|
|
compute="_compute_sale_ordered_qty",
|
|
)
|
|
|
|
@api.depends("move_id.sale_ordered_qty", "move_id.product_uom", "product_uom_id")
|
|
def _compute_sale_ordered_qty(self):
|
|
for line in self:
|
|
move = line.move_id
|
|
if move.sale_ordered_qty and line.product_uom_id:
|
|
line.sale_ordered_qty = move.product_uom._compute_quantity(
|
|
move.sale_ordered_qty, line.product_uom_id, round=False
|
|
)
|
|
else:
|
|
line.sale_ordered_qty = 0.0
|
|
|
|
def write(self, vals):
|
|
res = super().write(vals)
|
|
if vals.get("is_collected"):
|
|
# Collecting a line is the operator saying the goods are in the
|
|
# basket: its quantity must survive the reservation engine even if
|
|
# it was never retyped. The demand is left alone, only a quantity
|
|
# typed by hand adjusts it.
|
|
lines = self.filtered(
|
|
lambda line: not line.picked and line.state not in ("done", "cancel")
|
|
)
|
|
if lines:
|
|
lines.picked = True
|
|
lines._freeze_manual_quantity(align_demand=False)
|
|
return res
|