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 for 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
AungKoKoLin1997 committed Jun 18, 2024
1 parent 7eecf03 commit 374baf4
Show file tree
Hide file tree
Showing 17 changed files with 439 additions and 60 deletions.
6 changes: 3 additions & 3 deletions quality_control_oca/i18n/ja.po
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ msgstr ""
"Project-Id-Version: Odoo Server 10.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-11-28 03:44+0000\n"
"PO-Revision-Date: 2024-06-06 00:30+0000\n"
"PO-Revision-Date: 2024-06-11 08:57+0000\n"
"Last-Translator: Wang-TKurata <[email protected]>\n"
"Language-Team: Japanese (https://www.transifex.com/oca/teams/23907/ja/)\n"
"Language: ja\n"
Expand All @@ -32,7 +32,7 @@ msgstr "トリガーを定義するための抽象行"
#. module: quality_control_oca
#: model_terms:ir.ui.view,arch_db:quality_control_oca.view_qc_test_set_test_form
msgid "Accept"
msgstr "合格"
msgstr "OK"

#. module: quality_control_oca
#: model:ir.model.fields,field_description:quality_control_oca.field_qc_inspection__message_needaction
Expand Down Expand Up @@ -856,7 +856,7 @@ msgstr "付番"
#. module: quality_control_oca
#: model_terms:ir.ui.view,arch_db:quality_control_oca.qc_inspection_form_view
msgid "Set test"
msgstr "テストに設定"
msgstr "テスト選択"

#. module: quality_control_oca
#: model:ir.model,name:quality_control_oca.model_qc_inspection_set_test
Expand Down
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={"plan": [("readonly", False)], "draft": [("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
12 changes: 9 additions & 3 deletions quality_control_oca/views/qc_inspection_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@
<field name="product_id" />
</group>
<group>
<field name="create_date" string="Create Date" />
<field name="date" />
<field name="date_done" />
<field name="success" />
Expand Down Expand Up @@ -144,11 +143,18 @@
<field name="test" />
<field name="qty" />
<field name="product_id" />
<field name="create_date" string="Create Date" optional="show" />
<field name="date" optional="show" />
<field name="date_done" optional="show" />
<field name="success" />
<field name="state" />
<field
name="state"
widget="badge"
decoration-info="state == 'ready'"
decoration-warning="state == 'waiting'"
decoration-success="state == 'success'"
decoration-danger="state == 'failed'"
decoration-muted="state == 'canceled'"
/>
</tree>
</field>
</record>
Expand Down
20 changes: 20 additions & 0 deletions quality_control_stock_oca/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ It also adds some shortcuts on picking and lots to these inspections.
.. contents::
:local:

Configuration
=============

Configure a QC trigger line in the product, product template, or product category to create the inspection.

* Trigger: Select the trigger that will be used to get the trigger line when the record with the trigger is processed.
* Test: It is a group of questions along with the values that make them valid.
* Responsible: Select the user who will be responsible for the QC inspection.
* Partner: If filled, the test will only be created when the action is done for one of the specified partners. If empty, the test will always be created.
* Timing: Select the timing: 'Before', 'After', or 'Plan Ahead' as necessary.
* Before: An inspection is generated for each related move when a picking with the trigger is confirmed.
* After: An inspection is generated for each related move when a picking with the trigger is completed.
* 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.

Known issues / Roadmap
======================

Expand Down Expand Up @@ -76,6 +91,11 @@ Contributors
* Pedro M. Baeza
* Carlos Roca

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

* Aung Ko Ko Lin
* Yoshi Tashiro

Maintainers
~~~~~~~~~~~

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
71 changes: 71 additions & 0 deletions quality_control_stock_oca/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# 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 functools import lru_cache

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 _get_partner_for_trigger_line(self):
return self.picking_id.partner_id

def trigger_inspection(self, timings, partner=False):
@lru_cache()
def get_qc_trigger(picking_type):
return (
self.env["qc.trigger"]
.sudo()
.search([("picking_type_id", "=", picking_type.id)])
)

self.ensure_one()
# To avoid CacheMiss error from tests of other modules
if "picking_type_id" not in self._cache:
return
inspection_model = self.env["qc.inspection"].sudo()
qc_trigger = get_qc_trigger(self.picking_type_id)
if qc_trigger.partner_selectable:
partner = partner or self._get_partner_for_trigger_line()
else:
partner = 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)

def _action_confirm(self, merge=True, merge_into=False):
res = super()._action_confirm(merge=merge, merge_into=merge_into)
for move in self:
move.trigger_inspection(["before", "plan_ahead"])
return res
57 changes: 32 additions & 25 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,37 @@ def _compute_count_inspections(self):
picking.passed_inspections + picking.failed_inspections
)

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()
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(timings, self.partner_id)

def action_cancel(self):
res = super().action_cancel()

Check warning on line 75 in quality_control_stock_oca/models/stock_picking.py

View check run for this annotation

Codecov / codecov/patch

quality_control_stock_oca/models/stock_picking.py#L75

Added line #L75 was not covered by tests
self.qc_inspections_ids.filtered(lambda x: x.state == "plan").action_cancel()
return res

Check warning on line 77 in quality_control_stock_oca/models/stock_picking.py

View check run for this annotation

Codecov / codecov/patch

quality_control_stock_oca/models/stock_picking.py#L77

Added line #L77 was not covered by tests

def _action_done(self):
res = super()._action_done()
inspection_model = self.env["qc.inspection"].sudo()
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)
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

def _create_backorder(self):
res = super()._create_backorder()
# To re-allocate backorder moves to the new backorder picking
self.qc_inspections_ids._compute_picking()
return res
Loading

0 comments on commit 374baf4

Please sign in to comment.