addons-cm/stock_picking_batch_collect/models/stock_picking.py
GitHub Copilot 9c81cb986d
[IMP] stock_picking_batch_collect: collected checks on transfers, ordered quantity, delivery slips report
- 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>
2026-10-04 14:34:06 +02:00

87 lines
3.2 KiB
Python

# Copyright 2026 Criptomart
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models
from odoo.exceptions import UserError
class StockPicking(models.Model):
_inherit = "stock.picking"
def _pre_action_done_hook(self):
"""Run collected checks only after Odoo resolves backorders.
This keeps the product summary checkbox informative during the initial
batch validation click, but still enforces it once the user confirms the
actual picking flow that will be processed.
"""
res = super()._pre_action_done_hook()
if res is not True:
return res
self._check_collected_products()
return res
def _get_open_move_lines(self, scope):
"""Move lines still to process; ``processed`` keeps those with quantity."""
lines = self.move_line_ids.filtered(
lambda line: line.move_id.state not in ("cancel", "done")
and line.product_id
)
if scope == "processed":
lines = lines.filtered(lambda line: line.move_id.quantity > 0)
return lines
def _get_not_collected_move_lines(self, scope):
return self._get_open_move_lines(scope).filtered(
lambda line: not line.is_collected
)
def _check_collected_products(self):
for batch in self.batch_id:
batch_pickings = self.filtered(
lambda picking, batch=batch: picking.batch_id == batch
)
if batch_pickings._get_open_move_lines("processed"):
batch._check_all_products_collected(batch_pickings)
loose_pickings = self.filtered(
lambda picking: not picking.batch_id
and picking.picking_type_id.collected_check_required
)
for picking in loose_pickings:
if picking._get_open_move_lines("processed"):
picking._check_picking_collected()
def _check_picking_collected(self):
"""Apply the batch restrictions to a transfer outside a batch.
A single transfer has no product summary: the toggle of its Operations
tab collects every line of the product, so both restrictions check its
move lines, with the widest scope enabled.
"""
self.ensure_one()
company = self.company_id
scopes = set()
if company.batch_detailed_restriction_enabled:
scopes.add(company.batch_detailed_restriction_scope)
if company.batch_summary_restriction_enabled:
scopes.add(company.batch_summary_restriction_scope)
if not scopes:
return
scope = "all" if "all" in scopes else "processed"
not_collected = self._get_not_collected_move_lines(scope)
if not not_collected:
return
product_names = ", ".join(
sorted(set(not_collected.mapped("product_id.display_name")))
)
raise UserError(
self.env._(
"You must mark the operations of %(picking)s as collected "
"before validating it.\nPending products: %(products)s",
picking=self.name,
products=product_names,
)
)