Skip to content

Commit

Permalink
Merge PR #2169 into 16.0
Browse files Browse the repository at this point in the history
Signed-off-by LoisRForgeFlow
  • Loading branch information
OCA-git-bot committed Dec 9, 2024
2 parents 54c9af4 + 4e1b9c0 commit 038ee8c
Show file tree
Hide file tree
Showing 15 changed files with 552 additions and 42 deletions.
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 @@ class StockCycleCount(models.Model):
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 @@ def action_create_inventory_adjustment(self):
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:
_logger.info("Error when beginning an adjustment: %s", str(e))
self.write({"state": "open"})
return True

Expand All @@ -124,3 +137,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
3 changes: 2 additions & 1 deletion stock_cycle_count/models/stock_cycle_count_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ def _propose_cycle_count(self, date, location):
"date": fields.Datetime.from_string(date),
"location": location,
"rule_type": self,
"company_id": location.company_id,
}
return cycle_count

Expand All @@ -172,7 +173,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 @@ class StockInventory(models.Model):
)
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(
{"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"]})
return result

def _update_cycle_state(self):
for inv in self:
Expand All @@ -61,6 +84,26 @@ def _domain_cycle_count_candidate(self):
("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
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 _link_to_planned_cycle_count(self):

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()
self._update_cycle_state()
return res

Expand Down Expand Up @@ -144,3 +189,15 @@ def _check_cycle_count_consistency(self):
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(
string="Accuracy",
store=True,
)
theoretical_qty = fields.Float(string="Theoretical Quantity", store=True)
counted_qty = fields.Float(string="Counted Quantity", store=True)
44 changes: 44 additions & 0 deletions stock_cycle_count/models/stock_quant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# 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),
]
+ ([("company_id", "=", rec.company_id.id)] if rec.company_id else []),
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
15 changes: 11 additions & 4 deletions stock_cycle_count/models/stock_warehouse.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,11 @@ 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",
"company_id": cycle_count_proposed["company_id"].id,
}

def action_compute_cycle_count_rules(self):
Expand Down Expand Up @@ -106,12 +107,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 +139,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>
Loading

0 comments on commit 038ee8c

Please sign in to comment.