mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
[IMP] stock_cycle_count: preformance improvements
This commit is contained in:
@@ -84,20 +84,23 @@ class StockCycleCount(models.Model):
|
||||
"responsible_id": self.responsible_id.id,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
vals["name"] = self.env["ir.sequence"].next_by_code("stock.cycle.count") or ""
|
||||
return super(StockCycleCount, self).create(vals)
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
vals["name"] = (
|
||||
self.env["ir.sequence"].next_by_code("stock.cycle.count") or ""
|
||||
)
|
||||
return super().create(vals_list)
|
||||
|
||||
def action_create_inventory_adjustment(self):
|
||||
if any([s != "draft" for s in self.mapped("state")]):
|
||||
if any([state != "draft" for state in self.mapped("state")]):
|
||||
raise UserError(_("You can only confirm cycle counts in state 'Planned'."))
|
||||
for rec in self:
|
||||
data = rec._prepare_inventory_adjustment()
|
||||
inv = self.env["stock.inventory"].create(data)
|
||||
if self.company_id.auto_start_inventory_from_cycle_count:
|
||||
if rec.company_id.auto_start_inventory_from_cycle_count:
|
||||
inv.prefill_counted_quantity = (
|
||||
self.company_id.inventory_adjustment_counted_quantities
|
||||
rec.company_id.inventory_adjustment_counted_quantities
|
||||
)
|
||||
inv.action_state_to_in_progress()
|
||||
if inv.prefill_counted_quantity == "zero":
|
||||
@@ -109,9 +112,9 @@ class StockCycleCount(models.Model):
|
||||
return True
|
||||
|
||||
def action_view_inventory(self):
|
||||
action = self.env.ref(
|
||||
action = self.env["ir.actions.act_window"]._for_xml_id(
|
||||
"stock_inventory.action_view_inventory_group_form"
|
||||
).read()[0]
|
||||
)
|
||||
action["context"] = {}
|
||||
adjustment_ids = self.mapped("stock_adjustment_ids").ids
|
||||
if len(adjustment_ids) > 1:
|
||||
|
||||
@@ -12,6 +12,47 @@ class StockCycleCountRule(models.Model):
|
||||
_name = "stock.cycle.count.rule"
|
||||
_description = "Stock Cycle Counts Rules"
|
||||
|
||||
name = fields.Char(required=True)
|
||||
rule_type = fields.Selection(
|
||||
selection="_selection_rule_types", string="Type of rule", required=True
|
||||
)
|
||||
rule_description = fields.Char(compute="_compute_rule_description")
|
||||
active = fields.Boolean(default=True)
|
||||
periodic_qty_per_period = fields.Integer(string="Counts per period", default=1)
|
||||
periodic_count_period = fields.Integer(string="Period in days")
|
||||
turnover_inventory_value_threshold = fields.Float()
|
||||
currency_id = fields.Many2one(
|
||||
comodel_name="res.currency", string="Currency", compute="_compute_currency_id"
|
||||
)
|
||||
accuracy_threshold = fields.Float(
|
||||
string="Minimum Accuracy Threshold", digits=(3, 2)
|
||||
)
|
||||
apply_in = fields.Selection(
|
||||
string="Apply this rule in:",
|
||||
selection=[
|
||||
("warehouse", "Selected warehouses"),
|
||||
("location", "Selected Location Zones."),
|
||||
],
|
||||
default="warehouse",
|
||||
)
|
||||
warehouse_ids = fields.Many2many(
|
||||
comodel_name="stock.warehouse",
|
||||
relation="warehouse_cycle_count_rule_rel",
|
||||
column1="rule_id",
|
||||
column2="warehouse_id",
|
||||
string="Warehouses where applied",
|
||||
compute="_compute_warehouse_ids",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
location_ids = fields.Many2many(
|
||||
comodel_name="stock.location",
|
||||
relation="location_cycle_count_rule_rel",
|
||||
column1="rule_id",
|
||||
column2="location_id",
|
||||
string="Zones where applied",
|
||||
)
|
||||
|
||||
def _compute_currency_id(self):
|
||||
for rec in self:
|
||||
rec.currency_id = self.env.user.company_id.currency_id
|
||||
@@ -94,53 +135,15 @@ class StockCycleCountRule(models.Model):
|
||||
if rec.periodic_count_period < 0:
|
||||
raise ValidationError(_("You cannot define a negative period."))
|
||||
|
||||
@api.onchange("location_ids")
|
||||
def _onchange_locaton_ids(self):
|
||||
"""Get the warehouses for the selected locations."""
|
||||
wh_ids = []
|
||||
for loc in self.location_ids:
|
||||
wh_ids.append(loc.warehouse_id.id)
|
||||
wh_ids = list(set(wh_ids))
|
||||
wh_ids = [wh for wh in wh_ids if wh]
|
||||
self.warehouse_ids = self.env["stock.warehouse"].browse(wh_ids)
|
||||
|
||||
name = fields.Char(required=True)
|
||||
rule_type = fields.Selection(
|
||||
selection="_selection_rule_types", string="Type of rule", required=True
|
||||
)
|
||||
rule_description = fields.Char(compute="_compute_rule_description")
|
||||
active = fields.Boolean(default=True)
|
||||
periodic_qty_per_period = fields.Integer(string="Counts per period", default=1)
|
||||
periodic_count_period = fields.Integer(string="Period in days")
|
||||
turnover_inventory_value_threshold = fields.Float()
|
||||
currency_id = fields.Many2one(
|
||||
comodel_name="res.currency", string="Currency", compute="_compute_currency_id"
|
||||
)
|
||||
accuracy_threshold = fields.Float(
|
||||
string="Minimum Accuracy Threshold", digits=(3, 2)
|
||||
)
|
||||
apply_in = fields.Selection(
|
||||
string="Apply this rule in:",
|
||||
selection=[
|
||||
("warehouse", "Selected warehouses"),
|
||||
("location", "Selected Location Zones."),
|
||||
],
|
||||
default="warehouse",
|
||||
)
|
||||
warehouse_ids = fields.Many2many(
|
||||
comodel_name="stock.warehouse",
|
||||
relation="warehouse_cycle_count_rule_rel",
|
||||
column1="rule_id",
|
||||
column2="warehouse_id",
|
||||
string="Warehouses where applied",
|
||||
)
|
||||
location_ids = fields.Many2many(
|
||||
comodel_name="stock.location",
|
||||
relation="location_cycle_count_rule_rel",
|
||||
column1="rule_id",
|
||||
column2="location_id",
|
||||
string="Zones where applied",
|
||||
)
|
||||
@api.depends("location_ids")
|
||||
def _compute_warehouse_ids(self):
|
||||
for record in self:
|
||||
wh_ids = []
|
||||
for loc in record.location_ids:
|
||||
if loc.warehouse_id: # Make sure warehouse_id is set
|
||||
wh_ids.append(loc.warehouse_id.id)
|
||||
wh_ids = list(set(wh_ids)) # Remove duplicates
|
||||
record.warehouse_ids = [(6, 0, wh_ids)]
|
||||
|
||||
def compute_rule(self, locs):
|
||||
if self.rule_type == "periodic":
|
||||
@@ -184,7 +187,7 @@ class StockCycleCountRule(models.Model):
|
||||
) + timedelta(days=period)
|
||||
if next_date < datetime.today():
|
||||
next_date = datetime.today()
|
||||
except Exception as e:
|
||||
except AttributeError as e:
|
||||
raise UserError(
|
||||
_(
|
||||
"Error found determining the frequency of periodic "
|
||||
@@ -220,35 +223,36 @@ class StockCycleCountRule(models.Model):
|
||||
@api.model
|
||||
def _compute_rule_turnover(self, locs):
|
||||
cycle_counts = []
|
||||
location_ids = locs.mapped("id")
|
||||
inventories = self.env["stock.inventory"].search(
|
||||
[
|
||||
("location_ids", "in", location_ids),
|
||||
("state", "in", ["confirm", "done", "draft"]),
|
||||
]
|
||||
)
|
||||
inventory_dates_by_location = {loc.id: [] for loc in locs}
|
||||
for inventory in inventories:
|
||||
for location in inventory.location_ids:
|
||||
if location.id in inventory_dates_by_location:
|
||||
inventory_dates_by_location[location.id].append(inventory.date)
|
||||
|
||||
for loc in locs:
|
||||
last_inventories = (
|
||||
self.env["stock.inventory"]
|
||||
.search(
|
||||
[
|
||||
("location_ids", "in", [loc.id]),
|
||||
("state", "in", ["confirm", "done", "draft"]),
|
||||
]
|
||||
)
|
||||
.mapped("date")
|
||||
)
|
||||
last_inventories = inventory_dates_by_location.get(loc.id, [])
|
||||
if last_inventories:
|
||||
latest_inventory = sorted(last_inventories, reverse=True)[0]
|
||||
moves = self._get_turnover_moves(loc, latest_inventory)
|
||||
if moves:
|
||||
total_turnover = 0.0
|
||||
for m in moves:
|
||||
turnover = self._compute_turnover(m)
|
||||
total_turnover += turnover
|
||||
total_turnover = sum(self._compute_turnover(m) for m in moves)
|
||||
try:
|
||||
if total_turnover > self.turnover_inventory_value_threshold:
|
||||
next_date = datetime.today()
|
||||
cycle_count = self._propose_cycle_count(next_date, loc)
|
||||
cycle_counts.append(cycle_count)
|
||||
except Exception as e:
|
||||
except AttributeError as e:
|
||||
raise UserError(
|
||||
_(
|
||||
"Error found when comparing turnover with the "
|
||||
"rule threshold. %s"
|
||||
"Error found when comparing turnover "
|
||||
"with the rule threshold. %s"
|
||||
)
|
||||
% str(e)
|
||||
) from e
|
||||
|
||||
@@ -11,20 +11,6 @@ PERCENT = 100.0
|
||||
class StockInventory(models.Model):
|
||||
_inherit = "stock.inventory"
|
||||
|
||||
@api.depends("state", "stock_quant_ids")
|
||||
def _compute_inventory_accuracy(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 not inv.stock_quant_ids and inv.state == "done":
|
||||
inv.inventory_accuracy = PERCENT
|
||||
|
||||
prefill_counted_quantity = fields.Selection(
|
||||
string="Counted Quantities",
|
||||
help="Allows to start with a pre-filled counted quantity for each lines or "
|
||||
@@ -49,20 +35,19 @@ class StockInventory(models.Model):
|
||||
group_operator="avg",
|
||||
)
|
||||
|
||||
def _get_default_counted_quantitites(self):
|
||||
company_id = self.env.context.get("default_company_id", self.env.company)
|
||||
return company_id.inventory_adjustment_counted_quantities or "counted"
|
||||
|
||||
prefill_counted_quantity = fields.Selection(
|
||||
string="Counted Quantities",
|
||||
help="Allows to start with a pre-filled counted quantity for each lines or "
|
||||
"with all counted quantities set to zero.",
|
||||
default=_get_default_counted_quantitites,
|
||||
selection=[
|
||||
("counted", "Default to stock on hand"),
|
||||
("zero", "Default to zero"),
|
||||
],
|
||||
)
|
||||
@api.depends("state", "stock_quant_ids")
|
||||
def _compute_inventory_accuracy(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 not inv.stock_quant_ids and inv.state == "done":
|
||||
inv.inventory_accuracy = PERCENT
|
||||
|
||||
def _update_cycle_state(self):
|
||||
for inv in self:
|
||||
@@ -99,21 +84,22 @@ class StockInventory(models.Model):
|
||||
return True
|
||||
|
||||
def action_state_to_done(self):
|
||||
res = super(StockInventory, self).action_state_to_done()
|
||||
res = super().action_state_to_done()
|
||||
self._update_cycle_state()
|
||||
return res
|
||||
|
||||
def action_force_done(self):
|
||||
res = super(StockInventory, self).action_force_done()
|
||||
res = super().action_force_done()
|
||||
self._update_cycle_state()
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
res = super().create(vals)
|
||||
if not res.cycle_count_id:
|
||||
res._link_to_planned_cycle_count()
|
||||
return res
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
inventories = super().create(vals_list)
|
||||
for inv in inventories:
|
||||
if not inv.cycle_count_id:
|
||||
inv._link_to_planned_cycle_count()
|
||||
return inventories
|
||||
|
||||
def _is_consistent_with_cycle_count(self):
|
||||
self.ensure_one()
|
||||
@@ -153,7 +139,9 @@ class StockInventory(models.Model):
|
||||
if not is_consistent:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"The Inventory Adjustment is inconsistent with the Cycle Count:\n%s"
|
||||
"The Inventory Adjustment is inconsistent "
|
||||
"with the Cycle Count:\n%(message)s",
|
||||
message=msg,
|
||||
)
|
||||
% msg
|
||||
)
|
||||
|
||||
@@ -21,28 +21,6 @@ except (ImportError, IOError) as err:
|
||||
class StockLocation(models.Model):
|
||||
_inherit = "stock.location"
|
||||
|
||||
def _compute_loc_accuracy(self):
|
||||
for rec in self:
|
||||
history = self.env["stock.inventory"].search(
|
||||
[("location_ids", "in", rec.id), ("state", "=", "done")],
|
||||
order="write_date desc",
|
||||
)
|
||||
if history:
|
||||
wh = rec.warehouse_id
|
||||
if (
|
||||
wh.counts_for_accuracy_qty
|
||||
and len(history) > wh.counts_for_accuracy_qty
|
||||
):
|
||||
rec.loc_accuracy = mean(
|
||||
history[: wh.counts_for_accuracy_qty].mapped(
|
||||
"inventory_accuracy"
|
||||
)
|
||||
)
|
||||
else:
|
||||
rec.loc_accuracy = mean(history.mapped("inventory_accuracy"))
|
||||
else:
|
||||
rec.loc_accuracy = 0
|
||||
|
||||
zero_confirmation_disabled = fields.Boolean(
|
||||
string="Disable Zero Confirmations",
|
||||
help="Define whether this location will trigger a zero-confirmation "
|
||||
@@ -60,19 +38,46 @@ class StockLocation(models.Model):
|
||||
string="Inventory Accuracy", compute="_compute_loc_accuracy", digits=(3, 2)
|
||||
)
|
||||
|
||||
def _compute_loc_accuracy(self):
|
||||
history = self.env["stock.inventory"].search(
|
||||
[("location_ids", "in", self.ids), ("state", "=", "done")],
|
||||
order="write_date desc",
|
||||
)
|
||||
for rec in self:
|
||||
loc_history = history.filtered_domain([("location_ids", "in", rec.id)])
|
||||
if loc_history:
|
||||
wh = rec.warehouse_id
|
||||
if (
|
||||
wh.counts_for_accuracy_qty
|
||||
and len(loc_history) > wh.counts_for_accuracy_qty
|
||||
):
|
||||
rec.loc_accuracy = mean(
|
||||
loc_history[: wh.counts_for_accuracy_qty].mapped(
|
||||
"inventory_accuracy"
|
||||
)
|
||||
)
|
||||
else:
|
||||
rec.loc_accuracy = mean(loc_history.mapped("inventory_accuracy"))
|
||||
else:
|
||||
rec.loc_accuracy = 0
|
||||
|
||||
def _get_zero_confirmation_domain(self):
|
||||
self.ensure_one()
|
||||
domain = [("location_id", "=", self.id), ("quantity", ">", 0.0)]
|
||||
return domain
|
||||
|
||||
def check_zero_confirmation(self):
|
||||
rule_model = self.env["stock.cycle.count.rule"]
|
||||
warehouse_ids = self.mapped("warehouse_id.id")
|
||||
zero_rules = rule_model.search(
|
||||
[("rule_type", "=", "zero"), ("warehouse_ids", "in", warehouse_ids)]
|
||||
)
|
||||
warehouse_to_rules = {rule.warehouse_ids.id: rule for rule in zero_rules}
|
||||
|
||||
for rec in self:
|
||||
if not rec.zero_confirmation_disabled:
|
||||
wh = rec.warehouse_id
|
||||
rule_model = self.env["stock.cycle.count.rule"]
|
||||
zero_rule = rule_model.search(
|
||||
[("rule_type", "=", "zero"), ("warehouse_ids", "=", wh.id)]
|
||||
)
|
||||
zero_rule = warehouse_to_rules.get(wh.id)
|
||||
if zero_rule:
|
||||
quants = self.env["stock.quant"].search(
|
||||
rec._get_zero_confirmation_domain()
|
||||
|
||||
@@ -41,7 +41,7 @@ class StockWarehouse(models.Model):
|
||||
@api.model
|
||||
def _get_cycle_count_locations_search_domain(self, parent):
|
||||
domain = [
|
||||
("parent_path", "=like", parent.parent_path + "%"),
|
||||
("id", "child_of", parent.id),
|
||||
("cycle_count_disabled", "=", False),
|
||||
]
|
||||
return domain
|
||||
@@ -81,56 +81,62 @@ class StockWarehouse(models.Model):
|
||||
returns a list with required dates for the cycle count of each
|
||||
location"""
|
||||
for rec in self:
|
||||
proposed_cycle_counts = []
|
||||
rules = rec._cycle_count_rules_to_compute()
|
||||
for rule in rules:
|
||||
locations = rec._search_cycle_count_locations(rule)
|
||||
if locations:
|
||||
proposed_cycle_counts.extend(rule.compute_rule(locations))
|
||||
proposed_cycle_counts = rec._get_proposed_cycle_counts()
|
||||
if proposed_cycle_counts:
|
||||
locations = list({d["location"] for d in proposed_cycle_counts})
|
||||
for loc in locations:
|
||||
proposed_for_loc = list(
|
||||
filter(lambda x: x["location"] == loc, proposed_cycle_counts)
|
||||
)
|
||||
earliest_date = min(d["date"] for d in proposed_for_loc)
|
||||
cycle_count_proposed = list(
|
||||
filter(lambda x: x["date"] == earliest_date, proposed_for_loc)
|
||||
)[0]
|
||||
domain = [("location_id", "=", loc.id), ("state", "in", ["draft"])]
|
||||
existing_cycle_counts = self.env["stock.cycle.count"].search(domain)
|
||||
if existing_cycle_counts:
|
||||
existing_earliest_date = sorted(
|
||||
existing_cycle_counts.mapped("date_deadline")
|
||||
)[0]
|
||||
existing_earliest_date = fields.Date.from_string(
|
||||
existing_earliest_date
|
||||
)
|
||||
cycle_count_proposed_date = fields.Date.from_string(
|
||||
cycle_count_proposed["date"]
|
||||
)
|
||||
if cycle_count_proposed_date < existing_earliest_date:
|
||||
cc_to_update = existing_cycle_counts.search(
|
||||
[("date_deadline", "=", existing_earliest_date)]
|
||||
)
|
||||
cc_to_update.write(
|
||||
{
|
||||
"date_deadline": cycle_count_proposed_date,
|
||||
"cycle_count_rule_id": cycle_count_proposed[
|
||||
"rule_type"
|
||||
].id,
|
||||
}
|
||||
)
|
||||
delta = (
|
||||
fields.Datetime.from_string(cycle_count_proposed["date"])
|
||||
- datetime.today()
|
||||
)
|
||||
if (
|
||||
not existing_cycle_counts
|
||||
and delta.days < rec.cycle_count_planning_horizon
|
||||
):
|
||||
cc_vals = self._prepare_cycle_count(cycle_count_proposed)
|
||||
self.env["stock.cycle.count"].create(cc_vals)
|
||||
cc_vals_list = rec._process_cycle_counts(proposed_cycle_counts)
|
||||
self.env["stock.cycle.count"].create(cc_vals_list)
|
||||
|
||||
def _get_proposed_cycle_counts(self):
|
||||
proposed_cycle_counts = []
|
||||
rules = self._cycle_count_rules_to_compute()
|
||||
for rule in rules:
|
||||
locations = self._search_cycle_count_locations(rule)
|
||||
if locations:
|
||||
proposed_cycle_counts.extend(rule.compute_rule(locations))
|
||||
return proposed_cycle_counts
|
||||
|
||||
def _process_cycle_counts(self, proposed_cycle_counts):
|
||||
cc_vals_list = []
|
||||
locations = list({d["location"] for d in proposed_cycle_counts})
|
||||
for loc in locations:
|
||||
proposed_for_loc = list(
|
||||
filter(lambda x: x["location"] == loc, proposed_cycle_counts)
|
||||
)
|
||||
earliest_date = min(d["date"] for d in proposed_for_loc)
|
||||
cycle_count_proposed = next(
|
||||
filter(lambda x: x["date"] == earliest_date, proposed_for_loc)
|
||||
)
|
||||
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:
|
||||
cc_vals = self._prepare_cycle_count(cycle_count_proposed)
|
||||
cc_vals_list.append(cc_vals)
|
||||
return cc_vals_list
|
||||
|
||||
def _handle_existing_cycle_counts(self, location, cycle_count_proposed):
|
||||
domain = [("location_id", "=", location.id), ("state", "in", ["draft"])]
|
||||
existing_cycle_counts = self.env["stock.cycle.count"].search(domain)
|
||||
if existing_cycle_counts:
|
||||
existing_earliest_date = sorted(
|
||||
existing_cycle_counts.mapped("date_deadline")
|
||||
)[0]
|
||||
existing_earliest_date = fields.Date.from_string(existing_earliest_date)
|
||||
cycle_count_proposed_date = fields.Date.from_string(
|
||||
cycle_count_proposed["date"]
|
||||
)
|
||||
if cycle_count_proposed_date < existing_earliest_date:
|
||||
cc_to_update = existing_cycle_counts.filtered(
|
||||
lambda x: x.date_deadline == existing_earliest_date
|
||||
)
|
||||
cc_to_update.write(
|
||||
{
|
||||
"date_deadline": cycle_count_proposed_date,
|
||||
"cycle_count_rule_id": cycle_count_proposed["rule_type"].id,
|
||||
}
|
||||
)
|
||||
|
||||
@api.model
|
||||
def cron_cycle_count(self):
|
||||
|
||||
@@ -22,9 +22,11 @@ class LocationAccuracyReport(models.AbstractModel):
|
||||
def _get_location_data(self, locations):
|
||||
data = dict()
|
||||
inventory_obj = self.env["stock.inventory"]
|
||||
location_ids = locations.mapped("id")
|
||||
counts = inventory_obj.search([("location_id", "in", location_ids)])
|
||||
for loc in locations:
|
||||
counts = inventory_obj.search(self._get_inventory_domain(loc.id))
|
||||
data[loc] = counts
|
||||
loc_counts = counts.filtered(lambda c: c.location_id == loc)
|
||||
data[loc] = loc_counts
|
||||
return data
|
||||
|
||||
def render_html(self, data=None):
|
||||
|
||||
@@ -8,84 +8,80 @@ from odoo.tests import common
|
||||
|
||||
|
||||
class TestStockCycleCount(common.TransactionCase):
|
||||
def setUp(self):
|
||||
super(TestStockCycleCount, self).setUp()
|
||||
self.res_users_model = self.env["res.users"]
|
||||
self.cycle_count_model = self.env["stock.cycle.count"]
|
||||
self.stock_cycle_count_rule_model = self.env["stock.cycle.count.rule"]
|
||||
self.inventory_model = self.env["stock.inventory"]
|
||||
self.stock_location_model = self.env["stock.location"]
|
||||
self.stock_move_model = self.env["stock.move"]
|
||||
self.stock_warehouse_model = self.env["stock.warehouse"]
|
||||
self.product_model = self.env["product.product"]
|
||||
self.quant_model = self.env["stock.quant"]
|
||||
self.move_model = self.env["stock.move"]
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestStockCycleCount, cls).setUpClass()
|
||||
cls.res_users_model = cls.env["res.users"]
|
||||
cls.cycle_count_model = cls.env["stock.cycle.count"]
|
||||
cls.stock_cycle_count_rule_model = cls.env["stock.cycle.count.rule"]
|
||||
cls.inventory_model = cls.env["stock.inventory"]
|
||||
cls.stock_location_model = cls.env["stock.location"]
|
||||
cls.stock_move_model = cls.env["stock.move"]
|
||||
cls.stock_warehouse_model = cls.env["stock.warehouse"]
|
||||
cls.product_model = cls.env["product.product"]
|
||||
cls.quant_model = cls.env["stock.quant"]
|
||||
cls.move_model = cls.env["stock.move"]
|
||||
|
||||
self.company = self.env.ref("base.main_company")
|
||||
self.partner = self.env.ref("base.res_partner_1")
|
||||
self.g_stock_manager = self.env.ref("stock.group_stock_manager")
|
||||
self.g_stock_user = self.env.ref("stock.group_stock_user")
|
||||
cls.company = cls.env.ref("base.main_company")
|
||||
cls.partner = cls.env.ref("base.res_partner_1")
|
||||
cls.g_stock_manager = cls.env.ref("stock.group_stock_manager")
|
||||
cls.g_stock_user = cls.env.ref("stock.group_stock_user")
|
||||
|
||||
# Create users:
|
||||
self.manager = self._create_user(
|
||||
"user_1", [self.g_stock_manager], self.company
|
||||
).id
|
||||
self.user = self._create_user("user_2", [self.g_stock_user], self.company).id
|
||||
cls.manager = cls._create_user("user_1", [cls.g_stock_manager], cls.company).id
|
||||
cls.user = cls._create_user("user_2", [cls.g_stock_user], cls.company).id
|
||||
|
||||
# Create warehouses:
|
||||
self.big_wh = self.stock_warehouse_model.create(
|
||||
cls.big_wh = cls.stock_warehouse_model.create(
|
||||
{"name": "BIG", "code": "B", "cycle_count_planning_horizon": 30}
|
||||
)
|
||||
self.small_wh = self.stock_warehouse_model.create(
|
||||
{"name": "SMALL", "code": "S"}
|
||||
)
|
||||
cls.small_wh = cls.stock_warehouse_model.create({"name": "SMALL", "code": "S"})
|
||||
|
||||
# Create rules:
|
||||
self.rule_periodic = self._create_stock_cycle_count_rule_periodic(
|
||||
self.manager, "rule_1", [2, 7]
|
||||
cls.rule_periodic = cls._create_stock_cycle_count_rule_periodic(
|
||||
cls.manager, "rule_1", [2, 7]
|
||||
)
|
||||
self.rule_turnover = self._create_stock_cycle_count_rule_turnover(
|
||||
self.manager, "rule_2", [100]
|
||||
cls.rule_turnover = cls._create_stock_cycle_count_rule_turnover(
|
||||
cls.manager, "rule_2", [100]
|
||||
)
|
||||
self.rule_accuracy = self._create_stock_cycle_count_rule_accuracy(
|
||||
self.manager, "rule_3", [5], self.big_wh.view_location_id.ids
|
||||
)
|
||||
self.zero_rule = self._create_stock_cycle_count_rule_zero(
|
||||
self.manager, "rule_4"
|
||||
cls.rule_accuracy = cls._create_stock_cycle_count_rule_accuracy(
|
||||
cls.manager, "rule_3", [5], cls.big_wh.view_location_id.ids
|
||||
)
|
||||
cls.zero_rule = cls._create_stock_cycle_count_rule_zero(cls.manager, "rule_4")
|
||||
|
||||
# Configure warehouses:
|
||||
self.rule_ids = [
|
||||
self.rule_periodic.id,
|
||||
self.rule_turnover.id,
|
||||
self.rule_accuracy.id,
|
||||
self.zero_rule.id,
|
||||
cls.rule_ids = [
|
||||
cls.rule_periodic.id,
|
||||
cls.rule_turnover.id,
|
||||
cls.rule_accuracy.id,
|
||||
cls.zero_rule.id,
|
||||
]
|
||||
self.big_wh.write({"cycle_count_rule_ids": [(6, 0, self.rule_ids)]})
|
||||
cls.big_wh.write({"cycle_count_rule_ids": [(6, 0, cls.rule_ids)]})
|
||||
|
||||
# Create a location:
|
||||
self.count_loc = self.stock_location_model.create(
|
||||
cls.count_loc = cls.stock_location_model.create(
|
||||
{"name": "Place", "usage": "production"}
|
||||
)
|
||||
self.stock_location_model._parent_store_compute()
|
||||
cls.stock_location_model._parent_store_compute()
|
||||
|
||||
# Create a cycle count:
|
||||
self.cycle_count_1 = self.cycle_count_model.with_user(self.manager).create(
|
||||
cls.cycle_count_1 = cls.cycle_count_model.with_user(cls.manager).create(
|
||||
{
|
||||
"name": "Test cycle count",
|
||||
"cycle_count_rule_id": self.rule_periodic.id,
|
||||
"location_id": self.count_loc.id,
|
||||
"cycle_count_rule_id": cls.rule_periodic.id,
|
||||
"location_id": cls.count_loc.id,
|
||||
}
|
||||
)
|
||||
|
||||
# Create a product:
|
||||
self.product1 = self.product_model.create(
|
||||
cls.product1 = cls.product_model.create(
|
||||
{"name": "Test Product 1", "type": "product", "default_code": "PROD1"}
|
||||
)
|
||||
|
||||
def _create_user(self, login, groups, company):
|
||||
@classmethod
|
||||
def _create_user(cls, login, groups, company):
|
||||
group_ids = [group.id for group in groups]
|
||||
user = self.res_users_model.create(
|
||||
user = cls.res_users_model.create(
|
||||
{
|
||||
"name": login,
|
||||
"login": login,
|
||||
@@ -97,8 +93,9 @@ class TestStockCycleCount(common.TransactionCase):
|
||||
)
|
||||
return user
|
||||
|
||||
def _create_stock_cycle_count_rule_periodic(self, uid, name, values):
|
||||
rule = self.stock_cycle_count_rule_model.with_user(uid).create(
|
||||
@classmethod
|
||||
def _create_stock_cycle_count_rule_periodic(cls, uid, name, values):
|
||||
rule = cls.stock_cycle_count_rule_model.with_user(uid).create(
|
||||
{
|
||||
"name": name,
|
||||
"rule_type": "periodic",
|
||||
@@ -108,8 +105,9 @@ class TestStockCycleCount(common.TransactionCase):
|
||||
)
|
||||
return rule
|
||||
|
||||
def _create_stock_cycle_count_rule_turnover(self, uid, name, values):
|
||||
rule = self.stock_cycle_count_rule_model.with_user(uid).create(
|
||||
@classmethod
|
||||
def _create_stock_cycle_count_rule_turnover(cls, uid, name, values):
|
||||
rule = cls.stock_cycle_count_rule_model.with_user(uid).create(
|
||||
{
|
||||
"name": name,
|
||||
"rule_type": "turnover",
|
||||
@@ -118,8 +116,9 @@ class TestStockCycleCount(common.TransactionCase):
|
||||
)
|
||||
return rule
|
||||
|
||||
def _create_stock_cycle_count_rule_accuracy(self, uid, name, values, zone_ids):
|
||||
rule = self.stock_cycle_count_rule_model.with_user(uid).create(
|
||||
@classmethod
|
||||
def _create_stock_cycle_count_rule_accuracy(cls, uid, name, values, zone_ids):
|
||||
rule = cls.stock_cycle_count_rule_model.with_user(uid).create(
|
||||
{
|
||||
"name": name,
|
||||
"rule_type": "accuracy",
|
||||
@@ -130,8 +129,9 @@ class TestStockCycleCount(common.TransactionCase):
|
||||
)
|
||||
return rule
|
||||
|
||||
def _create_stock_cycle_count_rule_zero(self, uid, name):
|
||||
rule = self.stock_cycle_count_rule_model.with_user(uid).create(
|
||||
@classmethod
|
||||
def _create_stock_cycle_count_rule_zero(cls, uid, name):
|
||||
rule = cls.stock_cycle_count_rule_model.with_user(uid).create(
|
||||
{"name": name, "rule_type": "zero"}
|
||||
)
|
||||
return rule
|
||||
@@ -263,7 +263,6 @@ class TestStockCycleCount(common.TransactionCase):
|
||||
for r in rules:
|
||||
r._compute_rule_description()
|
||||
self.assertTrue(r.rule_description, "No description provided")
|
||||
self.rule_accuracy._onchange_locaton_ids()
|
||||
self.assertEqual(
|
||||
self.rule_accuracy.warehouse_ids.ids,
|
||||
self.big_wh.ids,
|
||||
|
||||
Reference in New Issue
Block a user