Files
tryton/move.py
T

791 lines
30 KiB
Python
Raw Normal View History

2008-10-23 17:22:25 +02: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.
import datetime
import operator
2010-08-19 22:22:44 +02:00
from decimal import Decimal
2013-10-05 12:17:26 +02:00
from functools import partial
2013-09-06 22:34:48 +02:00
from sql import Column
from sql.operators import Concat
2013-06-21 23:24:17 +02:00
from trytond.model import Workflow, Model, ModelView, ModelSQL, fields
2013-09-06 22:34:48 +02:00
from trytond import backend
2011-01-23 00:13:56 +01:00
from trytond.pyson import In, Eval, Not, Equal, If, Get, Bool
2010-08-19 22:22:44 +02:00
from trytond.transaction import Transaction
2011-07-03 13:19:14 +02:00
from trytond.pool import Pool
2008-04-08 14:27:53 +02:00
__all__ = ['StockMixin', 'Move']
2012-09-24 22:18:17 +02:00
2008-04-08 14:27:53 +02:00
STATES = {
'readonly': In(Eval('state'), ['cancel', 'assigned', 'done']),
2008-04-08 14:27:53 +02:00
}
2011-08-04 16:57:42 +02:00
DEPENDS = ['state']
2008-04-08 14:27:53 +02:00
class StockMixin:
'''Mixin class with helper to setup stock quantity field.'''
@classmethod
def _quantity_context(cls, name):
pool = Pool()
Date = pool.get('ir.date')
context = Transaction().context
new_context = {}
if name == 'quantity':
if (context.get('stock_date_end')
and context['stock_date_end'] > Date.today()):
new_context['stock_date_end'] = Date.today()
elif name == 'forecast_quantity':
new_context['forecast'] = True
if not context.get('stock_date_end'):
new_context['stock_date_end'] = datetime.date.max
return new_context
@classmethod
def _get_quantity(cls, records, name, location_ids, products=None,
grouping=('product',), position=-1):
"""
Compute for each record the stock quantity in the default uom of the
product.
location_ids is the list of IDs of locations to take account to compute
the stock. It can't be empty.
products restrict the stock computation to the this products (more
efficient), so it should be the products related to records.
If it is None all products are used.
grouping defines how stock moves are grouped.
position defines which field of grouping corresponds to the record
whose quantity is computed.
Return a dictionary with records id as key and quantity as value.
"""
pool = Pool()
Product = pool.get('product.product')
record_ids = [r.id for r in records]
quantities = dict.fromkeys(record_ids, 0.0)
if not location_ids:
return quantities
product_ids = products and [p.id for p in products] or None
with Transaction().set_context(cls._quantity_context(name)):
pbl = Product.products_by_location(location_ids=location_ids,
product_ids=product_ids, with_childs=True,
grouping=grouping)
for key, quantity in pbl.iteritems():
# pbl could return None in some keys
if (key[position] is not None and
key[position] in quantities):
quantities[key[position]] += quantity
return quantities
@classmethod
def _search_quantity(cls, name, location_ids, domain=None,
grouping=('product',), position=-1):
"""
Compute the domain to filter records which validates the domain over
quantity field.
location_ids is the list of IDs of locations to take account to compute
the stock. It can't be empty.
grouping defines how stock moves are grouped.
position defines which field of grouping corresponds to the record
whose quantity is computed.
"""
pool = Pool()
Product = pool.get('product.product')
if not location_ids or not domain:
return []
def _search_quantity_eval_domain(line, domain):
operator_funcs = {
'=': operator.eq,
'>=': operator.ge,
'>': operator.gt,
'<=': operator.le,
'<': operator.lt,
'!=': operator.ne,
'in': lambda v, l: v in l,
'not in': lambda v, l: v not in l,
}
field, op, operand = domain
value = line.get(field)
return operator_funcs[op](value, operand)
with Transaction().set_context(cls._quantity_context(name)):
pbl = Product.products_by_location(
location_ids=location_ids,
with_childs=True, grouping=grouping)
processed_lines = []
for key, quantity in pbl.iteritems():
# pbl could return None in some keys
if key[position] is not None:
processed_lines.append({
'record_id': key[position],
name: quantity,
})
record_ids = [line['record_id'] for line in processed_lines
if _search_quantity_eval_domain(line, domain)]
return [('id', 'in', record_ids)]
2012-12-27 21:53:07 +01:00
class Move(Workflow, ModelSQL, ModelView):
2008-04-08 14:27:53 +02:00
"Stock Move"
2012-09-24 22:18:17 +02:00
__name__ = 'stock.move'
2011-06-15 11:41:28 +02:00
_order_name = 'product'
2008-05-13 11:49:37 +02:00
product = fields.Many2One("product.product", "Product", required=True,
2012-03-05 10:53:08 +01:00
select=True, states=STATES,
2011-08-04 16:57:42 +02:00
on_change=['product', 'currency', 'uom', 'company',
'from_location', 'to_location'],
domain=[('type', '!=', 'service')],
depends=DEPENDS)
2012-03-25 15:39:36 +02:00
product_uom_category = fields.Function(
fields.Many2One('product.uom.category', 'Product Uom Category',
on_change_with=['product']),
2012-09-24 22:18:17 +02:00
'on_change_with_product_uom_category')
2008-08-26 19:05:47 +02:00
uom = fields.Many2One("product.uom", "Uom", required=True, states=STATES,
2011-08-04 16:57:42 +02:00
domain=[
2012-03-25 15:39:36 +02:00
('category', '=', Eval('product_uom_category')),
2010-01-20 11:04:28 +01:00
],
2011-08-04 16:57:42 +02:00
on_change=['product', 'currency', 'uom', 'company',
'from_location', 'to_location'],
2012-03-25 15:39:36 +02:00
depends=['state', 'product_uom_category'])
2010-03-11 16:54:32 +01:00
unit_digits = fields.Function(fields.Integer('Unit Digits',
2012-09-24 22:18:17 +02:00
on_change_with=['uom']), 'on_change_with_unit_digits')
2008-08-26 19:05:47 +02:00
quantity = fields.Float("Quantity", required=True,
2011-08-04 16:57:42 +02:00
digits=(16, Eval('unit_digits', 2)), states=STATES,
depends=['state', 'unit_digits'])
internal_quantity = fields.Float('Internal Quantity', readonly=True,
required=True)
2012-03-05 10:53:08 +01:00
from_location = fields.Many2One("stock.location", "From Location",
select=True, required=True, states=STATES, depends=DEPENDS,
2011-08-04 16:57:42 +02:00
domain=[('type', 'not in', ('warehouse', 'view'))])
2012-03-05 10:53:08 +01:00
to_location = fields.Many2One("stock.location", "To Location", select=True,
2011-08-04 16:57:42 +02:00
required=True, states=STATES, depends=DEPENDS,
domain=[('type', 'not in', ('warehouse', 'view'))])
2013-03-23 15:42:36 +01:00
shipment = fields.Reference('Shipment', selection='get_shipment',
readonly=True, select=True)
origin = fields.Reference('Origin', selection='get_origin', select=True,
states={
'readonly': Eval('state') != 'draft',
},
depends=['state'])
planned_date = fields.Date("Planned Date", states={
'readonly': (In(Eval('state'), ['cancel', 'assigned', 'done'])
2013-03-23 15:42:36 +01:00
| Eval('shipment'))
}, depends=['state', 'shipment'],
2012-03-05 10:53:08 +01:00
select=True)
effective_date = fields.Date("Effective Date", readonly=True, select=True)
state = fields.Selection([
('draft', 'Draft'),
2008-07-07 15:54:10 +02:00
('assigned', 'Assigned'),
('done', 'Done'),
2009-03-20 13:09:19 +01:00
('cancel', 'Canceled'),
2012-03-05 10:53:08 +01:00
], 'State', select=True, readonly=True)
2008-05-09 12:32:46 +02:00
company = fields.Many2One('company.company', 'Company', required=True,
2011-08-04 16:57:42 +02:00
states={
'readonly': Not(Equal(Eval('state'), 'draft')),
},
domain=[
('id', If(In('company', Eval('context', {})), '=', '!='),
2013-10-09 18:25:53 +02:00
Eval('context', {}).get('company', -1)),
2011-08-04 16:57:42 +02:00
],
depends=['state'])
2008-05-13 11:49:37 +02:00
unit_price = fields.Numeric('Unit Price', digits=(16, 4),
2011-08-04 16:57:42 +02:00
states={
'invisible': Not(Bool(Eval('unit_price_required'))),
'required': Bool(Eval('unit_price_required')),
'readonly': Not(Equal(Eval('state'), 'draft')),
},
depends=['unit_price_required', 'state'])
cost_price = fields.Numeric('Cost Price', digits=(16, 4), readonly=True)
2008-05-13 11:49:37 +02:00
currency = fields.Many2One('currency.currency', 'Currency',
2011-08-04 16:57:42 +02:00
states={
'invisible': Not(Bool(Eval('unit_price_required'))),
2011-10-18 09:57:45 +02:00
'required': Bool(Eval('unit_price_required')),
2011-08-04 16:57:42 +02:00
'readonly': Not(Equal(Eval('state'), 'draft')),
},
depends=['unit_price_required', 'state'])
2010-03-11 16:54:32 +01:00
unit_price_required = fields.Function(fields.Boolean('Unit Price Required',
on_change_with=['from_location', 'to_location']),
2012-09-24 22:18:17 +02:00
'on_change_with_unit_price_required')
2008-04-08 14:27:53 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
def __setup__(cls):
super(Move, cls).__setup__()
cls._deny_modify_assigned = set(['product', 'uom', 'quantity',
'from_location', 'to_location', 'company', 'unit_price',
'currency'])
cls._deny_modify_done_cancel = (cls._deny_modify_assigned |
set(['planned_date', 'effective_date', 'state']))
cls._allow_modify_closed_period = set()
2012-09-24 22:18:17 +02:00
cls._sql_constraints += [
2008-04-22 00:50:23 +02:00
('check_move_qty_pos',
'CHECK(quantity >= 0.0)', 'Move quantity must be positive'),
('check_move_internal_qty_pos',
'CHECK(internal_quantity >= 0.0)',
'Internal move quantity must be positive'),
('check_from_to_locations',
'CHECK(from_location != to_location)',
'Source and destination location must be different'),
2013-02-24 16:18:17 +01:00
]
2012-09-24 22:18:17 +02:00
cls._order[0] = ('id', 'DESC')
cls._error_messages.update({
'set_state_draft': ('You can not set stock move "%s" to draft '
'state.'),
'set_state_assigned': ('You can not set stock move "%s" to '
'assigned state.'),
'set_state_done': 'You can not set stock move "%s" to done state.',
'del_draft_cancel': ('You can not delete stock move "%s" because '
'it is not in draft or cancelled state.'),
'period_closed': ('You can not modify move "%(move)s" because '
'period "%(period)s" is closed.'),
'modify_assigned': ('You can not modify stock move "%s" because '
'it is in "Assigned" state.'),
'modify_done_cancel': ('You can not modify stock move "%s" '
'because it is in "Done" or "Cancel" state.'),
2008-07-17 19:37:38 +02:00
})
2012-12-27 21:53:07 +01:00
cls._transitions |= set((
('draft', 'assigned'),
('draft', 'done'),
('draft', 'cancel'),
('assigned', 'draft'),
('assigned', 'done'),
('assigned', 'cancel'),
))
cls._buttons.update({
'cancel': {
'invisible': ~Eval('state').in_(['draft', 'assigned']),
},
'draft': {
'invisible': ~Eval('state').in_(['assigned']),
},
'assign': {
'invisible': ~Eval('state').in_(['assigned']),
},
'do': {
'invisible': ~Eval('state').in_(['draft', 'assigned']),
},
})
2008-04-22 00:50:23 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
def __register__(cls, module_name):
2013-09-06 22:34:48 +02:00
TableHandler = backend.get('TableHandler')
2010-08-19 22:22:44 +02:00
cursor = Transaction().cursor
2013-09-06 22:34:48 +02:00
sql_table = cls.__table__()
# Migration from 1.2: packing renamed into shipment
2012-09-24 22:18:17 +02:00
table = TableHandler(cursor, cls, module_name)
table.drop_constraint('check_packing')
for suffix in ('in', 'out', 'in_return', 'out_return', 'internal'):
old_column = 'packing_%s' % suffix
new_column = 'shipment_%s' % suffix
if table.column_exist(old_column):
table.index_action(old_column, action='remove')
table.drop_fk(old_column)
table.column_rename(old_column, new_column)
# Migration from 1.8: new field internal_quantity
internal_quantity_exist = table.column_exist('internal_quantity')
2012-09-24 22:18:17 +02:00
super(Move, cls).__register__(module_name)
# Migration from 1.8: fill new field internal_quantity
if not internal_quantity_exist:
offset = 0
limit = cursor.IN_MAX
2012-09-24 22:18:17 +02:00
moves = True
while moves:
moves = cls.search([], offset=offset, limit=limit)
offset += limit
2012-09-24 22:18:17 +02:00
for move in moves:
internal_quantity = cls._get_internal_quantity(
move.quantity, move.uom, move.product)
2013-09-06 22:34:48 +02:00
cursor.execute(*sql_table.update(
columns=[sql_table.internal_quantity],
values=[internal_quantity],
where=sql_table.id == move.id))
2012-09-24 22:18:17 +02:00
table = TableHandler(cursor, cls, module_name)
table.not_null_action('internal_quantity', action='add')
# Migration from 1.0 check_packing_in_out has been removed
2012-09-24 22:18:17 +02:00
table = TableHandler(cursor, cls, module_name)
table.drop_constraint('check_packing_in_out')
2013-03-23 15:42:36 +01:00
# Migration from 2.6: merge all shipments
table.drop_constraint('check_shipment')
shipments = {
'shipment_in': 'stock.shipment.in',
'shipment_out': 'stock.shipment.out',
'shipment_out_return': 'stock.shipment.out.return',
'shipment_in_return': 'stock.shipment.in.return',
'shipment_internal': 'stock.shipment.internal',
}
for column, model in shipments.iteritems():
if table.column_exist(column):
2013-09-06 22:34:48 +02:00
cursor.execute(*sql_table.update(
columns=[sql_table.shipment],
values=[Concat(model + ',',
Column(sql_table, column))],
where=Column(sql_table, column) != None))
2013-03-23 15:42:36 +01:00
table.drop_column(column)
# Add index on create_date
table.index_action('create_date', action='add')
2012-09-24 22:18:17 +02:00
@staticmethod
def default_planned_date():
2012-03-27 10:44:04 +02:00
return Transaction().context.get('planned_date')
2008-10-06 12:20:19 +02:00
2012-09-24 22:18:17 +02:00
@staticmethod
def default_state():
2008-04-08 14:27:53 +02:00
return 'draft'
2012-09-24 22:18:17 +02:00
@staticmethod
def default_company():
2012-03-27 10:44:04 +02:00
return Transaction().context.get('company')
2008-05-09 12:32:46 +02:00
2012-09-24 22:18:17 +02:00
@staticmethod
def default_currency():
Company = Pool().get('company.company')
2010-08-19 22:22:44 +02:00
company = Transaction().context.get('company')
if company:
2012-09-24 22:18:17 +02:00
company = Company(company)
return company.currency.id
2008-04-08 14:27:53 +02:00
2012-09-24 22:18:17 +02:00
@staticmethod
def default_unit_digits():
2012-03-26 14:42:50 +02:00
return 2
2012-09-24 22:18:17 +02:00
def on_change_with_unit_digits(self, name=None):
if self.uom:
return self.uom.digits
return 2
2012-09-24 22:18:17 +02:00
def on_change_product(self):
2011-07-03 13:19:14 +02:00
pool = Pool()
2012-09-24 22:18:17 +02:00
Uom = pool.get('product.uom')
Currency = pool.get('currency.currency')
res = {
'unit_price': Decimal('0.0'),
2012-09-24 22:18:17 +02:00
}
if self.product:
res['uom'] = self.product.default_uom.id
res['uom.rec_name'] = self.product.default_uom.rec_name
res['unit_digits'] = self.product.default_uom.digits
unit_price = None
2012-09-24 22:18:17 +02:00
if self.from_location and self.from_location.type in ('supplier',
'production'):
2012-09-24 22:18:17 +02:00
unit_price = self.product.cost_price
elif self.to_location and self.to_location.type == 'customer':
unit_price = self.product.list_price
if unit_price:
2012-09-24 22:18:17 +02:00
if self.uom != self.product.default_uom:
unit_price = Uom.compute_price(self.product.default_uom,
unit_price, self.uom)
if self.currency and self.company:
unit_price = Currency.compute(self.company.currency,
unit_price, self.currency, round=False)
res['unit_price'] = unit_price
return res
2012-09-24 22:18:17 +02:00
def on_change_with_product_uom_category(self, name=None):
if self.product:
return self.product.default_uom_category.id
2012-03-25 15:39:36 +02:00
2012-09-24 22:18:17 +02:00
def on_change_uom(self):
2011-07-03 13:19:14 +02:00
pool = Pool()
2012-09-24 22:18:17 +02:00
Uom = pool.get('product.uom')
Currency = pool.get('currency.currency')
res = {
'unit_price': Decimal('0.0'),
2012-09-24 22:18:17 +02:00
}
if self.product:
if self.to_location and self.to_location.type == 'storage':
unit_price = self.product.cost_price
if self.uom and self.uom != self.product.default_uom:
unit_price = Uom.compute_price(self.product.default_uom,
unit_price, self.uom)
if self.currency and self.company:
unit_price = Currency.compute(self.company.currency,
unit_price, self.currency, round=False)
res['unit_price'] = unit_price
return res
2012-09-24 22:18:17 +02:00
def on_change_with_unit_price_required(self, name=None):
if (self.from_location
and self.from_location.type in ('supplier', 'production')):
return True
if (self.to_location
and self.to_location.type == 'customer'):
return True
if (self.from_location and self.to_location
and self.from_location.type == 'storage'
and self.to_location.type == 'supplier'):
return True
return False
2013-03-23 15:42:36 +01:00
@staticmethod
def _get_shipment():
'Return list of Model names for shipment Reference'
return [
'stock.shipment.in',
'stock.shipment.out',
'stock.shipment.out.return',
'stock.shipment.in.return',
'stock.shipment.internal',
]
@classmethod
def get_shipment(cls):
2013-10-05 12:17:26 +02:00
IrModel = Pool().get('ir.model')
2013-03-23 15:42:36 +01:00
models = cls._get_shipment()
2013-10-05 12:17:26 +02:00
models = IrModel.search([
2013-03-23 15:42:36 +01:00
('model', 'in', models),
])
return [(None, '')] + [(m.model, m.name) for m in models]
@staticmethod
def _get_origin():
'Return list of Model names for origin Reference'
return []
@classmethod
def get_origin(cls):
2013-10-05 12:17:26 +02:00
IrModel = Pool().get('ir.model')
2013-03-23 15:42:36 +01:00
models = cls._get_origin()
2013-10-05 12:17:26 +02:00
models = IrModel.search([
2013-03-23 15:42:36 +01:00
('model', 'in', models),
])
return [(None, '')] + [(m.model, m.name) for m in models]
2013-09-19 17:34:23 +02:00
@property
def origin_name(self):
return self.origin.rec_name if self.origin else None
2012-09-24 22:18:17 +02:00
@classmethod
def check_period_closed(cls, moves):
Period = Pool().get('stock.period')
periods = Period.search([
('state', '=', 'closed'),
], order=[('date', 'DESC')], limit=1)
if periods:
period, = periods
for move in moves:
date = (move.effective_date if move.effective_date
else move.planned_date)
if date and date < period.date:
cls.raise_user_error('period_closed', {
'move': move.rec_name,
'period': period.rec_name,
})
2012-09-24 22:18:17 +02:00
def get_rec_name(self, name):
return ("%s%s %s"
% (self.quantity, self.uom.symbol, self.product.rec_name))
2012-09-24 22:18:17 +02:00
@classmethod
def search_rec_name(cls, name, clause):
return [('product',) + tuple(clause[1:])]
2012-12-27 21:53:07 +01:00
def _update_product_cost_price(self, direction):
2008-10-23 17:22:25 +02:00
"""
2012-09-24 22:18:17 +02:00
Update the cost price on the given product.
2012-12-27 21:53:07 +01:00
The direction must be "in" if incoming and "out" if outgoing.
2008-10-23 17:22:25 +02:00
"""
2011-07-03 13:19:14 +02:00
pool = Pool()
2012-09-24 22:18:17 +02:00
Uom = pool.get('product.uom')
Product = pool.get('product.product')
ProductTemplate = pool.get('product.template')
Location = pool.get('stock.location')
Currency = pool.get('currency.currency')
Date = pool.get('ir.date')
2008-10-23 17:22:25 +02:00
2012-12-27 21:53:07 +01:00
if direction == 'in':
quantity = self.quantity
elif direction == 'out':
quantity = -self.quantity
2010-08-19 22:22:44 +02:00
context = {}
2013-03-06 13:08:21 +01:00
locations = Location.search([
2012-09-24 22:18:17 +02:00
('type', '=', 'storage'),
])
2013-03-06 13:08:21 +01:00
context['locations'] = [l.id for l in locations]
2012-09-24 22:18:17 +02:00
context['stock_date_end'] = Date.today()
2010-08-19 22:22:44 +02:00
with Transaction().set_context(context):
2012-12-27 21:53:07 +01:00
product = Product(self.product.id)
qty = Uom.compute_qty(self.uom, quantity, product.default_uom)
2008-10-23 17:22:25 +02:00
qty = Decimal(str(qty))
2012-09-24 22:18:17 +02:00
if hasattr(Product, 'cost_price'):
product_qty = product.quantity
else:
product_qty = product.template.quantity
product_qty = Decimal(str(product_qty))
# convert wrt currency
2012-12-27 21:53:07 +01:00
with Transaction().set_context(date=self.effective_date):
unit_price = Currency.compute(self.currency, self.unit_price,
self.company.currency, round=False)
# convert wrt to the uom
2012-12-27 21:53:07 +01:00
unit_price = Uom.compute_price(self.uom, unit_price,
product.default_uom)
if product_qty + qty != Decimal('0.0'):
new_cost_price = (
(product.cost_price * product_qty) + (unit_price * qty)
) / (product_qty + qty)
else:
new_cost_price = product.cost_price
2009-04-09 11:44:35 +02:00
2012-09-24 22:18:17 +02:00
if hasattr(Product, 'cost_price'):
digits = Product.cost_price.digits
2013-03-20 22:51:12 +01:00
write = partial(Product.write, [product])
else:
2012-09-24 22:18:17 +02:00
digits = ProductTemplate.cost_price.digits
2013-03-20 22:51:12 +01:00
write = partial(ProductTemplate.write, [product.template])
new_cost_price = new_cost_price.quantize(
2012-04-03 20:07:43 +02:00
Decimal(str(10.0 ** -digits[1])))
2010-08-19 22:22:44 +02:00
with Transaction().set_user(0, set_context=True):
2013-03-20 22:51:12 +01:00
write({
2012-09-24 22:18:17 +02:00
'cost_price': new_cost_price,
})
2008-10-23 17:22:25 +02:00
2012-09-24 22:18:17 +02:00
@staticmethod
def _get_internal_quantity(quantity, uom, product):
Uom = Pool().get('product.uom')
internal_quantity = Uom.compute_qty(uom, quantity,
product.default_uom, round=True)
return internal_quantity
2012-09-24 22:18:17 +02:00
@classmethod
2012-12-27 21:53:07 +01:00
@ModelView.button
@Workflow.transition('draft')
def draft(cls, moves):
pass
@classmethod
@ModelView.button
@Workflow.transition('assigned')
def assign(cls, moves):
2011-07-03 13:19:14 +02:00
pool = Pool()
2012-09-24 22:18:17 +02:00
Date = pool.get('ir.date')
2008-10-23 17:22:25 +02:00
2012-09-24 22:18:17 +02:00
today = Date.today()
2012-12-27 21:53:07 +01:00
for move in moves:
if not move.effective_date:
move.effective_date = today
move.save()
@classmethod
@ModelView.button
@Workflow.transition('done')
def do(cls, moves):
pool = Pool()
Date = pool.get('ir.date')
today = Date.today()
for move in moves:
if not move.effective_date:
move.effective_date = today
if (move.from_location.type in ('supplier', 'production')
and move.to_location.type == 'storage'
and move.product.cost_price_method == 'average'):
move._update_product_cost_price('in')
elif (move.to_location.type == 'supplier'
and move.from_location.type == 'storage'
and move.product.cost_price_method == 'average'):
move._update_product_cost_price('out')
if not move.cost_price:
move.cost_price = move.product.cost_price
move.save()
@classmethod
@ModelView.button
@Workflow.transition('cancel')
def cancel(cls, moves):
pool = Pool()
Date = pool.get('ir.date')
today = Date.today()
for move in moves:
move.effective_date = today
if (move.from_location.type in ('supplier', 'production')
and move.to_location.type == 'storage'
and move.product.cost_price_method == 'average'):
move._update_product_cost_price('out')
elif (move.to_location.type == 'supplier'
and move.from_location.type == 'storage'
and move.product.cost_price_method == 'average'):
move._update_product_cost_price('in')
move.effective_date = None
move.save()
@classmethod
2013-01-06 22:18:13 +01:00
def create(cls, vlist):
2012-12-27 21:53:07 +01:00
pool = Pool()
Product = pool.get('product.product')
Uom = pool.get('product.uom')
2013-01-06 22:18:13 +01:00
vlist = [x.copy() for x in vlist]
for vals in vlist:
assert vals.get('state', 'draft') == 'draft'
2013-01-06 22:18:13 +01:00
product = Product(vals['product'])
uom = Uom(vals['uom'])
internal_quantity = cls._get_internal_quantity(vals['quantity'],
uom, product)
vals['internal_quantity'] = internal_quantity
moves = super(Move, cls).create(vlist)
cls.check_period_closed(moves)
return moves
2008-10-23 17:22:25 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
def write(cls, moves, vals):
vals_set = set(vals)
if cls._deny_modify_assigned & vals_set:
for move in moves:
if move.state == 'assigned':
cls.raise_user_error('modify_assigned', (move.rec_name,))
if cls._deny_modify_done_cancel & vals_set:
for move in moves:
if move.state in ('done', 'cancel'):
cls.raise_user_error('modify_done_cancel',
(move.rec_name,))
if any(f not in cls._allow_modify_closed_period for f in vals):
cls.check_period_closed(moves)
2012-09-24 22:18:17 +02:00
super(Move, cls).write(moves, vals)
2008-05-13 13:11:53 +02:00
2012-09-24 22:18:17 +02:00
for move in moves:
internal_quantity = cls._get_internal_quantity(move.quantity,
move.uom, move.product)
if (internal_quantity != move.internal_quantity
and internal_quantity != vals.get('internal_quantity')):
2012-12-27 21:53:07 +01:00
cls.write([move], {
2012-09-24 22:18:17 +02:00
'internal_quantity': internal_quantity,
})
2012-09-24 22:18:17 +02:00
@classmethod
def delete(cls, moves):
for move in moves:
2013-02-24 16:18:17 +01:00
if move.state not in ('draft', 'cancel'):
cls.raise_user_error('del_draft_cancel', (move.rec_name,))
2012-09-24 22:18:17 +02:00
super(Move, cls).delete(moves)
2012-09-24 22:18:17 +02:00
def pick_product(self, location_quantities):
"""
2009-01-26 18:59:29 +01:00
Pick the product across the location. Naive (fast) implementation.
2012-09-24 22:18:17 +02:00
Return a list of tuple (location, quantity) for quantities that can be
picked.
"""
to_pick = []
2012-09-24 22:18:17 +02:00
needed_qty = self.quantity
for location, available_qty in location_quantities.iteritems():
# Ignore available_qty when too small
2012-09-24 22:18:17 +02:00
if available_qty < self.uom.rounding:
continue
if needed_qty <= available_qty:
to_pick.append((location, needed_qty))
return to_pick
else:
to_pick.append((location, available_qty))
needed_qty -= available_qty
# Force assignation for consumables:
2012-09-24 22:18:17 +02:00
if self.product.consumable:
to_pick.append((self.from_location, needed_qty))
return to_pick
2009-01-26 18:59:29 +01:00
return to_pick
2012-09-24 22:18:17 +02:00
@classmethod
2013-06-21 23:24:17 +02:00
def assign_try(cls, moves, grouping=('product',)):
'''
Try to assign moves.
2009-01-26 18:59:29 +01:00
It will split the moves to assign as much possible.
2012-09-24 22:18:17 +02:00
Return True if succeed or False if not.
'''
2011-07-03 13:19:14 +02:00
pool = Pool()
2012-09-24 22:18:17 +02:00
Product = pool.get('product.product')
Uom = pool.get('product.uom')
Date = pool.get('ir.date')
Location = pool.get('stock.location')
2012-09-24 22:18:17 +02:00
Transaction().cursor.lock(cls._table)
2012-09-24 22:18:17 +02:00
locations = Location.search([
('parent', 'child_of', [x.from_location.id for x in moves]),
])
2010-08-19 22:22:44 +02:00
with Transaction().set_context(
2012-09-24 22:18:17 +02:00
stock_date_end=Date.today(),
2010-08-19 22:22:44 +02:00
stock_assign=True):
2012-09-24 22:18:17 +02:00
pbl = Product.products_by_location(
location_ids=[l.id for l in locations],
2013-06-21 23:24:17 +02:00
product_ids=[m.product.id for m in moves],
grouping=grouping)
def get_key(location):
key = (location.id,)
for field in grouping:
value = getattr(move, field)
if isinstance(value, Model):
value = value.id
key += (value,)
return key
success = True
for move in moves:
if move.state != 'draft':
continue
to_location = move.to_location
location_qties = {}
2012-09-24 22:18:17 +02:00
childs = Location.search([
('parent', 'child_of', [move.from_location.id]),
])
for location in childs:
2013-06-21 23:24:17 +02:00
key = get_key(location)
if key in pbl:
2012-09-24 22:18:17 +02:00
location_qties[location] = Uom.compute_qty(
2013-06-21 23:24:17 +02:00
move.product.default_uom, pbl[key], move.uom,
round=False)
2012-09-24 22:18:17 +02:00
to_pick = move.pick_product(location_qties)
2009-01-26 18:59:29 +01:00
picked_qties = 0.0
for _, qty in to_pick:
picked_qties += qty
if picked_qties < move.quantity:
success = False
2009-01-26 18:59:29 +01:00
first = False
2012-09-24 22:18:17 +02:00
cls.write([move], {
2009-01-26 18:59:29 +01:00
'quantity': move.quantity - picked_qties,
2010-08-19 22:22:44 +02:00
})
2009-01-26 18:59:29 +01:00
else:
first = True
for from_location, qty in to_pick:
values = {
'from_location': from_location.id,
'quantity': qty,
}
if first:
2012-09-24 22:18:17 +02:00
cls.write([move], values)
2012-12-27 21:53:07 +01:00
cls.assign([move])
first = False
else:
2012-12-27 21:53:07 +01:00
cls.assign(cls.copy([move], default=values))
2012-09-24 22:18:17 +02:00
qty_default_uom = Uom.compute_qty(move.uom, qty,
2010-08-19 22:22:44 +02:00
move.product.default_uom, round=False)
2013-06-21 23:24:17 +02:00
from_key, to_key = get_key(from_location), get_key(to_location)
pbl[from_key] = pbl.get(from_key, 0.0) - qty_default_uom
pbl[to_key] = pbl.get(to_key, 0.0) + qty_default_uom
return success