Skip to content

Commit

Permalink
[IMP] quality_control_stock_oca: Add option to generate inspections f…
Browse files Browse the repository at this point in the history
…or confirmed moves

This commit adds the timing field in the QC trigger line to enable
following scenarios:

- When timing is 'Before', an inspection is generated wfor each related
  move when a picking with the trigger is confirmed.
- When timing is 'Plan Ahead', a 'Plan' inspection is generated for
  each related move when a picking with the trigger is confirmed. A
  plan inspection is just a plan, and cannot be updated except for the
  date. A plan inspection gets converted into an executable inspection
  once the picking is done.
  • Loading branch information
yostashiro committed Jun 10, 2024
1 parent 8f2367e commit c1f0c4c
Show file tree
Hide file tree
Showing 12 changed files with 136 additions and 33 deletions.
15 changes: 12 additions & 3 deletions quality_control_oca/models/qc_inspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def _compute_product_id(self):
readonly=True,
copy=False,
default=fields.Datetime.now,
states={"draft": [("readonly", False)]},
states={"draft": [("readonly", False)], "plan": [("readonly", False)]},
)
date_done = fields.Datetime("Completion Date", readonly=True)
object_id = fields.Reference(
Expand Down Expand Up @@ -80,6 +80,7 @@ def _compute_product_id(self):
)
state = fields.Selection(
[
("plan", "Plan"),
("draft", "Draft"),
("ready", "Ready"),
("waiting", "Waiting supervisor approval"),
Expand Down Expand Up @@ -196,7 +197,7 @@ def set_test(self, trigger_line, force_fill=False):
trigger_line.test, force_fill=force_fill
)

def _make_inspection(self, object_ref, trigger_line):
def _make_inspection(self, object_ref, trigger_line, date=None):
"""Overridable hook method for creating inspection from test.
:param object_ref: Object instance
:param trigger_line: Trigger line instance
Expand All @@ -205,6 +206,8 @@ def _make_inspection(self, object_ref, trigger_line):
inspection = self.create(
self._prepare_inspection_header(object_ref, trigger_line)
)
if date:
inspection.date = date
inspection.set_test(trigger_line)
return inspection

Expand All @@ -218,7 +221,7 @@ def _prepare_inspection_header(self, object_ref, trigger_line):
"object_id": object_ref
and "{},{}".format(object_ref._name, object_ref.id)
or False,
"state": "ready",
"state": trigger_line.timing == "plan_ahead" and "plan" or "ready",
"test": trigger_line.test.id,
"user": trigger_line.user.id,
"auto_generated": True,
Expand Down Expand Up @@ -257,6 +260,12 @@ def _prepare_inspection_line(self, test, line, fill=None):
data["quantitative_value"] = (line.min_value + line.max_value) * 0.5
return data

def _get_existing_inspections(self, records):
reference_vals = []
for rec in records:
reference_vals.append(",".join([rec._name, str(rec.id)]))
return self.sudo().search([("object_id", "in", reference_vals)])


class QcInspectionLine(models.Model):
_name = "qc.inspection.line"
Expand Down
18 changes: 17 additions & 1 deletion quality_control_oca/models/qc_trigger_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,24 @@ class QcTriggerLine(models.AbstractModel):
" created.",
domain="[('parent_id', '=', False)]",
)
timing = fields.Selection(
selection=[
("before", "Before"),
("after", "After"),
("plan_ahead", "Plan Ahead"),
],
default="after",
help="* Before: An executable inspection is generated before the record "
"related to the trigger is completed (e.g. when picking is confirmed).\n"
"* After: An executable inspection is generated when the record related to the "
"trigger is completed (e.g. when picking is done).\n"
"* Plan Ahead: A non-executable inspection is generated before the record "
"related to the trigger is completed (e.g. when picking is confirmed), and the "
"inspection becomes executable when the record related to the trigger is "
"completed (e.g. when picking is done).",
)

def get_trigger_line_for_product(self, trigger, product, partner=False):
def get_trigger_line_for_product(self, trigger, timings, product, partner=False):
"""Overridable method for getting trigger_line associated to a product.
Each inherited model will complete this module to make the search by
product, template or category.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ class QcTriggerProductCategoryLine(models.Model):

product_category = fields.Many2one(comodel_name="product.category")

def get_trigger_line_for_product(self, trigger, product, partner=False):
def get_trigger_line_for_product(self, trigger, timings, product, partner=False):
trigger_lines = super().get_trigger_line_for_product(
trigger, product, partner=partner
trigger, timings, product, partner=partner
)
category = product.categ_id
while category:
for trigger_line in category.qc_triggers.filtered(
lambda r: r.trigger == trigger
and r.timing in timings
and (
not r.partners
or not partner
Expand Down
5 changes: 3 additions & 2 deletions quality_control_oca/models/qc_trigger_product_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ class QcTriggerProductLine(models.Model):

product = fields.Many2one(comodel_name="product.product")

def get_trigger_line_for_product(self, trigger, product, partner=False):
def get_trigger_line_for_product(self, trigger, timings, product, partner=False):
trigger_lines = super().get_trigger_line_for_product(
trigger, product, partner=partner
trigger, timings, product, partner=partner
)
for trigger_line in product.qc_triggers.filtered(
lambda r: r.trigger == trigger
and r.timing in timings
and (
not r.partners
or not partner
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ class QcTriggerProductTemplateLine(models.Model):

product_template = fields.Many2one(comodel_name="product.template")

def get_trigger_line_for_product(self, trigger, product, partner=False):
def get_trigger_line_for_product(self, trigger, timings, product, partner=False):
trigger_lines = super().get_trigger_line_for_product(
trigger, product, partner=partner
trigger, timings, product, partner=partner
)
for trigger_line in product.product_tmpl_id.qc_triggers.filtered(
lambda r: r.trigger == trigger
and r.timing in timings
and (
not r.partners
or not partner
Expand Down
2 changes: 1 addition & 1 deletion quality_control_oca/tests/test_quality_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ def test_get_qc_trigger_product(self):
]:
trigger_lines = trigger_lines.union(
self.env[model].get_trigger_line_for_product(
self.qc_trigger, self.product
self.qc_trigger, ["after"], self.product
)
)
self.assertEqual(len(trigger_lines), 3)
Expand Down
1 change: 1 addition & 0 deletions quality_control_oca/views/product_template_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<field name="test" />
<field name="user" />
<field name="partners" widget="many2many_tags" />
<field name="timing" />
</tree>
</field>
</group>
Expand Down
1 change: 1 addition & 0 deletions quality_control_stock_oca/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from . import qc_trigger
from . import qc_inspection
from . import stock_move
from . import stock_picking_type
from . import stock_picking
from . import stock_production_lot
45 changes: 45 additions & 0 deletions quality_control_stock_oca/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Copyright 2014 Serv. Tec. Avanzados - Pedro M. Baeza
# Copyright 2018 Simone Rubino - Agile Business Group
# Copyright 2019 Andrii Skrypka
# Copyright 2024 Quartile
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import models

from odoo.addons.quality_control_oca.models.qc_trigger_line import _filter_trigger_lines


class StockMove(models.Model):
_inherit = "stock.move"

def write(self, vals):
if "date" in vals:
existing_inspections = self.env["qc.inspection"]._get_existing_inspections(
self
)
existing_inspections.write({"date": vals.get("date")})
return super().write(vals)

def trigger_inspection(self, qc_trigger, timings, partner=False):
self.ensure_one()
inspection_model = self.env["qc.inspection"].sudo()
partner = partner if qc_trigger.partner_selectable else False
trigger_lines = set()
for model in [
"qc.trigger.product_category_line",
"qc.trigger.product_template_line",
"qc.trigger.product_line",
]:
trigger_lines = trigger_lines.union(
self.env[model]
.sudo()
.get_trigger_line_for_product(
qc_trigger, timings, self.product_id.sudo(), partner=partner
)
)
for trigger_line in _filter_trigger_lines(trigger_lines):
date = False
if trigger_line.timing in ["before", "plan_ahead"]:
# To pass scheduled date to the generated inspection
date = self.date
inspection_model._make_inspection(self, trigger_line, date=date)
56 changes: 34 additions & 22 deletions quality_control_stock_oca/models/stock_picking.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
# Copyright 2014 Serv. Tec. Avanzados - Pedro M. Baeza
# Copyright 2018 Simone Rubino - Agile Business Group
# Copyright 2019 Andrii Skrypka
# Copyright 2024 Quartile
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import api, fields, models

from odoo.addons.quality_control_oca.models.qc_trigger_line import _filter_trigger_lines


class StockPicking(models.Model):
_inherit = "stock.picking"
Expand Down Expand Up @@ -56,29 +55,42 @@ def _compute_count_inspections(self):
picking.passed_inspections + picking.failed_inspections
)

def _action_done(self):
res = super()._action_done()
inspection_model = self.env["qc.inspection"].sudo()
def trigger_inspections(self, timings):
"""Triggers the creation of or an update on inspections for attached stock moves
:param: timings: list of timings among 'before', 'after' and 'plan_ahead'
"""
self.ensure_one()
qc_trigger = (
self.env["qc.trigger"]
.sudo()
.search([("picking_type_id", "=", self.picking_type_id.id)])
)
for operation in self.move_ids:
trigger_lines = set()
for model in [
"qc.trigger.product_category_line",
"qc.trigger.product_template_line",
"qc.trigger.product_line",
]:
partner = self.partner_id if qc_trigger.partner_selectable else False
trigger_lines = trigger_lines.union(
self.env[model]
.sudo()
.get_trigger_line_for_product(
qc_trigger, operation.product_id.sudo(), partner=partner
)
)
for trigger_line in _filter_trigger_lines(trigger_lines):
inspection_model._make_inspection(operation, trigger_line)
moves_with_inspections = self.env["stock.move"]
existing_inspections = self.env["qc.inspection"]._get_existing_inspections(
self.move_ids
)
for inspection in existing_inspections:
inspection.onchange_object_id()
moves_with_inspections += inspection.object_id
for operation in self.move_ids - moves_with_inspections:
operation.trigger_inspection(qc_trigger, timings, self.partner_id)

def action_confirm(self):
res = super().action_confirm()
for picking in self:
picking.trigger_inspections(["before", "plan_ahead"])
return res

def action_cancel(self):
res = super().action_cancel()
self.qc_inspections_ids.filtered(lambda x: x.state == "plan").action_cancel()
return res

def _action_done(self):
res = super()._action_done()
plan_inspections = self.qc_inspections_ids.filtered(lambda x: x.state == "plan")
plan_inspections.write({"state": "ready", "date": fields.Datetime.now()})
for picking in self:
picking.trigger_inspections(["after"])
return res
5 changes: 5 additions & 0 deletions quality_control_stock_oca/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@

* Pedro M. Baeza
* Carlos Roca

* `Quartile <https://www.quartile.co>`_:

* Aung Ko Ko Lin
* Yoshi Tashiro
11 changes: 11 additions & 0 deletions quality_control_stock_oca/tests/test_quality_control_stock.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ def test_inspection_create_for_product(self):
(0, 0, {"trigger": self.trigger.id, "test": self.test.id})
]
self.picking1._action_done()
# Just so _compute_count_inspections() is triggered
self.picking1.qc_inspections_ids
self.assertEqual(
self.picking1.created_inspections, 1, "Only one inspection must be created"
)
Expand All @@ -85,6 +87,7 @@ def test_inspection_create_for_template(self):
(0, 0, {"trigger": self.trigger.id, "test": self.test.id})
]
self.picking1._action_done()
self.picking1.qc_inspections_ids
self.assertEqual(
self.picking1.created_inspections, 1, "Only one inspection must be created"
)
Expand All @@ -100,6 +103,7 @@ def test_inspection_create_for_category(self):
(0, 0, {"trigger": self.trigger.id, "test": self.test.id})
]
self.picking1._action_done()
self.picking1.qc_inspections_ids
self.assertEqual(
self.picking1.created_inspections, 1, "Only one inspection must be created"
)
Expand All @@ -123,6 +127,7 @@ def test_inspection_create_for_product_partner(self):
)
]
self.picking1._action_done()
self.picking1.qc_inspections_ids
self.assertEqual(
self.picking1.created_inspections, 1, "Only one inspection must be created"
)
Expand All @@ -146,6 +151,7 @@ def test_inspection_create_for_template_partner(self):
)
]
self.picking1._action_done()
self.picking1.qc_inspections_ids
self.assertEqual(
self.picking1.created_inspections, 1, "Only one inspection must be created"
)
Expand All @@ -169,6 +175,7 @@ def test_inspection_create_for_category_partner(self):
)
]
self.picking1._action_done()
self.picking1.qc_inspections_ids
self.assertEqual(
self.picking1.created_inspections, 1, "Only one inspection must be created"
)
Expand All @@ -192,6 +199,7 @@ def test_inspection_create_for_product_wrong_partner(self):
)
]
self.picking1._action_done()
self.picking1.qc_inspections_ids
self.assertEqual(
self.picking1.created_inspections, 0, "No inspection must be created"
)
Expand All @@ -210,6 +218,7 @@ def test_inspection_create_for_template_wrong_partner(self):
)
]
self.picking1._action_done()
self.picking1.qc_inspections_ids
self.assertEqual(
self.picking1.created_inspections, 0, "No inspection must be created"
)
Expand All @@ -228,6 +237,7 @@ def test_inspection_create_for_category_wrong_partner(self):
)
]
self.picking1._action_done()
self.picking1.qc_inspections_ids
self.assertEqual(
self.picking1.created_inspections, 0, "No inspection must be created"
)
Expand All @@ -241,6 +251,7 @@ def test_inspection_create_only_one(self):
(0, 0, {"trigger": self.trigger.id, "test": self.test.id})
]
self.picking1._action_done()
self.picking1.qc_inspections_ids
self.assertEqual(
self.picking1.created_inspections, 1, "Only one inspection must be created"
)
Expand Down

0 comments on commit c1f0c4c

Please sign in to comment.