Files
tryton/modules/stock/period.py
T

199 lines
6.8 KiB
Python
Raw Normal View History

2014-12-02 12:56:04 +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.
from itertools import chain, groupby
2021-12-11 17:58:44 +01:00
from sql import For, Literal
2018-12-29 14:20:29 +01:00
from trytond.i18n import gettext
from trytond.model import Index, ModelSQL, ModelView, Workflow, fields
2011-07-03 13:19:14 +02:00
from trytond.pool import Pool
2021-12-11 17:58:44 +01:00
from trytond.pyson import Eval
2014-07-17 19:00:19 +02:00
from trytond.tools import grouped_slice
2021-12-11 17:58:44 +01:00
from trytond.transaction import Transaction
2018-12-29 14:20:29 +01:00
from .exceptions import PeriodCloseError
2013-06-21 23:24:17 +02:00
class Period(Workflow, ModelSQL, ModelView):
'Stock Period'
2012-09-24 22:18:17 +02:00
__name__ = 'stock.period'
date = fields.Date('Date', required=True, states={
2015-06-28 23:45:31 +02:00
'readonly': Eval('state') == 'closed',
},
2018-12-04 12:25:45 +01:00
help="When the stock period ends.")
2021-04-20 09:18:58 +02:00
company = fields.Many2One(
'company.company', "Company", required=True,
2018-12-04 12:25:45 +01:00
help="The company the stock period is associated with.")
2013-06-21 23:24:17 +02:00
caches = fields.One2Many('stock.period.cache', 'period', 'Caches',
readonly=True)
state = fields.Selection([
('draft', 'Draft'),
('closed', 'Closed'),
], "State", readonly=True, sort=False,
2018-12-04 12:25:45 +01:00
help="The current state of the stock period.")
2012-09-24 22:18:17 +02:00
@classmethod
def __setup__(cls):
super(Period, cls).__setup__()
t = cls.__table__()
cls._sql_indexes.add(
Index(
t,
(t.company, Index.Equality()),
(t.date, Index.Range(order='DESC')),
where=t.state == 'closed'))
2013-06-21 23:24:17 +02:00
cls._transitions |= set((
('draft', 'closed'),
('closed', 'draft'),
))
2012-09-24 22:18:17 +02:00
cls._buttons.update({
2012-03-25 00:18:19 +01:00
'draft': {
'invisible': Eval('state') == 'draft',
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
'close': {
'invisible': Eval('state') == 'closed',
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
})
2015-01-16 16:14:32 +01:00
@staticmethod
def default_company():
return Transaction().context.get('company')
2012-12-05 15:12:50 +01:00
@staticmethod
def default_state():
return 'draft'
2013-06-21 23:24:17 +02:00
@staticmethod
def groupings():
return [('product',)]
@staticmethod
def get_cache(grouping):
pool = Pool()
if all(g == 'product' or g.startswith('product.') for g in grouping):
2013-06-21 23:24:17 +02:00
return pool.get('stock.period.cache')
2017-07-26 23:43:27 +02:00
def get_rec_name(self, name):
return str(self.date)
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
2013-06-21 23:24:17 +02:00
@Workflow.transition('draft')
def draft(cls, periods):
for grouping in cls.groupings():
Cache = cls.get_cache(grouping)
caches = []
2014-07-17 19:00:19 +02:00
for sub_periods in grouped_slice(periods):
2013-06-21 23:24:17 +02:00
caches.append(Cache.search([
2013-10-05 12:17:26 +02:00
('period', 'in',
2014-07-17 19:00:19 +02:00
[p.id for p in sub_periods]),
2013-06-21 23:24:17 +02:00
], order=[]))
Cache.delete(list(chain(*caches)))
@classmethod
@ModelView.button
@Workflow.transition('closed')
2012-09-24 22:18:17 +02:00
def close(cls, periods):
2011-07-03 13:19:14 +02:00
pool = Pool()
2012-09-24 22:18:17 +02:00
Product = pool.get('product.product')
Location = pool.get('stock.location')
Move = pool.get('stock.move')
Date = pool.get('ir.date')
transaction = Transaction()
connection = transaction.connection
database = transaction.database
# XXX: A move in the period could be inserted before the lock
# from a different transaction. It will not be taken in the pbl
# computation but it is quite rare because only past periods are
# closed.
2021-10-18 01:22:26 +02:00
Move.lock()
if database.has_select_for():
move = Move.__table__()
query = move.select(Literal(1), for_=For('UPDATE', nowait=True))
with connection.cursor() as cursor:
cursor.execute(*query)
2012-09-24 22:18:17 +02:00
locations = Location.search([
('type', 'not in', ['warehouse', 'view']),
], order=[])
for company, c_periods in groupby(periods, key=lambda p: p.company):
with Transaction().set_context(company=company.id):
today = Date.today()
recent_date = max(period.date for period in c_periods)
if recent_date >= today:
raise PeriodCloseError(
gettext('stock.msg_period_close_date'))
2012-09-24 22:18:17 +02:00
if Move.search([
('state', '=', 'assigned'),
['OR', [
('effective_date', '=', None),
('planned_date', '<=', recent_date),
],
('effective_date', '<=', recent_date),
]]):
2018-12-29 14:20:29 +01:00
raise PeriodCloseError(
gettext('stock.msg_period_close_assigned_move'))
2013-06-21 23:24:17 +02:00
for grouping in cls.groupings():
Cache = cls.get_cache(grouping)
to_create = []
for period in periods:
with Transaction().set_context(
stock_date_end=period.date,
stock_date_start=None,
stock_assign=False,
forecast=False,
stock_destinations=None,
):
pbl = Product.products_by_location(
[l.id for l in locations], grouping=grouping)
2018-06-26 19:11:55 +02:00
for key, quantity in pbl.items():
2013-06-21 23:24:17 +02:00
values = {
'location': key[0],
2012-09-24 22:18:17 +02:00
'period': period.id,
'internal_quantity': quantity,
2013-06-21 23:24:17 +02:00
}
for i, field in enumerate(grouping, 1):
values[field] = key[i]
to_create.append(values)
if to_create:
Cache.create(to_create)
class Cache(ModelSQL, ModelView):
'''
Stock Period Cache
It is used to store cached computation of stock quantities.
'''
2012-09-24 22:18:17 +02:00
__name__ = 'stock.period.cache'
period = fields.Many2One(
'stock.period', "Period",
required=True, readonly=True, ondelete='CASCADE')
location = fields.Many2One(
'stock.location', "Location",
required=True, readonly=True, ondelete='CASCADE')
product = fields.Many2One(
'product.product', "Product",
required=True, readonly=True, ondelete='CASCADE')
internal_quantity = fields.Float('Internal Quantity', readonly=True)
@classmethod
def __setup__(cls):
super().__setup__()
t = cls.__table__()
cls._sql_indexes.update({
Index(
t,
(t.period, Index.Equality()),
(t.product, Index.Equality()),
include=[t.internal_quantity]),
Index(
t,
(t.location, Index.Equality())),
})