first commit
This commit is contained in:
200
modules/stock/period.py
Normal file
200
modules/stock/period.py
Normal file
@@ -0,0 +1,200 @@
|
||||
# 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
|
||||
|
||||
from sql import For, Literal
|
||||
|
||||
from trytond.i18n import gettext
|
||||
from trytond.model import Index, ModelSQL, ModelView, Workflow, fields
|
||||
from trytond.pool import Pool
|
||||
from trytond.pyson import Eval
|
||||
from trytond.tools import grouped_slice
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
from .exceptions import PeriodCloseError
|
||||
|
||||
|
||||
class Period(Workflow, ModelSQL, ModelView):
|
||||
__name__ = 'stock.period'
|
||||
date = fields.Date('Date', required=True, states={
|
||||
'readonly': Eval('state') == 'closed',
|
||||
},
|
||||
help="When the stock period ends.")
|
||||
company = fields.Many2One(
|
||||
'company.company', "Company", required=True,
|
||||
help="The company the stock period is associated with.")
|
||||
caches = fields.One2Many('stock.period.cache', 'period', 'Caches',
|
||||
readonly=True)
|
||||
state = fields.Selection([
|
||||
('draft', 'Draft'),
|
||||
('closed', 'Closed'),
|
||||
], "State", readonly=True, sort=False,
|
||||
help="The current state of the stock period.")
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
t = cls.__table__()
|
||||
cls._sql_indexes.add(
|
||||
Index(
|
||||
t,
|
||||
(t.company, Index.Range()),
|
||||
(t.date, Index.Range(order='DESC')),
|
||||
where=t.state == 'closed'))
|
||||
cls._transitions |= set((
|
||||
('draft', 'closed'),
|
||||
('closed', 'draft'),
|
||||
))
|
||||
cls._buttons.update({
|
||||
'draft': {
|
||||
'invisible': Eval('state') == 'draft',
|
||||
'depends': ['state'],
|
||||
},
|
||||
'close': {
|
||||
'invisible': Eval('state') == 'closed',
|
||||
'depends': ['state'],
|
||||
},
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def default_company():
|
||||
return Transaction().context.get('company')
|
||||
|
||||
@staticmethod
|
||||
def default_state():
|
||||
return 'draft'
|
||||
|
||||
@staticmethod
|
||||
def groupings():
|
||||
return [('product',)]
|
||||
|
||||
@staticmethod
|
||||
def get_cache(grouping):
|
||||
pool = Pool()
|
||||
if all(g == 'product' or g.startswith('product.') for g in grouping):
|
||||
return pool.get('stock.period.cache')
|
||||
|
||||
def get_rec_name(self, name):
|
||||
return Pool().get('ir.lang').get().strftime(self.date)
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@Workflow.transition('draft')
|
||||
def draft(cls, periods):
|
||||
for grouping in cls.groupings():
|
||||
Cache = cls.get_cache(grouping)
|
||||
caches = []
|
||||
for sub_periods in grouped_slice(periods):
|
||||
caches.append(Cache.search([
|
||||
('period', 'in',
|
||||
[p.id for p in sub_periods]),
|
||||
], order=[]))
|
||||
Cache.delete(list(chain(*caches)))
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@Workflow.transition('closed')
|
||||
def close(cls, periods):
|
||||
pool = Pool()
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
locations = Location.search([
|
||||
('type', 'not in', ['warehouse', 'view']),
|
||||
], order=[])
|
||||
|
||||
for company, c_periods in groupby(periods, key=lambda p: p.company):
|
||||
c_periods = list(c_periods)
|
||||
with Transaction().set_context(company=company.id):
|
||||
today = Date.today()
|
||||
recent_period = max(c_periods, key=lambda p: p.date)
|
||||
recent_date = recent_period.date
|
||||
if recent_date >= today:
|
||||
raise PeriodCloseError(
|
||||
gettext('stock.msg_period_close_date'))
|
||||
if assigned_moves := Move.search([
|
||||
('company', '=', company.id),
|
||||
('state', '=', 'assigned'),
|
||||
['OR', [
|
||||
('effective_date', '=', None),
|
||||
('planned_date', '<=', recent_date),
|
||||
],
|
||||
('effective_date', '<=', recent_date),
|
||||
]],
|
||||
limit=6):
|
||||
names = ', '.join(m.rec_name for m in assigned_moves[:5])
|
||||
if len(assigned_moves) > 5:
|
||||
names += '...'
|
||||
raise PeriodCloseError(
|
||||
gettext('stock.msg_period_close_assigned_move',
|
||||
period=recent_period.rec_name,
|
||||
moves=names))
|
||||
|
||||
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)
|
||||
for key, quantity in pbl.items():
|
||||
if quantity:
|
||||
values = {
|
||||
'location': key[0],
|
||||
'period': period.id,
|
||||
'internal_quantity': quantity,
|
||||
}
|
||||
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):
|
||||
"It is used to store cached computation of stock quantities"
|
||||
__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.Range()),
|
||||
(t.location, Index.Range()),
|
||||
(t.product, Index.Range()),
|
||||
include=[t.internal_quantity]),
|
||||
Index(
|
||||
t,
|
||||
(t.location, Index.Range())),
|
||||
})
|
||||
Reference in New Issue
Block a user