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" )