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 2 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
1 change: 1 addition & 0 deletions stock_cycle_count/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"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",
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
27 changes: 27 additions & 0 deletions stock_cycle_count/models/stock_cycle_count.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,21 @@ class StockCycleCount(models.Model):
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 @@ -124,3 +139,15 @@ def action_view_inventory(self):
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
10 changes: 10 additions & 0 deletions stock_cycle_count/models/stock_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,13 @@ def _check_cycle_count_consistency(self):
message=msg,
)
)

def action_state_to_in_progress(self):
res = super().action_state_to_in_progress()
self.stock_quant_ids.update(
{
"user_id": self.cycle_count_id.responsible_id,
"inventory_date": self.cycle_count_id.date_deadline,
}
)
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)
47 changes: 47 additions & 0 deletions stock_cycle_count/models/stock_quant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# 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",
).filtered(
lambda x: not x.company_id.id
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't it correspond to normal record rule ?

Copy link
Contributor

Choose a reason for hiding this comment

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

In other words, is the filtered necessary ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rousseldenis You are correct, since we are searching for stock move lines by location, and the locations already belong to a company, it is not needed this filter. I'll make the change.

Thanks

Copy link
Contributor

Choose a reason for hiding this comment

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

@rousseldenis @ArnauCForgeFlow it is not the normal record rule, it is filtering based on the quant company, the normal record rules filters based on the users companies, we cannot rely on the user having the proper companies.

That been said, I think instead of filtering, it can be done with a domain that includes a couple of company_id clauses.

or not rec.company_id.id
or rec.company_id.id == x.company_id.id
)
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
22 changes: 20 additions & 2 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 @@ -133,7 +133,7 @@ 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,
}
)
Expand All @@ -144,6 +144,24 @@ def cron_cycle_count(self):
try:
whs = self.search([])
whs.action_compute_cycle_count_rules()
today = fields.Date.today()
cycle_counts = self.env["stock.cycle.count"].search(
[("date_deadline", "<=", today), ("state", "=", "draft")]
)
for cycle_count in cycle_counts:
open_cycle_counts = self.env["stock.cycle.count"].search(
[
("location_id", "=", cycle_count.location_id.id),
("state", "=", "open"),
]
)
if open_cycle_counts:
continue
cycle_count.action_create_inventory_adjustment()
try:
cycle_count.stock_adjustment_ids.action_state_to_in_progress()
except Exception as e:
_logger.info("Error when beginning an adjustment: %s", str(e))
except Exception as e:
_logger.info("Error while running stock_cycle_count cron job: %s", str(e))
raise
Expand Down
32 changes: 25 additions & 7 deletions stock_cycle_count/tests/test_stock_cycle_count.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def test_cycle_count_planner(self):
"name": "To be cancelled when running cron job.",
"cycle_count_rule_id": self.rule_periodic.id,
"location_id": loc.id,
"date_deadline": date_pre_existing_cc,
"automatic_deadline_date": date_pre_existing_cc,
}
)
self.assertEqual(
Expand Down Expand Up @@ -188,14 +188,32 @@ def test_cycle_count_planner(self):
move1._action_assign()
move1.move_line_ids[0].qty_done = 1.0
move1._action_done()
# Remove the pre_existing_count
self.inventory_model.search(
[("cycle_count_id", "=", pre_existing_count.id)], limit=1
).unlink()
pre_existing_count.unlink()
# Execute cron for first time
wh.cron_cycle_count()
self.assertNotEqual(
pre_existing_count.date_deadline,
date_pre_existing_cc,
"Date of pre-existing cycle counts has not been " "updated.",
# There are counts in state open(execution) and not in state draft
open_counts = self.cycle_count_model.search(
[("location_id", "in", locs.ids), ("state", "=", "open")]
)
counts = self.cycle_count_model.search([("location_id", "in", locs.ids)])
self.assertTrue(counts, "Cycle counts not planned")
self.assertTrue(open_counts, "Cycle counts in execution state")
draft_counts = self.cycle_count_model.search(
[("location_id", "in", locs.ids), ("state", "=", "draft")]
)
self.assertFalse(draft_counts, "No Cycle counts in draft state")
# Execute the cron for second time
wh.cron_cycle_count()
# New cycle counts for same location created in draft state
draft_counts = self.cycle_count_model.search(
[("location_id", "in", locs.ids), ("state", "=", "draft")]
)
self.assertTrue(draft_counts, "No Cycle counts in draft state")
# Inventory adjustment only started for cycle counts in open state
self.assertTrue(open_counts.stock_adjustment_ids)
self.assertFalse(draft_counts.stock_adjustment_ids)
# Zero-confirmations:
count = self.cycle_count_model.search(
[
Expand Down
1 change: 1 addition & 0 deletions stock_cycle_count/views/stock_cycle_count_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<tree
decoration-muted="state == 'cancelled'"
decoration-info="state == 'draft'"
multi_edit="1"
>
<field name="name" />
<field name="location_id" />
Expand Down
55 changes: 55 additions & 0 deletions stock_cycle_count/views/stock_move_line_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2017 ForgeFlow S.L.
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="view_stock_move_line_tree" model="ir.ui.view">
<field name="name">Stock Move Line Tree - cycle count extension</field>
<field name="model">stock.move.line</field>
<field
name="inherit_id"
ref="stock_inventory.view_stock_move_line_inventory_tree"
/>
<field name="arch" type="xml">
<field name="product_id" position="after">
<field name="is_inventory" invisible="1" />
<field
name="theoretical_qty"
attrs="{'invisible': [('is_inventory', '=', False)]}"
/>
<field
name="counted_qty"
attrs="{'invisible': [('is_inventory', '=', False)]}"
/>
<field
name="line_accuracy"
attrs="{'invisible': [('is_inventory', '=', False)]}"
widget="percentage"
/>
</field>
</field>
</record>
<record id="view_stock_move_line_form" model="ir.ui.view">
<field name="name">Stock Move Line Form - cycle count extension</field>
<field name="model">stock.move.line</field>
<field name="inherit_id" ref="stock.view_move_line_form" />
<field name="arch" type="xml">
<field name="lot_id" position="after">
<field name="is_inventory" invisible="1" />
<field
name="theoretical_qty"
attrs="{'invisible': [('is_inventory', '=', False)]}"
/>
<field
name="counted_qty"
attrs="{'invisible': [('is_inventory', '=', False)]}"
/>
<field
name="line_accuracy"
attrs="{'invisible': [('is_inventory', '=', False)]}"
class="oe_inline"
widget="percentage"
/>
</field>
</field>
</record>
</odoo>