parent
b331ebbe80
commit
e2d1d2b0b9
|
@ -2,6 +2,7 @@
|
|||
# this repository contains the full copyright notices and license terms.
|
||||
from trytond.model import fields
|
||||
from trytond.pool import PoolMeta
|
||||
from trytond.pyson import Id
|
||||
|
||||
__all__ = ['Configuration']
|
||||
|
||||
|
@ -15,4 +16,4 @@ class Configuration:
|
|||
fields.Many2One('ir.sequence', 'Load unit Sequence', required=True,
|
||||
domain=[('code', '=', 'stock.unit_load'),
|
||||
('name', '=', 'Unit load')])
|
||||
)
|
||||
)
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
copyright notices and license terms. -->
|
||||
<tryton>
|
||||
<data>
|
||||
|
||||
<!-- Configuration form -->
|
||||
<record model="ir.ui.view" id="stock_configuration_view_form">
|
||||
<field name="model">stock.configuration</field>
|
||||
|
@ -37,6 +36,5 @@
|
|||
<field name="field" search="[('model.model', '=', 'stock.configuration'), ('name', '=', 'unit_load_sequence')]"/>
|
||||
<field name="value" eval="'ir.sequence,' + str(ref('sequence_unit_load'))"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
5
stock.py
5
stock.py
|
@ -4,9 +4,12 @@ from trytond.pool import PoolMeta
|
|||
from trytond.model import fields
|
||||
|
||||
__all__ = ['Move']
|
||||
|
||||
__metaclass__ = PoolMeta
|
||||
|
||||
|
||||
class Move:
|
||||
__name__ = 'stock.move'
|
||||
|
||||
unit_load = fields.Many2One('stock.unit_load', 'Unit load', ondelete='RESTRICT', select=True)
|
||||
unit_load = fields.Many2One('stock.unit_load', 'Unit load',
|
||||
ondelete='RESTRICT', select=True)
|
||||
|
|
|
@ -11,6 +11,7 @@ Imports::
|
|||
>>> from trytond.modules.company.tests.tools import create_company, \
|
||||
... get_company
|
||||
>>> today = datetime.date.today()
|
||||
>>> tomorrow = today + relativedelta(days=1)
|
||||
|
||||
Create database::
|
||||
|
||||
|
@ -73,13 +74,18 @@ Add moves::
|
|||
>>> move = unit_load.moves.new()
|
||||
>>> move.planned_date = today
|
||||
>>> move.product = product
|
||||
>>> move.quantity = 1
|
||||
>>> move.quantity = Decimal(35)
|
||||
>>> move.from_location = lost_found_loc
|
||||
>>> move.to_location = storage_loc
|
||||
>>> move.currency = company.currency
|
||||
>>> unit_load.save()
|
||||
>>> unit_load.state
|
||||
u'draft'
|
||||
>>> unit_load.moves[0].state
|
||||
u'draft'
|
||||
|
||||
Check computed fields::
|
||||
|
||||
>>> unit_load.location.code
|
||||
u'STO'
|
||||
>>> len(unit_load.last_moves) == 1
|
||||
|
@ -87,8 +93,13 @@ Add moves::
|
|||
>>> unit_load.click('assign')
|
||||
>>> unit_load.state
|
||||
u'assigned'
|
||||
>>> unit_load.moves[0].state
|
||||
u'assigned'
|
||||
>>> len(unit_load.ul_moves)
|
||||
1
|
||||
>>> unit_load.uom.rec_name
|
||||
u'Unit'
|
||||
>>> unit_load.save()
|
||||
|
||||
Move wizard::
|
||||
|
||||
|
@ -105,6 +116,10 @@ State error::
|
|||
>>> unit_load.click('do')
|
||||
>>> unit_load.state
|
||||
u'done'
|
||||
>>> unit_load.quantity
|
||||
35.0
|
||||
>>> unit_load.forecast_quantity
|
||||
35.0
|
||||
|
||||
Location error::
|
||||
|
||||
|
@ -120,12 +135,12 @@ Date error::
|
|||
Traceback (most recent call last):
|
||||
...
|
||||
UserError: ...
|
||||
>>> move_ul.form.planned_date = today + relativedelta(days=1)
|
||||
>>> move_ul.form.planned_date = tomorrow
|
||||
>>> move_ul.execute('move_')
|
||||
>>> unit_load.reload()
|
||||
>>> unit_load.state
|
||||
u'draft'
|
||||
>>> len(unit_load.moves)
|
||||
2
|
||||
>>> unit_load.last_date == today + relativedelta(days=1)
|
||||
>>> unit_load.last_date == tomorrow
|
||||
True
|
||||
|
|
|
@ -18,7 +18,8 @@ class StockUnitLoadTestCase(ModuleTestCase):
|
|||
def suite():
|
||||
suite = trytond.tests.test_tryton.suite()
|
||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(StockUnitLoadTestCase))
|
||||
suite.addTests(doctest.DocFileSuite('scenario_stock_unit_load.rst',
|
||||
setUp=doctest_setup, tearDown=doctest_teardown, encoding='utf-8',
|
||||
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
|
||||
suite.addTests(doctest.DocFileSuite(
|
||||
'scenario_stock_unit_load.rst',
|
||||
setUp=doctest_setup, tearDown=doctest_teardown, encoding='utf-8',
|
||||
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
|
||||
return suite
|
||||
|
|
123
unit_load.py
123
unit_load.py
|
@ -1,5 +1,6 @@
|
|||
# The COPYRIGHT file at the top level of this repository contains the full
|
||||
# copyright notices and license terms.
|
||||
import datetime
|
||||
from sql import Literal, Null
|
||||
from sql.aggregate import Max
|
||||
from sql.conditionals import Coalesce
|
||||
|
@ -9,6 +10,7 @@ from trytond.pyson import Id, Not, Eval, In, Bool, Or, Equal
|
|||
from trytond.model import fields, ModelView, ModelSQL
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.wizard import Wizard, StateTransition, StateView, Button
|
||||
from trytond.modules.stock import StockMixin
|
||||
|
||||
__all__ = ['UnitLoad', 'UnitLoadMove', 'MoveUnitLoad',
|
||||
'MoveUnitLoadStart']
|
||||
|
@ -19,7 +21,7 @@ UL_STATES = {'readonly': Bool(Eval('moves', [0]))}
|
|||
UL_DEPENDS = ['moves']
|
||||
|
||||
|
||||
class UnitLoad(ModelView, ModelSQL):
|
||||
class UnitLoad(ModelView, ModelSQL, StockMixin):
|
||||
"""Unit load"""
|
||||
__name__ = 'stock.unit_load'
|
||||
_rec_name = 'code'
|
||||
|
@ -30,9 +32,22 @@ class UnitLoad(ModelView, ModelSQL):
|
|||
code_readonly = fields.Function(fields.Boolean('Code Readonly'),
|
||||
'get_code_readonly')
|
||||
code_length = fields.Integer('Code Length', select=True, readonly=True)
|
||||
product = fields.Many2One('product.product', 'Product', select=True, required=True, ondelete='RESTRICT')
|
||||
product = fields.Many2One('product.product', 'Product',
|
||||
select=True, required=True,
|
||||
ondelete='RESTRICT')
|
||||
uom = fields.Function(fields.Many2One('product.uom', 'UOM'),
|
||||
'on_change_with_uom')
|
||||
uom_digits = fields.Function(fields.Integer('UOM Digits'),
|
||||
'on_change_with_uom_digits')
|
||||
quantity = fields.Function(fields.Float('Quantity'),
|
||||
'get_quantity',
|
||||
searcher='search_quantity')
|
||||
forecast_quantity = fields.Function(fields.Float('Forecast Quantity'),
|
||||
'get_quantity',
|
||||
searcher='search_quantity')
|
||||
moves = fields.One2Many('stock.move', 'unit_load', 'Moves', readonly=True)
|
||||
location = fields.Function(fields.Many2One('stock.location', 'Location'),
|
||||
location = fields.Function(fields.Many2One('stock.location', 'Location',
|
||||
depends=['moves']),
|
||||
'get_location')
|
||||
last_date = fields.Function(fields.Date('Last date',
|
||||
states={'readonly': Not(Equal(Eval('state'), 'draft'))},
|
||||
|
@ -56,10 +71,11 @@ class UnitLoad(ModelView, ModelSQL):
|
|||
def __setup__(cls):
|
||||
super(UnitLoad, cls).__setup__()
|
||||
cls._error_messages.update({
|
||||
'wrong_move_location': ('Cannot move unit load "%s" to Location "%s". Check its movements.'),
|
||||
'wrong_move_date': ('Cannot move unit load "%s" at date "%s" because later moves exist.'),
|
||||
'wrong_state': ('Unit load "%s" must be in Done state before moving.'),
|
||||
'state_origin': ('Cannot change state of UL "%s" due to its last moves come from "%s".')})
|
||||
'missing_location': 'Cannot find current location of UL "%s" from its moves.',
|
||||
'wrong_move_location': 'Cannot move unit load "%s" to Location "%s". Check its movements.',
|
||||
'wrong_move_date': 'Cannot move unit load "%s" at date "%s" because later moves exist.',
|
||||
'wrong_state': 'Unit load "%s" must be in Done state before moving.',
|
||||
'state_origin': 'Cannot change state of UL "%s" due to its last moves come from "%s".'})
|
||||
cls._buttons.update({
|
||||
'move_try': {'icon': 'tryton-go-next',
|
||||
'invisible': Eval('state') != 'done'},
|
||||
|
@ -107,13 +123,40 @@ class UnitLoad(ModelView, ModelSQL):
|
|||
super(UnitLoad, cls).write(*args)
|
||||
|
||||
@classmethod
|
||||
def copy(cls, unit_load, default=None):
|
||||
def copy(cls, uls, default=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
default = default.copy()
|
||||
default['code'] = None
|
||||
default['moves'] = None
|
||||
return super(unit_load, cls).copy(unit_load, default=default)
|
||||
return super(uls, cls).copy(uls, default=default)
|
||||
|
||||
@fields.depends('product')
|
||||
def on_change_with_uom(self, name=None):
|
||||
if self.product:
|
||||
return self.product.default_uom.id
|
||||
return None
|
||||
|
||||
@fields.depends('product')
|
||||
def on_change_with_uom_digits(self, name=None):
|
||||
if self.product:
|
||||
return self.product.default_uom.digits
|
||||
return 2
|
||||
|
||||
@classmethod
|
||||
def get_quantity(cls, records, name):
|
||||
location_ids = Transaction().context.get('locations')
|
||||
if not location_ids:
|
||||
location_ids = [r.location.id for r in records if r.location]
|
||||
products = [r.product for r in records]
|
||||
return cls._get_quantity(records, name, location_ids,
|
||||
products=products,
|
||||
grouping=('product', 'unit_load'))
|
||||
|
||||
@classmethod
|
||||
def search_quantity(cls, name, domain=None):
|
||||
location_ids = Transaction().context.get('locations')
|
||||
return cls._search_quantity(name, location_ids, domain)
|
||||
|
||||
@classmethod
|
||||
def get_location(cls, records, name=None):
|
||||
|
@ -121,19 +164,42 @@ class UnitLoad(ModelView, ModelSQL):
|
|||
|
||||
@classmethod
|
||||
def _get_location(cls, records):
|
||||
pool = Pool()
|
||||
Move = pool.get('stock.move')
|
||||
|
||||
record_ids = [r.id for r in records]
|
||||
ul_products = {r.id: r.product.id for r in records}
|
||||
location_ids = list(set([m.to_location.id for r in records for m in r.moves]))
|
||||
locations = dict.fromkeys(record_ids, None)
|
||||
product_ids = [r.product.id for r in records] or None
|
||||
|
||||
context = Transaction().context
|
||||
res = {}
|
||||
for record in records:
|
||||
res.setdefault(record.id, None)
|
||||
if not record.moves:
|
||||
if not context.get('stock_date_max'):
|
||||
context['stock_date_end'] = datetime.date.max
|
||||
if 'forecast' not in context:
|
||||
context['forecast'] = True
|
||||
context['active_test'] = False
|
||||
|
||||
grouping_filter = (product_ids, record_ids)
|
||||
grouping = ('product', 'unit_load')
|
||||
with Transaction().set_context(context):
|
||||
query = Move.compute_quantities_query(location_ids=location_ids,
|
||||
with_childs=False,
|
||||
grouping=grouping,
|
||||
grouping_filter=grouping_filter)
|
||||
if query is None:
|
||||
return {}
|
||||
quantities = Move.compute_quantities(query, location_ids,
|
||||
with_childs=False,
|
||||
grouping=grouping,
|
||||
grouping_filter=grouping_filter)
|
||||
|
||||
for key, quantity in quantities.iteritems():
|
||||
if quantity <= 0:
|
||||
continue
|
||||
at_date = (context['stock_date_max'] if context.get('stock_date_max')
|
||||
else max(m.effective_date or m.planned_date for m in record.moves
|
||||
if m.state != 'cancel'))
|
||||
moves = [m for m in record.moves if
|
||||
(m.effective_date or m.planned_date) == at_date and m.state != 'cancel']
|
||||
res[record.id] = moves[0].to_location.id
|
||||
return res
|
||||
if key[1] == ul_products[key[-1]]:
|
||||
locations[key[-1]] = key[0]
|
||||
return locations
|
||||
|
||||
def get_ul_moves(self, name=None):
|
||||
pool = Pool()
|
||||
|
@ -176,6 +242,8 @@ class UnitLoad(ModelView, ModelSQL):
|
|||
to_create = []
|
||||
for record in records:
|
||||
from_location = record.location
|
||||
if not from_location:
|
||||
cls.raise_user_error('missing_location', record.rec_name)
|
||||
if record.state != 'done':
|
||||
cls.raise_user_error('wrong_state', record.rec_name)
|
||||
if to_location.id == from_location.id:
|
||||
|
@ -191,12 +259,15 @@ class UnitLoad(ModelView, ModelSQL):
|
|||
to_create.extend(new_moves)
|
||||
if to_create:
|
||||
Move.save(to_create)
|
||||
return to_create
|
||||
return Move.browse(map(int, to_create))
|
||||
|
||||
def get_state(self, name=None):
|
||||
pool = Pool()
|
||||
Move = pool.get('stock.move')
|
||||
|
||||
if self.last_moves:
|
||||
return self.last_moves[0].state
|
||||
return 'draft'
|
||||
return Move.default_state()
|
||||
|
||||
@classmethod
|
||||
def search_state(cls, name, clause):
|
||||
|
@ -305,9 +376,14 @@ class UnitLoadMove(ModelSQL, ModelView):
|
|||
def table_query():
|
||||
pool = Pool()
|
||||
Move = pool.get('stock.move')
|
||||
UL = pool.get('stock.unit_load')
|
||||
move = Move.__table__()
|
||||
|
||||
ul_id = Transaction().context.get('unit_load')
|
||||
ul = UL(ul_id)
|
||||
where_clause = Literal(True)
|
||||
if getattr(ul, 'product', None):
|
||||
where_clause &= (move.product == ul.product.id)
|
||||
date_column = Coalesce(move.effective_date, move.planned_date
|
||||
).as_('date')
|
||||
return move.select(
|
||||
|
@ -322,7 +398,8 @@ class UnitLoadMove(ModelSQL, ModelView):
|
|||
Literal(None).as_('write_date'),
|
||||
date_column,
|
||||
where=(move.unit_load == ul_id)
|
||||
& (Coalesce(move.effective_date, move.planned_date) != Null),
|
||||
& (Coalesce(move.effective_date, move.planned_date) != Null)
|
||||
& where_clause,
|
||||
group_by=(date_column, move.unit_load, move.state, move.from_location, move.to_location))
|
||||
|
||||
|
||||
|
|
|
@ -8,12 +8,13 @@
|
|||
<field name="product"/>
|
||||
<newline />
|
||||
<notebook>
|
||||
<page id="general" string="General">
|
||||
<page id="general" string="General" col="6">
|
||||
<label name="location"/>
|
||||
<field name="location"/>
|
||||
<label name="last_date"/>
|
||||
<field name="last_date"/>
|
||||
<field name="ul_moves" colspan="4"/>
|
||||
<newline/>
|
||||
<field name="ul_moves" colspan="6"/>
|
||||
</page>
|
||||
<page id="moves" string="Product moves">
|
||||
<field name="moves" colspan="4"/>
|
||||
|
|
Loading…
Reference in New Issue