[MOV] stock: orderpoint own file

Move the code from stock warehouse to its own file.

[MOV] stock: adapt warehouse and orderpoint files
This commit is contained in:
Arnold Moyaux
2020-01-09 12:17:38 +00:00
parent 709063442e
commit d81d12daaa
3 changed files with 150 additions and 143 deletions
+1
View File
@@ -10,6 +10,7 @@ from . import stock_inventory
from . import stock_location
from . import stock_move
from . import stock_move_line
from . import stock_orderpoint
from . import stock_production_lot
from . import stock_picking
from . import stock_quant
+148
View File
@@ -0,0 +1,148 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime
from dateutil import relativedelta
from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.osv import expression
class StockWarehouseOrderpoint(models.Model):
""" Defines Minimum stock rules. """
_name = "stock.warehouse.orderpoint"
_description = "Minimum Inventory Rule"
_check_company_auto = True
@api.model
def default_get(self, fields):
res = super().default_get(fields)
warehouse = None
if 'warehouse_id' not in res and res.get('company_id'):
warehouse = self.env['stock.warehouse'].search([('company_id', '=', res['company_id'])], limit=1)
if warehouse:
res['warehouse_id'] = warehouse.id
res['location_id'] = warehouse.lot_stock_id.id
return res
name = fields.Char(
'Name', copy=False, required=True, readonly=True,
default=lambda self: self.env['ir.sequence'].next_by_code('stock.orderpoint'))
active = fields.Boolean(
'Active', default=True,
help="If the active field is set to False, it will allow you to hide the orderpoint without removing it.")
warehouse_id = fields.Many2one(
'stock.warehouse', 'Warehouse',
check_company=True, ondelete="cascade", required=True)
location_id = fields.Many2one(
'stock.location', 'Location',
ondelete="cascade", required=True, check_company=True)
product_id = fields.Many2one(
'product.product', 'Product',
domain="[('type', '=', 'product'), '|', ('company_id', '=', False), ('company_id', '=', company_id)]", ondelete='cascade', required=True, check_company=True)
product_uom = fields.Many2one(
'uom.uom', 'Unit of Measure', related='product_id.uom_id')
product_uom_name = fields.Char(string='Product unit of measure label', related='product_uom.display_name', readonly=True)
product_min_qty = fields.Float(
'Minimum Quantity', digits='Product Unit of Measure', required=True,
help="When the virtual stock equals to or goes below the Min Quantity specified for this field, Odoo generates "
"a procurement to bring the forecasted quantity to the Max Quantity.")
product_max_qty = fields.Float(
'Maximum Quantity', digits='Product Unit of Measure', required=True,
help="When the virtual stock goes below the Min Quantity, Odoo generates "
"a procurement to bring the forecasted quantity to the Quantity specified as Max Quantity.")
qty_multiple = fields.Float(
'Qty Multiple', digits='Product Unit of Measure',
default=1, required=True,
help="The procurement quantity will be rounded up to this multiple. If it is 0, the exact quantity will be used.")
group_id = fields.Many2one(
'procurement.group', 'Procurement Group', copy=False,
help="Moves created through this orderpoint will be put in this procurement group. If none is given, the moves generated by stock rules will be grouped into one big picking.")
company_id = fields.Many2one(
'res.company', 'Company', required=True, index=True,
default=lambda self: self.env.company)
lead_days = fields.Integer(
'Lead Time', default=1,
help="Number of days after the orderpoint is triggered to receive the products or to order to the vendor")
lead_type = fields.Selection(
[('net', 'Days to get the products'), ('supplier', 'Days to purchase')], 'Lead Type',
required=True, default='supplier')
allowed_location_ids = fields.One2many(comodel_name='stock.location', compute='_compute_allowed_location_ids')
_sql_constraints = [
('qty_multiple_check', 'CHECK( qty_multiple >= 0 )', 'Qty Multiple must be greater than or equal to zero.'),
]
@api.depends('warehouse_id')
def _compute_allowed_location_ids(self):
loc_domain = [('usage', 'in', ('internal', 'view'))]
# We want to keep only the locations
# - strictly belonging to our warehouse
# - not belonging to any warehouses
for orderpoint in self:
other_warehouses = self.env['stock.warehouse'].search([('id', '!=', orderpoint.warehouse_id.id)])
for view_location_id in other_warehouses.mapped('view_location_id'):
loc_domain = expression.AND([loc_domain, ['!', ('id', 'child_of', view_location_id.id)]])
loc_domain = expression.AND([loc_domain, ['|', ('company_id', '=', False), ('company_id', '=', orderpoint.company_id.id)]])
orderpoint.allowed_location_ids = self.env['stock.location'].search(loc_domain)
def _quantity_in_progress(self):
"""Return Quantities that are not yet in virtual stock but should be deduced from orderpoint rule
(example: purchases created from orderpoints)"""
return dict(self.mapped(lambda x: (x.id, 0.0)))
@api.constrains('product_id')
def _check_product_uom(self):
''' Check if the UoM has the same category as the product standard UoM '''
if any(orderpoint.product_id.uom_id.category_id != orderpoint.product_uom.category_id for orderpoint in self):
raise ValidationError(_('You have to select a product unit of measure that is in the same category than the default unit of measure of the product'))
@api.onchange('warehouse_id')
def onchange_warehouse_id(self):
""" Finds location id for changed warehouse. """
if self.warehouse_id:
self.location_id = self.warehouse_id.lot_stock_id.id
@api.onchange('product_id')
def _onchange_product_id(self):
if self.product_id:
self.product_uom = self.product_id.uom_id.id
@api.onchange('company_id')
def _onchange_company_id(self):
if self.company_id:
self.warehouse_id = self.env['stock.warehouse'].search([
('company_id', '=', self.company_id.id)
], limit=1)
def write(self, vals):
if 'company_id' in vals:
for orderpoint in self:
if orderpoint.company_id.id != vals['company_id']:
raise UserError(_("Changing the company of this record is forbidden at this point, you should rather archive it and create a new one."))
return super().write(vals)
def _get_date_planned(self, product_qty, start_date):
days = self.lead_days or 0.0
if self.lead_type == 'supplier':
# These days will be substracted when creating the PO
days += self.product_id._select_seller(
quantity=product_qty,
date=fields.Date.context_today(self,start_date),
uom_id=self.product_uom).delay or 0.0
date_planned = start_date + relativedelta.relativedelta(days=days)
return date_planned.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
def _prepare_procurement_values(self, product_qty, date=False, group=False):
""" Prepare specific key for moves or other components that will be created from a stock rule
comming from an orderpoint. This method could be override in order to add other custom key that could
be used in move/po creation.
"""
return {
'date_planned': date or self._get_date_planned(product_qty, datetime.today()),
'warehouse_id': self.warehouse_id,
'orderpoint_id': self,
'group_id': group or self.group_id,
}
+1 -143
View File
@@ -2,13 +2,9 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import namedtuple
from datetime import datetime
from dateutil import relativedelta
from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.osv import expression
from odoo.exceptions import UserError
import logging
@@ -957,141 +953,3 @@ class Warehouse(models.Model):
'limit': 20,
'context': dict(self._context, default_warehouse_selectable=True, default_warehouse_ids=self.ids)
}
class Orderpoint(models.Model):
""" Defines Minimum stock rules. """
_name = "stock.warehouse.orderpoint"
_description = "Minimum Inventory Rule"
_check_company_auto = True
@api.model
def default_get(self, fields):
res = super(Orderpoint, self).default_get(fields)
warehouse = None
if 'warehouse_id' not in res and res.get('company_id'):
warehouse = self.env['stock.warehouse'].search([('company_id', '=', res['company_id'])], limit=1)
if warehouse:
res['warehouse_id'] = warehouse.id
res['location_id'] = warehouse.lot_stock_id.id
return res
name = fields.Char(
'Name', copy=False, required=True, readonly=True,
default=lambda self: self.env['ir.sequence'].next_by_code('stock.orderpoint'))
active = fields.Boolean(
'Active', default=True,
help="If the active field is set to False, it will allow you to hide the orderpoint without removing it.")
warehouse_id = fields.Many2one(
'stock.warehouse', 'Warehouse',
check_company=True, ondelete="cascade", required=True)
location_id = fields.Many2one(
'stock.location', 'Location',
ondelete="cascade", required=True, check_company=True)
product_id = fields.Many2one(
'product.product', 'Product',
domain="[('type', '=', 'product'), '|', ('company_id', '=', False), ('company_id', '=', company_id)]", ondelete='cascade', required=True, check_company=True)
product_uom = fields.Many2one(
'uom.uom', 'Unit of Measure', related='product_id.uom_id')
product_uom_name = fields.Char(string='Product unit of measure label', related='product_uom.display_name', readonly=True)
product_min_qty = fields.Float(
'Minimum Quantity', digits='Product Unit of Measure', required=True,
help="When the virtual stock equals to or goes below the Min Quantity specified for this field, Odoo generates "
"a procurement to bring the forecasted quantity to the Max Quantity.")
product_max_qty = fields.Float(
'Maximum Quantity', digits='Product Unit of Measure', required=True,
help="When the virtual stock goes below the Min Quantity, Odoo generates "
"a procurement to bring the forecasted quantity to the Quantity specified as Max Quantity.")
qty_multiple = fields.Float(
'Qty Multiple', digits='Product Unit of Measure',
default=1, required=True,
help="The procurement quantity will be rounded up to this multiple. If it is 0, the exact quantity will be used.")
group_id = fields.Many2one(
'procurement.group', 'Procurement Group', copy=False,
help="Moves created through this orderpoint will be put in this procurement group. If none is given, the moves generated by stock rules will be grouped into one big picking.")
company_id = fields.Many2one(
'res.company', 'Company', required=True, index=True,
default=lambda self: self.env.company)
lead_days = fields.Integer(
'Lead Time', default=1,
help="Number of days after the orderpoint is triggered to receive the products or to order to the vendor")
lead_type = fields.Selection(
[('net', 'Days to get the products'), ('supplier', 'Days to purchase')], 'Lead Type',
required=True, default='supplier')
allowed_location_ids = fields.One2many(comodel_name='stock.location', compute='_compute_allowed_location_ids')
_sql_constraints = [
('qty_multiple_check', 'CHECK( qty_multiple >= 0 )', 'Qty Multiple must be greater than or equal to zero.'),
]
@api.depends('warehouse_id')
def _compute_allowed_location_ids(self):
loc_domain = [('usage', 'in', ('internal', 'view'))]
# We want to keep only the locations
# - strictly belonging to our warehouse
# - not belonging to any warehouses
for orderpoint in self:
other_warehouses = self.env['stock.warehouse'].search([('id', '!=', orderpoint.warehouse_id.id)])
for view_location_id in other_warehouses.mapped('view_location_id'):
loc_domain = expression.AND([loc_domain, ['!', ('id', 'child_of', view_location_id.id)]])
loc_domain = expression.AND([loc_domain, ['|', ('company_id', '=', False), ('company_id', '=', orderpoint.company_id.id)]])
orderpoint.allowed_location_ids = self.env['stock.location'].search(loc_domain)
def _quantity_in_progress(self):
"""Return Quantities that are not yet in virtual stock but should be deduced from orderpoint rule
(example: purchases created from orderpoints)"""
return dict(self.mapped(lambda x: (x.id, 0.0)))
@api.constrains('product_id')
def _check_product_uom(self):
''' Check if the UoM has the same category as the product standard UoM '''
if any(orderpoint.product_id.uom_id.category_id != orderpoint.product_uom.category_id for orderpoint in self):
raise ValidationError(_('You have to select a product unit of measure that is in the same category than the default unit of measure of the product'))
@api.onchange('warehouse_id')
def onchange_warehouse_id(self):
""" Finds location id for changed warehouse. """
if self.warehouse_id:
self.location_id = self.warehouse_id.lot_stock_id.id
@api.onchange('product_id')
def _onchange_product_id(self):
if self.product_id:
self.product_uom = self.product_id.uom_id.id
@api.onchange('company_id')
def _onchange_company_id(self):
if self.company_id:
self.warehouse_id = self.env['stock.warehouse'].search([
('company_id', '=', self.company_id.id)
], limit=1)
def write(self, vals):
if 'company_id' in vals:
for orderpoint in self:
if orderpoint.company_id.id != vals['company_id']:
raise UserError(_("Changing the company of this record is forbidden at this point, you should rather archive it and create a new one."))
return super(Orderpoint, self).write(vals)
def _get_date_planned(self, product_qty, start_date):
days = self.lead_days or 0.0
if self.lead_type == 'supplier':
# These days will be substracted when creating the PO
days += self.product_id._select_seller(
quantity=product_qty,
date=fields.Date.context_today(self,start_date),
uom_id=self.product_uom).delay or 0.0
date_planned = start_date + relativedelta.relativedelta(days=days)
return date_planned.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
def _prepare_procurement_values(self, product_qty, date=False, group=False):
""" Prepare specific key for moves or other components that will be created from a stock rule
comming from an orderpoint. This method could be override in order to add other custom key that could
be used in move/po creation.
"""
return {
'date_planned': date or self._get_date_planned(product_qty, datetime.today()),
'warehouse_id': self.warehouse_id,
'orderpoint_id': self,
'group_id': group or self.group_id,
}