Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[16.0][IMP] quality_control_oca, quality_control_stock_oca #1278

Open
wants to merge 2 commits into
base: 16.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion quality_control_mrp_oca/models/mrp_production.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def _post_inventory(self, cancel_backorder=False):
]:
trigger_lines = trigger_lines.union(
self.env[model].get_trigger_line_for_product(
qc_trigger, move.product_id
qc_trigger, ["after"], move.product_id
)
)
for trigger_line in _filter_trigger_lines(trigger_lines):
Expand Down
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)
AungKoKoLin1997 marked this conversation as resolved.
Show resolved Hide resolved

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
19 changes: 19 additions & 0 deletions quality_control_stock_oca/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ 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 define the conditions for creating inspections:

* 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.

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

Expand Down Expand Up @@ -76,6 +90,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 @@
# 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
Loading