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

[15.0][IMP]stock_cycle_count: several improvements #1997

Merged
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7065ca1
[15.0][IMP] stock_cycle_count: add auto complete into cron
ArnauCForgeFlow Apr 17, 2024
e191484
[ADD] stock_cycle_count: line_accuracy in stock move lines
JoanSForgeFlow Apr 29, 2024
ab3986c
[IMP] stock_cycle_count: improved inventory_accuracy calculation
ArnauCForgeFlow May 3, 2024
232ed7b
[FIX] stock_cyle_count: remove duplicated field location_ids in tree …
JoanSForgeFlow May 10, 2024
bdc8e31
[IMP] stock_cycle_count: start inv adjustments depending on setting
ArnauCForgeFlow May 28, 2024
2e5b1f0
[FIX] stock_cycle_count: prevent creating infinte cycle counts
ArnauCForgeFlow Jun 25, 2024
3980626
[FIX] stock_cycle_count: prefill_counted_quantity zero, counted, false
ArnauCForgeFlow Jul 22, 2024
82bbd01
[FIX] stock_cycle_count: inventory adjustment already adds these fields
ArnauCForgeFlow Jul 24, 2024
ed30876
[IMP] stock_cycle_count: added more tests
ArnauCForgeFlow Jul 24, 2024
d04fe77
[IMP] stock_cycle_count: Enable edit and cascade updates for responsi…
JoanSForgeFlow Jul 31, 2024
d6c95c0
[IMP] stock_cycle_count: added new group by required date
ArnauCForgeFlow Sep 6, 2024
a82f01c
[IMP] stock_cycle_count: add multicompany rule
ArnauCForgeFlow Sep 10, 2024
bd1e6a6
[IMP] stock_cycle_count: remove auto confirmation logic from the cron
JoanSForgeFlow Sep 17, 2024
cddec1e
[FIX] stock_cycle_count: fixed tests since having initial quantity 0 …
ArnauCForgeFlow Sep 25, 2024
b8fd2aa
[IMP] stock_cycle_count: Refactor responsible_id sync using compute/i…
JoanSForgeFlow Oct 22, 2024
bbba2f8
[IMP] stock_cycle_count: Remove useless filtered
ArnauCForgeFlow Nov 15, 2024
2c9ecf6
[FIX] stock_cycle_count: use company of the location when creating cy…
JoanSForgeFlow Nov 21, 2024
cc0320c
[IMP] stock_cycle_count: add rec company_id to domain
ArnauCForgeFlow Nov 28, 2024
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: 2 additions & 0 deletions stock_cycle_count/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
"views/stock_warehouse_view.xml",
"views/stock_inventory_view.xml",
"views/stock_location_view.xml",
"views/stock_move_line_view.xml",
"views/res_config_settings_view.xml",
"data/cycle_count_sequence.xml",
"data/cycle_count_ir_cron.xml",
"reports/stock_location_accuracy_report.xml",
"reports/stock_cycle_count_report.xml",
"security/ir.model.access.csv",
"security/security.xml",
],
"license": "AGPL-3",
"installable": True,
Expand Down
2 changes: 2 additions & 0 deletions stock_cycle_count/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
from . import stock_inventory
from . import stock_warehouse
from . import stock_move
from . import stock_move_line
from . import stock_quant
45 changes: 35 additions & 10 deletions stock_cycle_count/models/stock_cycle_count.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# Copyright 2017-18 ForgeFlow S.L.
# (http://www.forgeflow.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
import logging

from odoo import _, api, fields, models
from odoo.exceptions import UserError

_logger = logging.getLogger(__name__)


class StockCycleCount(models.Model):
_name = "stock.cycle.count"
Expand All @@ -24,14 +27,29 @@
comodel_name="res.users",
string="Assigned to",
readonly=True,
states={"draft": [("readonly", False)]},
states={"draft": [("readonly", False)], "open": [("readonly", False)]},
tracking=True,
)
date_deadline = fields.Date(
string="Required Date",
readonly=True,
states={"draft": [("readonly", False)]},
tracking=True,
compute="_compute_date_deadline",
inverse="_inverse_date_deadline",
store=True,
)
automatic_deadline_date = fields.Date(
string="Automatic Required Date",
readonly=True,
states={"draft": [("readonly", False)]},
tracking=True,
)
manual_deadline_date = fields.Date(
string="Manual Required Date",
readonly=True,
states={"draft": [("readonly", False)]},
tracking=True,
)
cycle_count_rule_id = fields.Many2one(
comodel_name="stock.cycle.count.rule",
Expand Down Expand Up @@ -99,15 +117,10 @@
data = rec._prepare_inventory_adjustment()
inv = self.env["stock.inventory"].create(data)
if rec.company_id.auto_start_inventory_from_cycle_count:
inv.prefill_counted_quantity = (
rec.company_id.inventory_adjustment_counted_quantities
)
inv.action_state_to_in_progress()
if inv.prefill_counted_quantity == "zero":
inv.stock_quant_ids.write({"inventory_quantity": 0})
else:
for quant in inv.stock_quant_ids:
quant.write({"inventory_quantity": quant.quantity})
try:
inv.action_state_to_in_progress()
except Exception as e:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it a too broad Exception ?

Moreover, shouldn't we manage the exception in a more elegant manner than 'just' logging ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @rousseldenis

You might be right. I implemented it this way because we don't want the cron job to stop running due to a validation error from a location that already has an inventory adjustment in progress.

Do you have any suggestions for a better approach?

Thanks.

_logger.info("Error when beginning an adjustment: %s", str(e))

Check warning on line 123 in stock_cycle_count/models/stock_cycle_count.py

View check run for this annotation

Codecov / codecov/patch

stock_cycle_count/models/stock_cycle_count.py#L122-L123

Added lines #L122 - L123 were not covered by tests
self.write({"state": "open"})
return True

Expand All @@ -124,3 +137,15 @@
action["views"] = [(res and res.id or False, "form")]
action["res_id"] = adjustment_ids and adjustment_ids[0] or False
return action

@api.depends("automatic_deadline_date", "manual_deadline_date")
def _compute_date_deadline(self):
for rec in self:
if rec.manual_deadline_date:
rec.date_deadline = rec.manual_deadline_date
else:
rec.date_deadline = rec.automatic_deadline_date

def _inverse_date_deadline(self):
for rec in self:
rec.manual_deadline_date = rec.date_deadline
2 changes: 1 addition & 1 deletion stock_cycle_count/models/stock_cycle_count_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def _compute_rule_periodic(self, locs):
.search(
[
("location_ids", "in", [loc.id]),
("state", "in", ["confirm", "done", "draft"]),
("state", "in", ["in_progress", "done", "draft"]),
],
order="date desc",
limit=1,
Expand Down
81 changes: 69 additions & 12 deletions stock_cycle_count/models/stock_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,48 @@
)
inventory_accuracy = fields.Float(
string="Accuracy",
compute="_compute_inventory_accuracy",
digits=(3, 2),
store=True,
group_operator="avg",
default=False,
)
responsible_id = fields.Many2one(
tracking=True,
compute="_compute_responsible_id",
inverse="_inverse_responsible_id",
store=True,
readonly=False,
)

@api.depends("state", "stock_quant_ids")
def _compute_inventory_accuracy(self):
@api.depends("cycle_count_id.responsible_id")
def _compute_responsible_id(self):
for inv in self:
theoretical = sum(inv.stock_quant_ids.mapped(lambda x: abs(x.quantity)))
abs_discrepancy = sum(
inv.stock_quant_ids.mapped(lambda x: abs(x.inventory_diff_quantity))
)
if theoretical:
inv.inventory_accuracy = max(
PERCENT * (theoretical - abs_discrepancy) / theoretical, 0.0
if inv.cycle_count_id:
inv.responsible_id = inv.cycle_count_id.responsible_id
inv.stock_quant_ids.write(
{"user_id": inv.cycle_count_id.responsible_id}
)
if not inv.stock_quant_ids and inv.state == "done":
inv.inventory_accuracy = PERCENT

def _inverse_responsible_id(self):
for inv in self:
if inv.cycle_count_id and inv.responsible_id:
inv.cycle_count_id.responsible_id = inv.responsible_id

def write(self, vals):
result = super().write(vals)
if "responsible_id" in vals:
if not self.env.context.get("no_propagate"):
if (
self.cycle_count_id
and self.cycle_count_id.responsible_id.id != vals["responsible_id"]
):
self.cycle_count_id.with_context(no_propagate=True).write(

Check warning on line 67 in stock_cycle_count/models/stock_inventory.py

View check run for this annotation

Codecov / codecov/patch

stock_cycle_count/models/stock_inventory.py#L67

Added line #L67 was not covered by tests
{"responsible_id": vals["responsible_id"]}
)
for quant in self.mapped("stock_quant_ids"):
if quant.user_id.id != vals["responsible_id"]:
quant.write({"user_id": vals["responsible_id"]})

Check warning on line 72 in stock_cycle_count/models/stock_inventory.py

View check run for this annotation

Codecov / codecov/patch

stock_cycle_count/models/stock_inventory.py#L72

Added line #L72 was not covered by tests
return result

def _update_cycle_state(self):
for inv in self:
Expand All @@ -61,6 +84,26 @@
("location_id", "in", self.location_ids.ids),
]

def _calculate_inventory_accuracy(self):
for inv in self:
accuracy = 100
sum_line_accuracy = 0
sum_theoretical_qty = 0
if inv.stock_move_ids:
for line in inv.stock_move_ids:
sum_line_accuracy += line.theoretical_qty * line.line_accuracy
sum_theoretical_qty += line.theoretical_qty
if sum_theoretical_qty != 0:
accuracy = (sum_line_accuracy / sum_theoretical_qty) * 100
else:
accuracy = 0

Check warning on line 99 in stock_cycle_count/models/stock_inventory.py

View check run for this annotation

Codecov / codecov/patch

stock_cycle_count/models/stock_inventory.py#L99

Added line #L99 was not covered by tests
inv.update(
{
"inventory_accuracy": accuracy,
}
)
return False

def _link_to_planned_cycle_count(self):
self.ensure_one()
domain = self._domain_cycle_count_candidate()
Expand All @@ -85,11 +128,13 @@

def action_state_to_done(self):
res = super().action_state_to_done()
self._calculate_inventory_accuracy()
self._update_cycle_state()
return res

def action_force_done(self):
res = super().action_force_done()
self._calculate_inventory_accuracy()

Check warning on line 137 in stock_cycle_count/models/stock_inventory.py

View check run for this annotation

Codecov / codecov/patch

stock_cycle_count/models/stock_inventory.py#L137

Added line #L137 was not covered by tests
self._update_cycle_state()
return res

Expand Down Expand Up @@ -144,3 +189,15 @@
message=msg,
)
)

def action_state_to_in_progress(self):
res = super().action_state_to_in_progress()
self.prefill_counted_quantity = (
self.company_id.inventory_adjustment_counted_quantities
)
if self.prefill_counted_quantity == "zero":
self.stock_quant_ids.write({"inventory_quantity": 0})
elif self.prefill_counted_quantity == "counted":
for quant in self.stock_quant_ids:
quant.write({"inventory_quantity": quant.quantity})
return res
2 changes: 1 addition & 1 deletion stock_cycle_count/models/stock_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def create_zero_confirmation_cycle_count(self):
)
self.env["stock.cycle.count"].create(
{
"date_deadline": date,
"automatic_deadline_date": date,
"location_id": self.id,
"cycle_count_rule_id": rule.id,
"state": "draft",
Expand Down
15 changes: 15 additions & 0 deletions stock_cycle_count/models/stock_move_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2024 ForgeFlow S.L.
# (http://www.forgeflow.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import fields, models


class StockMoveLine(models.Model):
_inherit = "stock.move.line"

line_accuracy = fields.Float(
rousseldenis marked this conversation as resolved.
Show resolved Hide resolved
string="Accuracy",
store=True,
)
theoretical_qty = fields.Float(string="Theoretical Quantity", store=True)
counted_qty = fields.Float(string="Counted Quantity", store=True)
43 changes: 43 additions & 0 deletions stock_cycle_count/models/stock_quant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright 2024 ForgeFlow S.L.
# (http://www.forgeflow.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from odoo import models


class StockQuant(models.Model):
_inherit = "stock.quant"

def _apply_inventory(self):
accuracy_dict = {}
theoretical_dict = {}
counted_dict = {}
for rec in self:
if rec.discrepancy_percent > 100:
line_accuracy = 0
else:
line_accuracy = 1 - (rec.discrepancy_percent / 100)
accuracy_dict[rec.id] = line_accuracy
theoretical_dict[rec.id] = rec.quantity
counted_dict[rec.id] = rec.inventory_quantity
res = super()._apply_inventory()
for rec in self:
record_moves = self.env["stock.move.line"]
moves = record_moves.search(
[
("product_id", "=", rec.product_id.id),
("lot_id", "=", rec.lot_id.id),
"|",
("location_id", "=", rec.location_id.id),
("location_dest_id", "=", rec.location_id.id),
],
order="create_date asc",
)
move = moves[len(moves) - 1]
move.write(
{
"line_accuracy": accuracy_dict[rec.id],
"theoretical_qty": theoretical_dict[rec.id],
"counted_qty": counted_dict[rec.id],
}
)
return res
14 changes: 10 additions & 4 deletions stock_cycle_count/models/stock_warehouse.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def _cycle_count_rules_to_compute(self):
@api.model
def _prepare_cycle_count(self, cycle_count_proposed):
return {
"date_deadline": cycle_count_proposed["date"],
"automatic_deadline_date": cycle_count_proposed["date"],
"location_id": cycle_count_proposed["location"].id,
"cycle_count_rule_id": cycle_count_proposed["rule_type"].id,
"state": "draft",
Expand Down Expand Up @@ -106,12 +106,17 @@ def _process_cycle_counts(self, proposed_cycle_counts):
cycle_count_proposed = next(
filter(lambda x: x["date"] == earliest_date, proposed_for_loc)
)
self._handle_existing_cycle_counts(loc, cycle_count_proposed)
existing_cycle_counts = self._handle_existing_cycle_counts(
loc, cycle_count_proposed
)
delta = (
fields.Datetime.from_string(cycle_count_proposed["date"])
- datetime.today()
)
if delta.days < self.cycle_count_planning_horizon:
if (
not existing_cycle_counts
and delta.days < self.cycle_count_planning_horizon
):
cc_vals = self._prepare_cycle_count(cycle_count_proposed)
cc_vals_list.append(cc_vals)
return cc_vals_list
Expand All @@ -133,10 +138,11 @@ def _handle_existing_cycle_counts(self, location, cycle_count_proposed):
)
cc_to_update.write(
{
"date_deadline": cycle_count_proposed_date,
"automatic_deadline_date": cycle_count_proposed_date,
"cycle_count_rule_id": cycle_count_proposed["rule_type"].id,
}
)
return existing_cycle_counts

@api.model
def cron_cycle_count(self):
Expand Down
11 changes: 11 additions & 0 deletions stock_cycle_count/security/security.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="0">
<record model="ir.rule" id="stock_cycle_count_comp_rule">
<field name="name">Stock Cycle Count multi-company</field>
<field name="model_id" ref="model_stock_cycle_count" />
<field name="global" eval="True" />
<field
name="domain_force"
>['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
</odoo>
11 changes: 7 additions & 4 deletions stock_cycle_count/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@

/*
:Author: David Goodger ([email protected])
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.

Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.

See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
Expand Down Expand Up @@ -274,7 +275,7 @@
margin-left: 2em ;
margin-right: 2em }

pre.code .ln { color: grey; } /* line numbers */
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
Expand All @@ -300,7 +301,7 @@
span.pre {
white-space: pre }

span.problematic {
span.problematic, pre.problematic {
color: red }

span.section-subtitle {
Expand Down Expand Up @@ -513,7 +514,9 @@ <h2><a class="toc-backref" href="#toc-entry-13">Contributors</a></h2>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-14">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
Expand Down
Loading
Loading