mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
[10.0][IMP] fix api issues and clean up code
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# Copyright 2017-18 Eficent Business and IT Consulting Services S.L.
|
||||
# (http://www.eficent.com)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
@@ -13,7 +13,7 @@ class StockCycleCount(models.Model):
|
||||
_inherit = 'mail.thread'
|
||||
|
||||
@api.multi
|
||||
def _count_inventory_adj(self):
|
||||
def _compute_inventory_adj_count(self):
|
||||
for rec in self:
|
||||
rec.inventory_adj_count = len(rec.stock_adjustment_ids)
|
||||
|
||||
@@ -53,7 +53,8 @@ class StockCycleCount(models.Model):
|
||||
inverse_name='cycle_count_id',
|
||||
string='Inventory Adjustment',
|
||||
track_visibility='onchange')
|
||||
inventory_adj_count = fields.Integer(compute='_count_inventory_adj')
|
||||
inventory_adj_count = fields.Integer(
|
||||
compute='_compute_inventory_adj_count')
|
||||
company_id = fields.Many2one(
|
||||
comodel_name='res.company', string='Company', required=True,
|
||||
default=_company_get, readonly=True)
|
||||
@@ -62,8 +63,9 @@ class StockCycleCount(models.Model):
|
||||
def do_cancel(self):
|
||||
self.write({'state': 'cancelled'})
|
||||
|
||||
@api.model
|
||||
@api.multi
|
||||
def _prepare_inventory_adjustment(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'name': 'INV/{}'.format(self.name),
|
||||
'cycle_count_id': self.id,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# Copyright 2017-18 Eficent Business and IT Consulting Services S.L.
|
||||
# (http://www.eficent.com)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
@@ -13,9 +13,10 @@ class StockCycleCountRule(models.Model):
|
||||
_name = 'stock.cycle.count.rule'
|
||||
_description = "Stock Cycle Counts Rules"
|
||||
|
||||
@api.one
|
||||
def _compute_currency(self):
|
||||
self.currency_id = self.env.user.company_id.currency_id
|
||||
@api.multi
|
||||
def _compute_currency_id(self):
|
||||
for rec in self:
|
||||
rec.currency_id = self.env.user.company_id.currency_id
|
||||
|
||||
@api.model
|
||||
def _selection_rule_types(self):
|
||||
@@ -25,26 +26,27 @@ class StockCycleCountRule(models.Model):
|
||||
('accuracy', _('Minimum Accuracy')),
|
||||
('zero', _('Zero Confirmation'))]
|
||||
|
||||
@api.one
|
||||
@api.multi
|
||||
@api.constrains('rule_type', 'warehouse_ids')
|
||||
def _check_zero_rule(self):
|
||||
if self.rule_type == 'zero' and len(self.warehouse_ids) > 1:
|
||||
raise UserError(
|
||||
_('Zero confirmation rules can only have one warehouse '
|
||||
'assigned.')
|
||||
)
|
||||
if self.rule_type == 'zero':
|
||||
zero_rule = self.search([
|
||||
('rule_type', '=', 'zero'),
|
||||
('warehouse_ids', '=', self.warehouse_ids.id)])
|
||||
if len(zero_rule) > 1:
|
||||
raise UserError(
|
||||
_('You can only have one zero confirmation rule per '
|
||||
'warehouse.')
|
||||
for rec in self:
|
||||
if rec.rule_type == 'zero' and len(rec.warehouse_ids) > 1:
|
||||
raise ValidationError(
|
||||
_('Zero confirmation rules can only have one warehouse '
|
||||
'assigned.')
|
||||
)
|
||||
if rec.rule_type == 'zero':
|
||||
zero_rule = self.search([
|
||||
('rule_type', '=', 'zero'),
|
||||
('warehouse_ids', '=', rec.warehouse_ids.id)])
|
||||
if len(zero_rule) > 1:
|
||||
raise ValidationError(
|
||||
_('You can only have one zero confirmation rule per '
|
||||
'warehouse.')
|
||||
)
|
||||
|
||||
@api.onchange('rule_type')
|
||||
def _get_rule_description(self):
|
||||
def _compute_rule_description(self):
|
||||
if self.rule_type == 'periodic':
|
||||
self.rule_description = _('Ensures that at least a defined number '
|
||||
'of counts in a given period will '
|
||||
@@ -66,17 +68,19 @@ class StockCycleCountRule(models.Model):
|
||||
else:
|
||||
self.rule_description = _('(No description provided.)')
|
||||
|
||||
@api.multi
|
||||
@api.constrains('periodic_qty_per_period', 'periodic_count_period')
|
||||
def _check_negative_periodic(self):
|
||||
if self.periodic_qty_per_period < 1:
|
||||
raise UserError(
|
||||
_('You cannot define a negative or null number of counts per '
|
||||
'period.')
|
||||
)
|
||||
if self.periodic_count_period < 0:
|
||||
raise UserError(
|
||||
_('You cannot define a negative period.')
|
||||
)
|
||||
for rec in self:
|
||||
if rec.periodic_qty_per_period < 1:
|
||||
raise ValidationError(
|
||||
_('You cannot define a negative or null number of counts '
|
||||
'per period.')
|
||||
)
|
||||
if rec.periodic_count_period < 0:
|
||||
raise ValidationError(
|
||||
_('You cannot define a negative period.')
|
||||
)
|
||||
|
||||
@api.onchange('location_ids')
|
||||
def _get_warehouses(self):
|
||||
@@ -87,36 +91,45 @@ class StockCycleCountRule(models.Model):
|
||||
wh_ids = list(set(wh_ids))
|
||||
self.warehouse_ids = self.env['stock.warehouse'].browse(wh_ids)
|
||||
|
||||
name = fields.Char('Name', required=True)
|
||||
rule_type = fields.Selection(selection="_selection_rule_types",
|
||||
string='Type of rule',
|
||||
required=True)
|
||||
rule_description = fields.Char(string='Rule Description',
|
||||
compute='_get_rule_description')
|
||||
active = fields.Boolean(string='Active', default=True)
|
||||
periodic_qty_per_period = fields.Integer(string='Counts per period',
|
||||
default=1)
|
||||
name = fields.Char(required=True)
|
||||
rule_type = fields.Selection(
|
||||
selection="_selection_rule_types",
|
||||
string='Type of rule', required=True,
|
||||
)
|
||||
rule_description = fields.Char(
|
||||
string='Rule Description', 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(
|
||||
string='Turnover Inventory Value Threshold')
|
||||
currency_id = fields.Many2one(comodel_name='res.currency',
|
||||
string='Currency',
|
||||
compute='_compute_currency')
|
||||
accuracy_threshold = fields.Float(string='Minimum Accuracy Threshold',
|
||||
digits=(3, 2))
|
||||
string='Turnover Inventory Value Threshold',
|
||||
)
|
||||
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')
|
||||
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')
|
||||
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')
|
||||
column2='location_id', string='Zones where applied',
|
||||
)
|
||||
|
||||
def compute_rule(self, locs):
|
||||
if self.rule_type == 'periodic':
|
||||
@@ -211,8 +224,9 @@ class StockCycleCountRule(models.Model):
|
||||
cycle_counts.append(cycle_count)
|
||||
return cycle_counts
|
||||
|
||||
@api.model
|
||||
@api.multi
|
||||
def _compute_rule_accuracy(self, locs):
|
||||
self.ensure_one()
|
||||
cycle_counts = []
|
||||
for loc in locs:
|
||||
if loc.loc_accuracy < self.accuracy_threshold:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# Copyright 2017-18 Eficent Business and IT Consulting Services S.L.
|
||||
# (http://www.eficent.com)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
@@ -20,59 +20,67 @@ except (ImportError, IOError) as err:
|
||||
class StockLocation(models.Model):
|
||||
_inherit = 'stock.location'
|
||||
|
||||
@api.one
|
||||
@api.multi
|
||||
def _compute_loc_accuracy(self):
|
||||
history = self.env['stock.inventory'].search([
|
||||
('location_id', '=', self.id), ('state', '=', 'done')])
|
||||
history = history.sorted(key=lambda r: r.write_date, reverse=True)
|
||||
if history:
|
||||
wh = self.get_warehouse()
|
||||
if len(history) > wh.counts_for_accuracy_qty:
|
||||
self.loc_accuracy = mean(history[:wh.counts_for_accuracy_qty].
|
||||
mapped('inventory_accuracy'))
|
||||
else:
|
||||
self.loc_accuracy = mean(history.mapped('inventory_accuracy'))
|
||||
for rec in self:
|
||||
history = self.env['stock.inventory'].search([
|
||||
('location_id', '=', rec.id), ('state', '=', 'done')])
|
||||
history = history.sorted(key=lambda r: r.write_date, reverse=True)
|
||||
if history:
|
||||
wh = rec.get_warehouse()
|
||||
if 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'))
|
||||
|
||||
zero_confirmation_disabled = fields.Boolean(
|
||||
string='Disable Zero Confirmations',
|
||||
default=False,
|
||||
help='Define whether this location will trigger a zero-confirmation '
|
||||
'validation when a rule for its warehouse is defined to perform '
|
||||
'zero-confirmations.')
|
||||
'zero-confirmations.',
|
||||
)
|
||||
cycle_count_disabled = fields.Boolean(
|
||||
string='Exclude from Cycle Count',
|
||||
default=False,
|
||||
help='Define whether the location is going to be cycle counted.')
|
||||
qty_variance_inventory_threshold = fields.Float('Acceptable Inventory '
|
||||
'Quantity Variance '
|
||||
'Threshold')
|
||||
help='Define whether the location is going to be cycle counted.',
|
||||
)
|
||||
qty_variance_inventory_threshold = fields.Float(
|
||||
string='Acceptable Inventory Quantity Variance Threshold',
|
||||
)
|
||||
loc_accuracy = fields.Float(
|
||||
string='Inventory Accuracy', compute='_compute_loc_accuracy',
|
||||
digits=(3, 2))
|
||||
digits=(3, 2),
|
||||
)
|
||||
|
||||
@api.model
|
||||
@api.multi
|
||||
def _get_zero_confirmation_domain(self):
|
||||
self.ensure_one()
|
||||
domain = [('location_id', '=', self.id)]
|
||||
return domain
|
||||
|
||||
@api.one
|
||||
@api.multi
|
||||
def check_zero_confirmation(self):
|
||||
if not self.zero_confirmation_disabled:
|
||||
wh = self.get_warehouse()
|
||||
rule_model = self.env['stock.cycle.count.rule']
|
||||
zero_rule = rule_model.search([
|
||||
('rule_type', '=', 'zero'),
|
||||
('warehouse_ids', '=', wh.id)])
|
||||
if zero_rule:
|
||||
quants = self.env['stock.quant'].search(
|
||||
self._get_zero_confirmation_domain())
|
||||
if not quants:
|
||||
self.create_zero_confirmation_cycle_count()
|
||||
for rec in self:
|
||||
if not rec.zero_confirmation_disabled:
|
||||
wh = rec.get_warehouse()
|
||||
rule_model = self.env['stock.cycle.count.rule']
|
||||
zero_rule = rule_model.search([
|
||||
('rule_type', '=', 'zero'),
|
||||
('warehouse_ids', '=', wh.id)])
|
||||
if zero_rule:
|
||||
quants = self.env['stock.quant'].search(
|
||||
rec._get_zero_confirmation_domain())
|
||||
if not quants:
|
||||
rec.create_zero_confirmation_cycle_count()
|
||||
|
||||
@api.multi
|
||||
def create_zero_confirmation_cycle_count(self):
|
||||
self.ensure_one()
|
||||
date = datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
wh_id = self.get_warehouse().id
|
||||
date_horizon = self.get_warehouse().get_horizon_date()[0].strftime(
|
||||
date_horizon = self.get_warehouse().get_horizon_date().strftime(
|
||||
DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
counts_planned = self.env['stock.cycle.count'].search([
|
||||
('date_deadline', '<', date_horizon), ('state', '=', 'draft'),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# Copyright 2017-18 Eficent Business and IT Consulting Services S.L.
|
||||
# (http://www.eficent.com)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
@@ -30,8 +30,9 @@ class StockWarehouse(models.Model):
|
||||
help='Number of latest inventories used to calculate location '
|
||||
'accuracy')
|
||||
|
||||
@api.one
|
||||
@api.multi
|
||||
def get_horizon_date(self):
|
||||
self.ensure_one()
|
||||
date = datetime.today()
|
||||
delta = timedelta(self.cycle_count_planning_horizon)
|
||||
date_horizon = date + delta
|
||||
@@ -58,60 +59,64 @@ class StockWarehouse(models.Model):
|
||||
self._get_cycle_count_locations_search_domain(loc))
|
||||
return locations
|
||||
|
||||
@api.model
|
||||
@api.multi
|
||||
def _cycle_count_rules_to_compute(self):
|
||||
rules = self.cycle_count_rule_ids.search([
|
||||
('rule_type', '!=', 'zero'), ('warehouse_ids', '=', self.id)])
|
||||
self.ensure_one()
|
||||
rules = self.env['stock.cycle.count.rule'].search([
|
||||
('rule_type', '!=', 'zero'), ('warehouse_ids', 'in', self.ids)])
|
||||
return rules
|
||||
|
||||
@api.one
|
||||
@api.multi
|
||||
def action_compute_cycle_count_rules(self):
|
||||
''' Apply the rule in all the sublocations of a given warehouse(s) and
|
||||
""" Apply the rule in all the sublocations of a given warehouse(s) and
|
||||
returns a list with required dates for the cycle count of each
|
||||
location '''
|
||||
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))
|
||||
if proposed_cycle_counts:
|
||||
locations = list(set([d['location'] for d in
|
||||
proposed_cycle_counts]))
|
||||
for loc in locations:
|
||||
proposed_for_loc = filter(lambda x: x['location'] == loc,
|
||||
proposed_cycle_counts)
|
||||
earliest_date = min([d['date'] for d in proposed_for_loc])
|
||||
cycle_count_proposed = 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]
|
||||
if cycle_count_proposed['date'] < existing_earliest_date:
|
||||
cc_to_update = existing_cycle_counts.search([
|
||||
('date_deadline', '=', existing_earliest_date)])
|
||||
cc_to_update.write({
|
||||
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))
|
||||
if proposed_cycle_counts:
|
||||
locations = list(set([d['location'] for d in
|
||||
proposed_cycle_counts]))
|
||||
for loc in locations:
|
||||
proposed_for_loc = filter(lambda x: x['location'] == loc,
|
||||
proposed_cycle_counts)
|
||||
earliest_date = min([d['date'] for d in proposed_for_loc])
|
||||
cycle_count_proposed = 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]
|
||||
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 = datetime.strptime(
|
||||
cycle_count_proposed['date'],
|
||||
DEFAULT_SERVER_DATETIME_FORMAT) - datetime.today()
|
||||
if not existing_cycle_counts and \
|
||||
delta.days < rec.cycle_count_planning_horizon:
|
||||
self.env['stock.cycle.count'].create({
|
||||
'date_deadline': cycle_count_proposed['date'],
|
||||
'location_id': cycle_count_proposed['location'].id,
|
||||
'cycle_count_rule_id': cycle_count_proposed[
|
||||
'rule_type'].id,
|
||||
'state': 'draft'
|
||||
})
|
||||
delta = datetime.strptime(
|
||||
cycle_count_proposed['date'],
|
||||
DEFAULT_SERVER_DATETIME_FORMAT) - datetime.today()
|
||||
if not existing_cycle_counts and \
|
||||
delta.days < self.cycle_count_planning_horizon:
|
||||
self.env['stock.cycle.count'].create({
|
||||
'date_deadline': cycle_count_proposed['date'],
|
||||
'location_id': cycle_count_proposed['location'].id,
|
||||
'cycle_count_rule_id': cycle_count_proposed[
|
||||
'rule_type'].id,
|
||||
'state': 'draft'
|
||||
})
|
||||
|
||||
@api.model
|
||||
def cron_cycle_count(self):
|
||||
|
||||
@@ -233,7 +233,7 @@ class TestStockCycleCount(common.TransactionCase):
|
||||
self.rule_accuracy,
|
||||
self.zero_rule]
|
||||
for r in rules:
|
||||
r._get_rule_description()
|
||||
r._compute_rule_description()
|
||||
self.assertTrue(r.rule_description, 'No description provided')
|
||||
self.rule_accuracy._get_warehouses()
|
||||
self.assertEqual(self.rule_accuracy.warehouse_ids.ids, self.big_wh.ids,
|
||||
|
||||
Reference in New Issue
Block a user