Skip to content

Commit

Permalink
[IMP] quality_control_oca, quality_control_stock_oca
Browse files Browse the repository at this point in the history
This commit adds date_done field, update views and filters and 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 26, 2024
1 parent 226e59e commit 471e306
Show file tree
Hide file tree
Showing 17 changed files with 371 additions and 55 deletions.
27 changes: 24 additions & 3 deletions quality_control_oca/models/qc_inspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
# Copyright 2017 Simone Rubino - Agile Business Group
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from datetime import datetime

from odoo import _, api, exceptions, fields, models
from odoo.tools import formatLang

Expand Down Expand Up @@ -43,12 +45,14 @@ def _compute_product_id(self):
copy=False,
)
date = fields.Datetime(
string="Plan Date",
required=True,
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(
string="Reference",
selection="object_selection_values",
Expand Down Expand Up @@ -76,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 @@ -119,6 +124,14 @@ def create(self, val_list):
vals["name"] = self.env["ir.sequence"].next_by_code("qc.inspection")
return super().create(vals)

def write(self, vals):
if "state" in vals:
if vals["state"] in ["success", "failed"]:
vals["date_done"] = datetime.now()
elif vals["state"] == "draft":
vals["date_done"] = False
return super().write(vals)

def unlink(self):
for inspection in self:
if inspection.auto_generated:
Expand Down Expand Up @@ -184,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 @@ -193,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 @@ -206,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 @@ -245,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
7 changes: 6 additions & 1 deletion quality_control_oca/tests/test_quality_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ def test_inspection_correct(self):
self.assertEqual(self.inspection1.state, "success")
self.inspection1.action_approve()
self.assertEqual(self.inspection1.state, "success")
self.assertTrue(bool(self.inspection1.date_done))
self.inspection1.action_cancel()
self.inspection1.action_draft()
self.assertFalse(self.inspection1.date_done)

def test_inspection_incorrect(self):
for line in self.inspection1.inspection_lines:
Expand All @@ -86,6 +90,7 @@ def test_inspection_incorrect(self):
self.assertEqual(self.inspection1.state, "waiting")
self.inspection1.action_approve()
self.assertEqual(self.inspection1.state, "failed")
self.assertTrue(bool(self.inspection1.date_done))

def test_actions_errors(self):
inspection2 = self.inspection1.copy()
Expand Down Expand Up @@ -166,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
28 changes: 27 additions & 1 deletion quality_control_oca/views/qc_inspection_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
</group>
<group>
<field name="date" />
<field name="date_done" />
<field name="success" />
<field name="auto_generated" />
</group>
Expand Down Expand Up @@ -142,8 +143,18 @@
<field name="test" />
<field name="qty" />
<field name="product_id" />
<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 Expand Up @@ -171,6 +182,9 @@
domain="[('success', '=', False)]"
/>
<newline />
<separator />
<filter name="plan_date" string="Plan Date" date="date" />
<filter name="date_done" string="Completion Date" date="date_done" />
<group expand="0" string="Group by...">
<filter
string="Reference"
Expand Down Expand Up @@ -214,6 +228,18 @@
domain="[]"
context="{'group_by': 'auto_generated'}"
/>
<filter
string="Plan Date"
name="groupby_date"
domain="[]"
context="{'group_by': 'date'}"
/>
<filter
string="Completion Date"
name="groupby_date_done"
domain="[]"
context="{'group_by': 'date_done'}"
/>
</group>
</search>
</field>
Expand Down
21 changes: 21 additions & 0 deletions quality_control_stock_oca/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,22 @@ It also adds some shortcuts on picking and lots to these inspections.
.. contents::
:local:

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

Configure a QC trigger in the product, product template, or product category to create inspections efficiently:

* Trigger: Choose the trigger to activate the inspection process.
* Test: Define a group of questions with valid values for the inspection.
* Responsible: Assign a user responsible for the QC inspection.
* Partner: Optionally specify partners to limit the test to actions involving them.
* Timing: Determine when inspections are generated:
* Before: On picking confirmation.
* After: On picking completion.
* Plan Ahead: On picking confirmation, generating a non-editable plan inspection that becomes executable post-picking completion.

This streamlined configuration ensures that QC inspections are created and managed effectively based on your specified triggers and conditions.

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

Expand Down Expand Up @@ -76,6 +92,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
5 changes: 5 additions & 0 deletions quality_control_stock_oca/models/qc_inspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ def _prepare_inspection_header(self, object_ref, trigger_line):
# Fill qty when coming from pack operations
if object_ref and object_ref._name == "stock.move":
res["qty"] = object_ref.product_uom_qty
if object_ref.picking_id.immediate_transfer and trigger_line.timing in [
"before",
"plan_ahead",
]:
res["qty"] = object_ref.quantity_done

Check warning on line 79 in quality_control_stock_oca/models/qc_inspection.py

View check run for this annotation

Codecov / codecov/patch

quality_control_stock_oca/models/qc_inspection.py#L79

Added line #L79 was not covered by tests
return res


Expand Down
68 changes: 68 additions & 0 deletions quality_control_stock_oca/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# 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()
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):
moves = super()._action_confirm(merge=merge, merge_into=merge_into)
for move in moves:
move.trigger_inspection(["before", "plan_ahead"])
return moves
Loading

0 comments on commit 471e306

Please sign in to comment.