Versión inicial de módulo

This commit is contained in:
Sergio Morillo 2014-07-25 17:19:04 +02:00
commit c3e56c5c5c
9 changed files with 354 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/.idea
*.pyc

15
__init__.py Normal file
View file

@ -0,0 +1,15 @@
from trytond.pool import Pool
from .stock import *
from .production import *
def register():
Pool.register(
Move,
Location,
ProductionShipmentData,
ProductionShipmentConfirm,
module='production_shipment_distribute', type_='model'),
Pool.register(
ProductionShipment,
module='production_shipment_distribute', type_='wizard')

55
production.py Normal file
View file

@ -0,0 +1,55 @@
#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 trytond.model import fields
from trytond.pool import PoolMeta, Pool
__all__ = ['ProductionShipment', 'ProductionShipmentData',
'ProductionShipmentConfirm']
__metaclass__ = PoolMeta
class ProductionShipment:
__name__ = 'production.shipment'
@classmethod
def __setup__(cls):
super(ProductionShipment, cls).__setup__()
def default_confirm(self, fields):
pool = Pool()
Confirm = pool.get('production.shipment.confirm')
Move = pool.get('stock.move')
# res = super(ProductionShipment, self).default_confirm(fields)
moves = []
for m in self.location.stock:
moves.append(Confirm.explode_move(m,
self.date.planned_date,
self.location.to_location))
# create new movements
new_moves = Move.distribute(moves, self.location.sequence_order)
# TODO: verify KIT for distribute not occupy_space products
result = []
for m in new_moves:
result.append(Confirm.explode_move_values(m))
return {'moves': result}
class ProductionShipmentData:
__name__ = 'production.shipment.data'
sequence_order = fields.Selection([('ascendant', 'Ascendant'),
('descendant', 'Descendant')],
'Location Sequence order',
required=True)
@staticmethod
def default_sequence_order():
return 'ascendant'
class ProductionShipmentConfirm:
__name__ = 'production.shipment.confirm'

11
production.xml Normal file
View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<tryton>
<data>
<record model="ir.ui.view" id="shipment_data_view_form">
<field name="model">production.shipment.data</field>
<field name="type">form</field>
<field name="inherit" ref="production_shipment_internal.shipment_data_view_form" />
<field name="name">shipment_data_form</field>
</record>
</data>
</tryton>

233
stock.py Normal file
View file

@ -0,0 +1,233 @@
from trytond.model import fields
from trytond.pool import PoolMeta, Pool
__all__ = ['Move', 'Location']
__metaclass__ = PoolMeta
class Move:
__name__ = 'stock.move'
@classmethod
def __setup__(cls):
super(Move, cls).__setup__()
cls._error_messages.update(
{'cannot_distribute': 'Cannot distribute movements along Locations. '
'Please revise location sequences configuration and space availability.'})
@classmethod
def distribute(cls, moves, order='ascendant'):
"""
Distributes given movements along locations
in order to not overload storage space
moves is a list of movements. They can be existing movements or
new ones that will be persisted.
order determines the filling order based on location sequence field
Returns new list of movements
"""
pool = Pool()
Location = pool.get('stock.location')
Move = pool.get('stock.move')
if not moves:
return []
new_moves = []
to_review = cls._get_locations_to_check_space(moves)
for key, extra_space in to_review.iteritems():
loc = key[0]
date = key[1]
# assigned_space = 0
for m in moves:
if not m.to_location.id == loc:
continue
if getattr(m, 'effective_date', None) and getattr(m, 'effective_date', None) != date:
continue
if m.planned_date and not m.planned_date == date:
continue
if not m.product.occupy_space:
new_moves.append(m)
continue
av_locations = Location.get_next_available_locations(m.to_location,
date,
m.product.get_space(m.quantity),
order)
#split movement
if not av_locations:
cls.raise_user_error('cannot_distribute', {})
assigned_qty = 0
for av_key, av_space in av_locations.iteritems():
if not av_key:
cls.raise_user_error('cannot_distribute', {})
location = Location(av_key)
space = m.product.get_space((m.quantity - assigned_qty))
qty = m.product.get_quantity_from_space(av_space if av_space < space else space)
move = Move(planned_date=m.planned_date,
effective_date=getattr(m, 'effective_date', None)
if getattr(m, 'effective_date', None) else None,
product=m.product,
uom=m.product.default_uom,
quantity=qty,
from_location=m.from_location,
to_location=location,
lot=m.lot,
company=m.company,
currency=m.company.currency if m.company else None,
state=m.state)
new_moves.append(move)
assigned_qty += qty
# assigned_space += m.product.get_space(av_space if av_space < space else space)
return new_moves
class Location:
__name__ = 'stock.location'
@classmethod
def __setup__(cls):
super(Location, cls).__setup__()
if cls.sequence.depends:
if 'parent' not in cls.sequence.depends:
cls.sequence.depends.append('parent')
if 'type' not in cls.sequence.depends:
cls.sequence.depends.append('type')
else:
cls.sequence.depends = ['parent', 'type']
cls._error_messages.update({
'duplicated_sequence': 'Sequence number must be unique among '
'childs of type %(type)s of location %(location)s.',
'no_next_location': 'Next locations to fill after "%s" cannot be obtained. '
'Please check locations sequences configuration.\nIf you decided '
'to continue anyway, exceeded quantity will be storage in "%s".'})
@classmethod
def validate(cls, locations):
super(Location, cls).validate(locations)
for location in locations:
location.check_sequence()
@fields.depends('sequence', 'parent', 'type')
def on_change_with_sequence(self):
pool = Pool()
Location = pool.get('stock.location')
if self.sequence:
return self.sequence
if not self.parent:
return
location = Location(self.parent.id)
if not location:
return
max_seq = 0
for c in location.childs:
if c.sequence and c.sequence > max_seq:
max_seq = c.sequence
max_seq += 1
return max_seq
def check_sequence(self):
pool = Pool()
Location = pool.get('stock.location')
if not self.parent:
return
location = Location.search([('parent', '=', self.parent.id),
('id', '!=', self.id),
('type', '=', self.type),
('sequence', '=', self.sequence)])
if location:
self.raise_user_error('duplicated_sequence',
{'location': self.parent.rec_name,
'type': self.type})
@classmethod
def get_next_available_locations(cls, start_location,
date,
needed_space,
order='ascendant'):
""" Collects locations necessary to complete space needed.
start_location determines where to start to fill.
date is the date to calculate stock forecast.
needed_space is the quantity of space needed.
order determines the filling order based on location sequence field
Return a dictionary with location id and quantity that can be stored in it.
"""
pool = Pool()
Location = pool.get('stock.location')
# when is view or warehouse, starts in a child
if start_location.type in ['view', 'warehouse'] and start_location.childs:
storage_childs = [c for c in start_location.childs if c.type == 'storage' and c.sequence]
if not storage_childs:
return {}
if order == 'ascendant':
seq = min(c.sequence for c in storage_childs)
else:
seq = max(c.sequence for c in storage_childs)
storage, = [c for c in storage_childs if c.sequence == seq]
return cls.get_next_available_locations(storage, date, needed_space, order)
if not start_location.control_space:
return {start_location.id: needed_space}
av_space = start_location.get_available_space(date)
if av_space >= needed_space:
return {start_location.id: needed_space}
if not start_location.parent:
cls.raise_user_warning('%s.no_next_location' % start_location.id,
'no_next_location',
(start_location.rec_name, start_location.rec_name))
return {start_location.id: needed_space}
# find next locations to fill
result = {start_location.id: av_space}
assigned_space = av_space
location_domain = [('parent', '=', start_location.parent.id),
('sequence', '!=', None),
('sequence', '>' if order == 'ascendant' else '<', start_location.sequence)]
locations = Location.search(location_domain,
order=[('sequence', 'ASC' if order == 'ascendant' else 'DESC')])
for l in locations:
if needed_space == assigned_space:
return result
av_space = l.get_available_space(date)
result.setdefault(l.id, 0)
result[l.id] = (needed_space - assigned_space) if av_space >= (needed_space - assigned_space) else av_space
assigned_space += result[l.id]
# Goes up in location hierarchy to continue filling
if needed_space > assigned_space:
if not getattr(start_location.parent, 'parent', None):
result.setdefault(None, needed_space - assigned_space)
return result
location_parent = start_location.parent.parent
location_domain = [('parent', '=', location_parent.id),
('sequence', '!=', None),
('sequence', '>' if order == 'ascendant' else '<', start_location.parent.sequence)]
last_location = locations[len(locations)-1] if locations else start_location
locations = Location.search(location_domain,
order=[('sequence', 'ASC' if order == 'ascendant' else 'DESC')],
limit=1)
if not locations:
cls.raise_user_warning('%s.no_next_location' % start_location.id,
'no_next_location',
(last_location.rec_name, last_location.rec_name))
result[last_location] = result[last_location] + (needed_space - assigned_space)
return result
# get next children locations from the following parent child location
result.update(cls.get_next_available_locations(locations[0], date,
(needed_space - assigned_space),
order))
return result

11
stock.xml Normal file
View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<tryton>
<data>
<record model="ir.ui.view" id="location_view_form">
<field name="model">stock.location</field>
<field name="type">form</field>
<field name="inherit" ref="stock.location_view_form" />
<field name="name">location_form</field>
</record>
</data>
</tryton>

10
tryton.cfg Normal file
View file

@ -0,0 +1,10 @@
[tryton]
version=3.2.1
depends:
stock_location_sequence
stock_storage_space
production_shipment_internal
xml:
stock.xml
production.xml

8
view/location_form.xml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="/form" position="inside">
<field name="childs" colspan="4" />
</xpath>
</data>

View file

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="/form/field[@name='to_location']" position="after">
<label name="sequence_order" />
<field name="sequence_order" />
</xpath>
</data>