diff --git a/quality_control_oca/models/qc_inspection.py b/quality_control_oca/models/qc_inspection.py
index 8223085d..ae00c8ca 100644
--- a/quality_control_oca/models/qc_inspection.py
+++ b/quality_control_oca/models/qc_inspection.py
@@ -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(
@@ -80,6 +80,7 @@ def _compute_product_id(self):
)
state = fields.Selection(
[
+ ("plan", "Plan"),
("draft", "Draft"),
("ready", "Ready"),
("waiting", "Waiting supervisor approval"),
@@ -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
@@ -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
@@ -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,
@@ -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"
diff --git a/quality_control_oca/models/qc_trigger_line.py b/quality_control_oca/models/qc_trigger_line.py
index 0c0b6e95..6bc54d85 100644
--- a/quality_control_oca/models/qc_trigger_line.py
+++ b/quality_control_oca/models/qc_trigger_line.py
@@ -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.
diff --git a/quality_control_oca/models/qc_trigger_product_category_line.py b/quality_control_oca/models/qc_trigger_product_category_line.py
index 8a814afa..cfc7edf1 100644
--- a/quality_control_oca/models/qc_trigger_product_category_line.py
+++ b/quality_control_oca/models/qc_trigger_product_category_line.py
@@ -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
diff --git a/quality_control_oca/models/qc_trigger_product_line.py b/quality_control_oca/models/qc_trigger_product_line.py
index 85040185..39357020 100644
--- a/quality_control_oca/models/qc_trigger_product_line.py
+++ b/quality_control_oca/models/qc_trigger_product_line.py
@@ -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
diff --git a/quality_control_oca/models/qc_trigger_product_template_line.py b/quality_control_oca/models/qc_trigger_product_template_line.py
index a5b48cf1..7fa739eb 100644
--- a/quality_control_oca/models/qc_trigger_product_template_line.py
+++ b/quality_control_oca/models/qc_trigger_product_template_line.py
@@ -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
diff --git a/quality_control_oca/tests/test_quality_control.py b/quality_control_oca/tests/test_quality_control.py
index 2dc20f89..c8fb8c1a 100644
--- a/quality_control_oca/tests/test_quality_control.py
+++ b/quality_control_oca/tests/test_quality_control.py
@@ -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)
diff --git a/quality_control_oca/views/product_template_view.xml b/quality_control_oca/views/product_template_view.xml
index e64886ff..16516d57 100644
--- a/quality_control_oca/views/product_template_view.xml
+++ b/quality_control_oca/views/product_template_view.xml
@@ -23,6 +23,7 @@
+
diff --git a/quality_control_stock_oca/models/__init__.py b/quality_control_stock_oca/models/__init__.py
index 634e9396..41ad15ab 100644
--- a/quality_control_stock_oca/models/__init__.py
+++ b/quality_control_stock_oca/models/__init__.py
@@ -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
diff --git a/quality_control_stock_oca/models/stock_move.py b/quality_control_stock_oca/models/stock_move.py
new file mode 100644
index 00000000..8b30fe28
--- /dev/null
+++ b/quality_control_stock_oca/models/stock_move.py
@@ -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)
diff --git a/quality_control_stock_oca/models/stock_picking.py b/quality_control_stock_oca/models/stock_picking.py
index e99a0d8d..606677be 100644
--- a/quality_control_stock_oca/models/stock_picking.py
+++ b/quality_control_stock_oca/models/stock_picking.py
@@ -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"
@@ -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
diff --git a/quality_control_stock_oca/readme/CONTRIBUTORS.rst b/quality_control_stock_oca/readme/CONTRIBUTORS.rst
index 42989acc..1670310d 100644
--- a/quality_control_stock_oca/readme/CONTRIBUTORS.rst
+++ b/quality_control_stock_oca/readme/CONTRIBUTORS.rst
@@ -7,3 +7,8 @@
* Pedro M. Baeza
* Carlos Roca
+
+* `Quartile `_:
+
+ * Aung Ko Ko Lin
+ * Yoshi Tashiro
diff --git a/quality_control_stock_oca/tests/test_quality_control_stock.py b/quality_control_stock_oca/tests/test_quality_control_stock.py
index f790f546..7151771f 100644
--- a/quality_control_stock_oca/tests/test_quality_control_stock.py
+++ b/quality_control_stock_oca/tests/test_quality_control_stock.py
@@ -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"
)
@@ -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"
)
@@ -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"
)
@@ -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"
)
@@ -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"
)
@@ -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"
)
@@ -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"
)
@@ -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"
)
@@ -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"
)
@@ -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"
)