Files
tryton/modules/stock_supply/order_point.py
T

234 lines
8.2 KiB
Python
Raw Normal View History

2014-12-02 12:56:15 +01:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
2018-12-29 14:20:30 +01:00
from trytond.i18n import gettext
2021-12-11 17:58:44 +01:00
from trytond.model import ModelSQL, ModelView, fields
2020-04-06 12:58:34 +02:00
from trytond.pool import Pool
2021-12-11 17:58:44 +01:00
from trytond.pyson import Equal, Eval, If, In, Not
2010-08-10 11:58:54 -04:00
from trytond.transaction import Transaction
2008-09-04 23:27:49 +02:00
2018-12-29 14:20:30 +01:00
from .exceptions import OrderPointValidationError
2008-10-18 01:20:35 +02:00
2009-02-28 17:06:35 +01:00
class OrderPoint(ModelSQL, ModelView):
2008-09-04 23:27:49 +02:00
"""
2012-09-24 22:18:24 +02:00
Order Point
Provide a way to define a supply policy for each
2008-09-04 23:27:49 +02:00
product on each locations. Order points on warehouse are
2008-09-08 14:56:13 +02:00
considered by the supply scheduler to generate purchase requests.
2008-09-04 23:27:49 +02:00
"""
2012-09-24 22:18:24 +02:00
__name__ = 'stock.order_point'
product = fields.Many2One(
'product.product', "Product", required=True,
2010-01-20 11:05:20 +01:00
domain=[
('type', '=', 'goods'),
('consumable', '=', False),
2010-01-20 11:05:20 +01:00
('purchasable', 'in', If(Equal(Eval('type'), 'purchase'),
2011-08-04 16:57:45 +02:00
[True], [True, False])),
],
context={
'company': Eval('company', -1),
},
depends={'company'})
warehouse_location = fields.Many2One(
'stock.location', 'Warehouse Location',
2009-06-23 18:49:38 +02:00
domain=[('type', '=', 'warehouse')],
2010-01-20 11:05:20 +01:00
states={
'invisible': Not(Equal(Eval('type'), 'purchase')),
'required': Equal(Eval('type'), 'purchase'),
})
storage_location = fields.Many2One(
'stock.location', "Storage Location",
2009-06-23 18:49:38 +02:00
domain=[('type', '=', 'storage')],
2010-01-20 11:05:20 +01:00
states={
'invisible': Not(Equal(Eval('type'), 'internal')),
'required': Equal(Eval('type'), 'internal'),
})
2010-03-11 16:54:57 +01:00
location = fields.Function(fields.Many2One('stock.location', 'Location'),
'get_location', searcher='search_location')
2008-10-18 01:20:35 +02:00
provisioning_location = fields.Many2One(
'stock.location', 'Provisioning Location',
2016-04-21 17:23:46 +02:00
domain=[('type', 'in', ['storage', 'view'])],
2010-01-20 11:05:20 +01:00
states={
'invisible': Not(Equal(Eval('type'), 'internal')),
2017-03-01 10:16:07 +01:00
'required': ((Eval('type') == 'internal')
2020-03-01 12:33:54 +01:00
& (Eval('min_quantity', None) != None)), # noqa: E711
})
2017-03-01 10:16:07 +01:00
overflowing_location = fields.Many2One(
'stock.location', 'Overflowing Location',
domain=[('type', 'in', ['storage', 'view'])],
states={
'invisible': Eval('type') != 'internal',
'required': ((Eval('type') == 'internal')
2020-03-01 12:33:54 +01:00
& (Eval('max_quantity', None) != None)), # noqa: E711
})
2008-10-18 01:20:35 +02:00
type = fields.Selection(
[('internal', 'Internal'),
('purchase', 'Purchase')],
"Type", required=True)
2021-08-30 00:27:14 +02:00
min_quantity = fields.Float(
"Minimal Quantity", digits='unit',
states={
# required for purchase and production types
'required': Eval('type') != 'internal',
},
2017-03-01 10:16:07 +01:00
domain=['OR',
('min_quantity', '=', None),
('min_quantity', '<=', Eval('target_quantity', 0)),
])
2021-08-30 00:27:14 +02:00
target_quantity = fields.Float(
"Target Quantity", digits='unit', required=True,
2017-03-01 10:16:07 +01:00
domain=[
['OR',
('min_quantity', '=', None),
('target_quantity', '>=', Eval('min_quantity', 0)),
],
['OR',
('max_quantity', '=', None),
('target_quantity', '<=', Eval('max_quantity', 0)),
],
])
2021-08-30 00:27:14 +02:00
max_quantity = fields.Float(
"Maximal Quantity", digits='unit',
2017-03-01 10:16:07 +01:00
states={
'invisible': Eval('type') != 'internal',
},
domain=['OR',
('max_quantity', '=', None),
('max_quantity', '>=', Eval('target_quantity', 0)),
])
company = fields.Many2One('company.company', 'Company', required=True,
2010-01-20 11:05:20 +01:00
domain=[
('id', If(In('company', Eval('context', {})), '=', '!='),
2013-10-09 18:25:59 +02:00
Eval('context', {}).get('company', -1)),
2010-01-20 11:05:20 +01:00
])
2010-03-11 16:54:57 +01:00
unit = fields.Function(fields.Many2One('product.uom', 'Unit'), 'get_unit')
2008-09-04 23:27:49 +02:00
2012-09-24 22:18:24 +02:00
@staticmethod
def default_type():
2008-10-18 01:20:35 +02:00
return "purchase"
@classmethod
def default_warehouse_location(cls):
return Pool().get('stock.location').get_default_warehouse()
2019-10-16 21:23:26 +02:00
@fields.depends('product', '_parent_product.default_uom')
2012-09-24 22:18:24 +02:00
def on_change_product(self):
2014-10-29 20:40:34 +01:00
self.unit = None
2012-09-24 22:18:24 +02:00
if self.product:
2014-10-29 20:40:34 +01:00
self.unit = self.product.default_uom
2009-03-12 19:02:45 +01:00
2012-09-24 22:18:24 +02:00
def get_unit(self, name):
return self.product.default_uom.id
2009-03-12 19:02:45 +01:00
@classmethod
def validate(cls, orderpoints):
super(OrderPoint, cls).validate(orderpoints)
cls.check_concurrent_internal(orderpoints)
cls.check_uniqueness(orderpoints)
2012-09-24 22:18:24 +02:00
@classmethod
def check_concurrent_internal(cls, orders):
2008-10-18 01:20:35 +02:00
"""
Ensure that there is no 'concurrent' internal order
points. I.E. no two order point with opposite location for the
same product and same company.
"""
internals = cls.browse([o for o in orders if o.type == 'internal'])
2012-09-24 22:18:24 +02:00
if not internals:
return
2008-10-18 01:20:35 +02:00
2017-03-01 10:16:07 +01:00
for location_name in [
'provisioning_location', 'overflowing_location']:
query = []
for op in internals:
if getattr(op, location_name, None) is None:
continue
arg = ['AND',
('product', '=', op.product.id),
(location_name, '=', op.storage_location.id),
('storage_location', '=',
getattr(op, location_name).id),
('company', '=', op.company.id),
('type', '=', 'internal')]
query.append(arg)
if query and cls.search(['OR'] + query):
2018-12-29 14:20:30 +01:00
raise OrderPointValidationError(
gettext('stock_supply'
'.msg_order_point_concurrent_%s_internal' %
location_name))
2008-10-18 01:20:35 +02:00
2012-09-24 22:18:24 +02:00
@staticmethod
def _type2field(type=None):
2012-04-03 20:07:50 +02:00
t2f = {
'purchase': 'warehouse_location',
'internal': 'storage_location',
}
2013-02-28 22:59:23 +01:00
if type is None:
return t2f
else:
return t2f[type]
2008-10-18 01:20:35 +02:00
2012-09-24 22:18:24 +02:00
@classmethod
def check_uniqueness(cls, orders):
2008-10-18 01:20:35 +02:00
"""
2008-11-05 17:54:28 +01:00
Ensure uniqueness of order points. I.E that there is no several
2008-10-18 01:20:35 +02:00
order point for the same location, the same product and the
same company.
"""
query = ['OR']
2012-09-24 22:18:24 +02:00
for op in orders:
field = cls._type2field(op.type)
2008-10-18 01:20:35 +02:00
arg = ['AND',
2012-04-03 20:07:50 +02:00
('product', '=', op.product.id),
2012-09-24 22:18:24 +02:00
(field, '=', getattr(op, field).id),
2012-04-03 20:07:50 +02:00
('id', '!=', op.id),
('company', '=', op.company.id),
]
2008-10-18 01:20:35 +02:00
query.append(arg)
if cls.search(query):
2018-12-29 14:20:30 +01:00
raise OrderPointValidationError(
gettext('stock_supply.msg_order_point_unique'))
2012-09-24 22:18:24 +02:00
def get_rec_name(self, name):
return "%s @ %s" % (self.product.name, self.location.name)
2012-09-24 22:18:24 +02:00
@classmethod
def search_rec_name(cls, name, clause):
_, operator, value = clause
if operator.startswith('!') or operator.startswith('not '):
bool_op = 'AND'
else:
bool_op = 'OR'
return [bool_op,
('location.rec_name', *clause[1:]),
('product.rec_name', *clause[1:]),
]
2008-10-21 12:49:04 +02:00
2012-09-24 22:18:24 +02:00
def get_location(self, name):
if self.type == 'purchase':
return self.warehouse_location.id
elif self.type == 'internal':
return self.storage_location.id
2008-10-18 01:20:35 +02:00
2012-09-24 22:18:24 +02:00
@classmethod
def search_location(cls, name, domain=None):
2019-03-15 17:49:32 +01:00
clauses = ['OR']
2018-06-26 19:11:57 +02:00
for type, field in cls._type2field().items():
2019-03-15 17:49:32 +01:00
clauses.append([
('type', '=', type),
(field,) + tuple(domain[1:]),
])
return clauses
2008-10-18 01:20:35 +02:00
2012-09-24 22:18:24 +02:00
@staticmethod
def default_company():
2012-03-27 10:44:10 +02:00
return Transaction().context.get('company')
2020-04-06 12:58:34 +02:00
@classmethod
def supply_stock(cls):
pool = Pool()
StockSupply = pool.get('stock.supply', type='wizard')
session_id, _, _ = StockSupply.create()
StockSupply.execute(session_id, {}, 'create_')
StockSupply.delete(session_id)