Files
tryton/move.py
T

743 lines
32 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.
2010-08-19 22:22:44 +02:00
from decimal import Decimal
from functools import reduce
from trytond.model import ModelView, ModelSQL, fields, OPERATORS
from trytond.backend import TableHandler
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
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
2009-02-28 17:04:50 +01:00
class Move(ModelSQL, ModelView):
2008-04-08 14:27:53 +02:00
"Stock Move"
_name = 'stock.move'
_description = __doc__
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,
2011-08-04 16:57:42 +02:00
select=1, states=STATES,
on_change=['product', 'currency', 'uom', 'company',
'from_location', 'to_location'],
domain=[('type', '!=', 'service')],
depends=DEPENDS)
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=[
('category', '=',
(Eval('product'), 'product.default_uom.category')),
2010-01-20 11:04:28 +01:00
],
2011-08-04 16:57:42 +02:00
context={
'category': (Eval('product'), 'product.default_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'],
depends=['state', 'product'])
2010-03-11 16:54:32 +01:00
unit_digits = fields.Function(fields.Integer('Unit Digits',
on_change_with=['uom']), 'get_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)
2008-05-13 11:49:37 +02:00
from_location = fields.Many2One("stock.location", "From Location", select=1,
2011-08-04 16:57:42 +02:00
required=True, states=STATES, depends=DEPENDS,
domain=[('type', 'not in', ('warehouse', 'view'))])
2008-05-13 11:49:37 +02:00
to_location = fields.Many2One("stock.location", "To Location", select=1,
2011-08-04 16:57:42 +02:00
required=True, states=STATES, depends=DEPENDS,
domain=[('type', 'not in', ('warehouse', 'view'))])
shipment_in = fields.Many2One('stock.shipment.in', 'Supplier Shipment',
2011-10-04 18:17:41 +02:00
domain=[('company', '=', Eval('company'))], depends=['company'],
readonly=True, select=1, ondelete='CASCADE')
shipment_out = fields.Many2One('stock.shipment.out', 'Customer Shipment',
2011-10-04 18:17:41 +02:00
domain=[('company', '=', Eval('company'))], depends=['company'],
readonly=True, select=1, ondelete='CASCADE')
shipment_out_return = fields.Many2One('stock.shipment.out.return',
2011-10-04 18:17:41 +02:00
'Customer Return Shipment', readonly=True, select=1,
domain=[('company', '=', Eval('company'))], depends=['company'],
ondelete='CASCADE')
shipment_in_return = fields.Many2One('stock.shipment.in.return',
2011-10-04 18:17:41 +02:00
'Supplier Return Shipment', readonly=True, select=1,
domain=[('company', '=', Eval('company'))], depends=['company'],
ondelete='CASCADE')
shipment_internal = fields.Many2One('stock.shipment.internal',
2011-10-04 18:17:41 +02:00
'Internal Shipment', readonly=True, select=1, ondelete='CASCADE',
domain=[('company', '=', Eval('company'))], depends=['company'])
planned_date = fields.Date("Planned Date", states={
'readonly': (In(Eval('state'), ['cancel', 'assigned', 'done'])
| Eval('shipment_in') | Eval('shipment_out')
| Eval('shipment_in_return') | Eval('shipment_out_return')
| Eval('shipment_internal'))
}, depends=['state', 'shipment_in', 'shipment_out',
'shipment_in_return', 'shipment_out_return', 'shipment_internal'],
2011-08-04 16:57:42 +02:00
select=2)
effective_date = fields.Date("Effective Date", readonly=True, select=2)
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'),
], 'State', select=1, 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', {})), '=', '!='),
Get(Eval('context', {}), 'company', 0)),
],
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']),
'get_unit_price_required')
2008-04-08 14:27:53 +02:00
def __init__(self):
super(Move, self).__init__()
2008-04-22 00:50:23 +02:00
self._sql_constraints += [
('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'),
('check_shipment',
'CHECK((COALESCE(shipment_in, 0) / COALESCE(shipment_in, 1) ' \
'+ COALESCE(shipment_out, 0) / ' \
'COALESCE(shipment_out, 1) ' \
'+ COALESCE(shipment_internal, 0) / ' \
'COALESCE(shipment_internal, 1) ' \
'+ COALESCE(shipment_in_return, 0) / ' \
'COALESCE(shipment_in_return, 1) ' \
'+ COALESCE(shipment_out_return, 0) / ' \
'COALESCE(shipment_out_return, 1)) ' \
'<= 1)',
'Move can be on only one Shipment'),
2008-04-22 00:50:23 +02:00
]
self._constraints += [
2008-08-05 11:36:37 +02:00
('check_product_type', 'service_product'),
('check_period_closed', 'period_closed'),
]
2008-04-22 17:49:50 +02:00
self._order[0] = ('id', 'DESC')
2008-07-17 19:37:38 +02:00
self._error_messages.update({
'set_state_draft': 'You can not set state to draft!',
'set_state_assigned': 'You can not set state to assigned!',
'set_state_done': 'You can not set state to done!',
'del_draft_cancel': 'You can only delete draft or cancelled moves!',
2009-03-20 13:49:37 +01:00
'service_product': 'You can not use service products for a move!',
'period_closed': 'You can not modify move in closed period!',
'modify_assigned_done_cancel': ('You can not modify a move '
'in the state: "Assigned", "Done" or "Cancel"'),
2008-07-17 19:37:38 +02:00
})
2008-04-22 00:50:23 +02:00
2010-08-19 22:22:44 +02:00
def init(self, module_name):
cursor = Transaction().cursor
# Migration from 1.2: packing renamed into shipment
table = TableHandler(cursor, self, 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')
2010-08-19 22:22:44 +02:00
super(Move, self).init(module_name)
# Migration from 1.8: fill new field internal_quantity
if not internal_quantity_exist:
offset = 0
limit = cursor.IN_MAX
move_ids = True
while move_ids:
move_ids = self.search([], offset=offset, limit=limit)
offset += limit
for move in self.browse(move_ids):
internal_quantity = self._get_internal_quantity(
move.quantity, move.uom, move.product)
self.write(move.id, {
'internal_quantity': internal_quantity,
})
table = TableHandler(cursor, self, module_name)
table.not_null_action('internal_quantity', action='add')
# Migration from 1.0 check_packing_in_out has been removed
table = TableHandler(cursor, self, module_name)
table.drop_constraint('check_packing_in_out')
# Add index on create_date
table.index_action('create_date', action='add')
2010-08-19 22:22:44 +02:00
def default_planned_date(self):
return Transaction().context.get('planned_date') or False
2008-10-06 12:20:19 +02:00
2010-08-19 22:22:44 +02:00
def default_to_location(self):
2011-07-03 13:19:14 +02:00
location_obj = Pool().get('stock.location')
party_obj = Pool().get('party.party')
res = False
warehouse = None
2010-08-19 22:22:44 +02:00
if Transaction().context.get('warehouse'):
warehouse = location_obj.browse(Transaction().context['warehouse'])
2010-08-19 22:22:44 +02:00
if Transaction().context.get('type', '') == 'inventory_in':
if warehouse:
res = warehouse.storage_location.id
2010-08-19 22:22:44 +02:00
elif Transaction().context.get('type', '') == 'inventory_out':
if warehouse:
res = warehouse.output_location.id
2010-08-19 22:22:44 +02:00
elif Transaction().context.get('type', '') == 'incoming':
if warehouse:
res = warehouse.input_location.id
2010-08-19 22:22:44 +02:00
elif Transaction().context.get('type', '') == 'outgoing':
if Transaction().context.get('customer'):
customer = party_obj.browse(Transaction().context['customer'])
res = customer.customer_location.id
2010-08-19 22:22:44 +02:00
if Transaction().context.get('to_location'):
res = Transaction().context['to_location']
return res
2008-04-17 02:31:55 +02:00
2010-08-19 22:22:44 +02:00
def default_from_location(self):
2011-07-03 13:19:14 +02:00
location_obj = Pool().get('stock.location')
party_obj = Pool().get('party.party')
res = False
warehouse = None
2010-08-19 22:22:44 +02:00
if Transaction().context.get('warehouse'):
warehouse = location_obj.browse(Transaction().context['warehouse'])
2010-08-19 22:22:44 +02:00
if Transaction().context.get('type', '') == 'inventory_in':
if warehouse:
res = warehouse.input_location.id
2010-08-19 22:22:44 +02:00
elif Transaction().context.get('type', '') == 'inventory_out':
if warehouse:
res = warehouse.storage_location.id
2010-08-19 22:22:44 +02:00
elif Transaction().context.get('type', '') == 'outgoing':
if warehouse:
res = warehouse.output_location.id
2010-08-19 22:22:44 +02:00
elif Transaction().context.get('type', '') == 'incoming':
if Transaction().context.get('supplier'):
supplier = party_obj.browse(Transaction().context['supplier'])
res = supplier.supplier_location.id
2010-08-19 22:22:44 +02:00
elif Transaction().context.get('customer'):
customer = party_obj.browse(Transaction().context['customer'])
2009-01-27 15:15:01 +01:00
res = customer.customer_location.id
2010-08-19 22:22:44 +02:00
if Transaction().context.get('from_location'):
res = Transaction().context['from_location']
return res
2008-04-17 02:31:55 +02:00
2010-08-19 22:22:44 +02:00
def default_state(self):
2008-04-08 14:27:53 +02:00
return 'draft'
2010-08-19 22:22:44 +02:00
def default_company(self):
return Transaction().context.get('company') or False
2008-05-09 12:32:46 +02:00
2010-08-19 22:22:44 +02:00
def default_currency(self):
2011-07-03 13:19:14 +02:00
company_obj = Pool().get('company.company')
currency_obj = Pool().get('currency.currency')
2010-08-19 22:22:44 +02:00
company = Transaction().context.get('company')
if company:
company = company_obj.browse(company)
return company.currency.id
2008-05-13 11:49:37 +02:00
return False
2008-04-08 14:27:53 +02:00
2010-08-19 22:22:44 +02:00
def on_change_with_unit_digits(self, vals):
2011-07-03 13:19:14 +02:00
uom_obj = Pool().get('product.uom')
if vals.get('uom'):
2010-08-19 22:22:44 +02:00
uom = uom_obj.browse(vals['uom'])
return uom.digits
return 2
2010-08-19 22:22:44 +02:00
def get_unit_digits(self, ids, name):
res = {}
2010-08-19 22:22:44 +02:00
for move in self.browse(ids):
res[move.id] = move.uom.digits
return res
2010-08-19 22:22:44 +02:00
def on_change_product(self, vals):
2011-07-03 13:19:14 +02:00
pool = Pool()
product_obj = pool.get('product.product')
uom_obj = pool.get('product.uom')
currency_obj = pool.get('currency.currency')
company_obj = pool.get('company.company')
location_obj = pool.get('stock.location')
res = {
'unit_price': Decimal('0.0'),
}
if vals.get('product'):
2010-08-19 22:22:44 +02:00
product = product_obj.browse(vals['product'])
res['uom'] = product.default_uom.id
res['uom.rec_name'] = product.default_uom.rec_name
res['unit_digits'] = product.default_uom.digits
to_location = None
if vals.get('to_location'):
2010-08-19 22:22:44 +02:00
to_location = location_obj.browse(vals['to_location'])
if to_location and to_location.type == 'storage':
unit_price = product.cost_price
if vals.get('uom') and vals['uom'] != product.default_uom.id:
2010-08-19 22:22:44 +02:00
uom = uom_obj.browse(vals['uom'])
unit_price = uom_obj.compute_price(product.default_uom,
unit_price, uom)
if vals.get('currency') and vals.get('company'):
2010-08-19 22:22:44 +02:00
currency = currency_obj.browse(vals['currency'])
company = company_obj.browse(vals['company'])
unit_price = currency_obj.compute(company.currency,
unit_price, currency, round=False)
res['unit_price'] = unit_price
return res
2010-08-19 22:22:44 +02:00
def on_change_uom(self, vals):
2011-07-03 13:19:14 +02:00
pool = Pool()
product_obj = pool.get('product.product')
uom_obj = pool.get('product.uom')
currency_obj = pool.get('currency.currency')
company_obj = pool.get('company.company')
location_obj = pool.get('stock.location')
res = {
'unit_price': Decimal('0.0'),
}
if vals.get('product'):
2010-08-19 22:22:44 +02:00
product = product_obj.browse(vals['product'])
to_location = None
if vals.get('to_location'):
2010-08-19 22:22:44 +02:00
to_location = location_obj.browse(vals['to_location'])
if to_location and to_location.type == 'storage':
unit_price = product.cost_price
if vals.get('uom') and vals['uom'] != product.default_uom.id:
2010-08-19 22:22:44 +02:00
uom = uom_obj.browse(vals['uom'])
unit_price = uom_obj.compute_price(product.default_uom,
unit_price, uom)
if vals.get('currency') and vals.get('company'):
2010-08-19 22:22:44 +02:00
currency = currency_obj.browse(vals['currency'])
company = company_obj.browse(vals['company'])
unit_price = currency_obj.compute(company.currency,
unit_price, currency, round=False)
res['unit_price'] = unit_price
return res
2010-08-19 22:22:44 +02:00
def default_unit_price_required(self):
from_location = self.default_from_location()
to_location = self.default_to_location()
vals = {
'from_location': from_location,
'to_location': to_location,
}
2010-08-19 22:22:44 +02:00
return self.on_change_with_unit_price_required(vals)
2010-08-19 22:22:44 +02:00
def on_change_with_unit_price_required(self, vals):
2011-07-03 13:19:14 +02:00
location_obj = Pool().get('stock.location')
if vals.get('from_location'):
2010-08-19 22:22:44 +02:00
from_location = location_obj.browse(vals['from_location'])
if from_location.type == 'supplier':
return True
if vals.get('to_location'):
2010-08-19 22:22:44 +02:00
to_location = location_obj.browse(vals['to_location'])
if to_location.type == 'customer':
return True
return False
2010-08-19 22:22:44 +02:00
def get_unit_price_required(self, ids, name):
res = {}
2010-08-19 22:22:44 +02:00
for move in self.browse(ids):
res[move.id] = False
if move.from_location.type == 'supplier':
res[move.id] = True
if move.to_location.type == 'customer':
res[move.id] = True
return res
2010-08-19 22:22:44 +02:00
def check_product_type(self, ids):
for move in self.browse(ids):
if move.product.type == 'service':
return False
return True
def check_period_closed(self, ids):
2011-07-03 13:19:14 +02:00
period_obj = Pool().get('stock.period')
period_ids = period_obj.search([
('state', '=', 'closed'),
], order=[('date', 'DESC')], limit=1)
if period_ids:
period, = period_obj.browse(period_ids)
for move in self.browse(ids):
date = (move.effective_date if move.effective_date
else move.planned_date)
if date and date < period.date:
return False
return True
2010-08-19 22:22:44 +02:00
def get_rec_name(self, ids, name):
if not ids:
return {}
res = {}
2010-08-19 22:22:44 +02:00
moves = self.browse(ids)
2008-11-05 12:00:37 +01:00
for m in moves:
res[m.id] = "%s%s %s" % (m.quantity, m.uom.symbol, m.product.rec_name)
return res
2010-08-19 22:22:44 +02:00
def search_rec_name(self, name, clause):
return [('product',) + clause[1:]]
2010-08-19 22:22:44 +02:00
def search(self, args, offset=0, limit=None, order=None, count=False,
query_string=False):
2011-07-03 13:19:14 +02:00
location_obj = Pool().get('stock.location')
args = args[:]
2008-09-04 18:29:14 +02:00
def process_args(args):
i = 0
while i < len(args):
#add test for xmlrpc that doesn't handle tuple
if (isinstance(args[i], tuple) or \
(isinstance(args[i], list) and len(args[i]) > 2 and \
args[i][1] in OPERATORS)) and \
args[i][0] == 'to_location_warehouse':
2008-09-04 18:29:14 +02:00
location_id = False
if args[i][2]:
2010-08-19 22:22:44 +02:00
location = location_obj.browse(args[i][2])
2008-09-04 18:29:14 +02:00
if location.type == 'warehouse':
location_id = location.input_location.id
args[i] = ('to_location', args[i][1], location_id)
elif isinstance(args[i], list):
process_args(args[i])
2008-09-04 18:29:14 +02:00
i += 1
process_args(args)
2010-08-19 22:22:44 +02:00
return super(Move, self).search(args, offset=offset, limit=limit,
order=order, count=count, query_string=query_string)
2010-08-19 22:22:44 +02:00
def _update_product_cost_price(self, product_id, quantity, uom, unit_price,
currency, company, date):
2008-10-23 17:22:25 +02:00
"""
Update the cost price on the given product
:param product_id: the id of the product
:param quantity: the quantity of the product, positive if incoming and
negative if outgoing
:param uom: the uom id or a BrowseRecord of the uom
:param unit_price: the unit price of the product
:param currency: the currency of the unit price
:param company: the company id ot a BrowseRecord of the company
:param date: the date for the currency rate calculation
2008-10-23 17:22:25 +02:00
"""
2011-07-03 13:19:14 +02:00
pool = Pool()
uom_obj = pool.get('product.uom')
product_obj = pool.get('product.product')
product_template_obj = pool.get('product.template')
location_obj = pool.get('stock.location')
currency_obj = pool.get('currency.currency')
company_obj = pool.get('company.company')
date_obj = pool.get('ir.date')
2008-10-23 17:22:25 +02:00
if isinstance(uom, (int, long)):
2010-08-19 22:22:44 +02:00
uom = uom_obj.browse(uom)
if isinstance(currency, (int, long)):
currency = currency_obj.browse(currency)
if isinstance(company, (int, long)):
2010-08-19 22:22:44 +02:00
company = company_obj.browse(company)
2008-10-23 17:22:25 +02:00
2010-08-19 22:22:44 +02:00
context = {}
context['locations'] = location_obj.search([
('type', '=', 'storage'),
])
context['stock_date_end'] = date_obj.today()
with Transaction().set_context(context):
product = product_obj.browse(product_id)
qty = uom_obj.compute_qty(uom, quantity, product.default_uom)
2008-10-23 17:22:25 +02:00
qty = Decimal(str(qty))
2008-10-31 14:15:20 +01:00
product_qty = Decimal(str(product.template.quantity))
# convert wrt currency
with Transaction().set_context(date=date):
unit_price = currency_obj.compute(currency.id, unit_price,
company.currency.id, round=False)
# convert wrt to the uom
2010-08-19 22:22:44 +02:00
unit_price = uom_obj.compute_price(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
if hasattr(product_obj, 'cost_price'):
digits = product_obj.cost_price.digits
else:
digits = product_template_obj.cost_price.digits
new_cost_price = new_cost_price.quantize(
Decimal(str(10.0**-digits[1])))
2010-08-19 22:22:44 +02:00
with Transaction().set_user(0, set_context=True):
product_obj.write(product.id, {
'cost_price': new_cost_price,
})
2008-10-23 17:22:25 +02:00
def _get_internal_quantity(self, quantity, uom, product):
2011-07-03 13:19:14 +02:00
uom_obj = Pool().get('product.uom')
internal_quantity = uom_obj.compute_qty(uom, quantity,
product.default_uom, round=True)
return internal_quantity
2010-08-19 22:22:44 +02:00
def create(self, vals):
2011-07-03 13:19:14 +02:00
pool = Pool()
location_obj = pool.get('stock.location')
product_obj = pool.get('product.product')
uom_obj = pool.get('product.uom')
date_obj = pool.get('ir.date')
2008-10-23 17:22:25 +02:00
today = date_obj.today()
vals = vals.copy()
effective_date = vals.get('effective_date') or today
product = product_obj.browse(vals['product'])
if vals.get('state') == 'done':
vals['effective_date'] = effective_date
currency_id = vals.get('currency', self.default_currency())
company_id = vals.get('company', self.default_company())
2010-08-19 22:22:44 +02:00
from_location = location_obj.browse(vals['from_location'])
to_location = location_obj.browse(vals['to_location'])
if from_location.type == 'supplier' \
and product.cost_price_method == 'average':
2010-08-19 22:22:44 +02:00
self._update_product_cost_price(vals['product'],
vals['quantity'], vals['uom'], vals['unit_price'],
currency_id, company_id, effective_date)
if to_location.type == 'supplier' \
and product.cost_price_method == 'average':
2010-08-19 22:22:44 +02:00
self._update_product_cost_price(vals['product'],
-vals['quantity'], vals['uom'], vals['unit_price'],
currency_id, company_id, effective_date)
2009-04-02 22:51:38 +02:00
if not vals.get('cost_price'):
2009-04-06 13:23:03 +02:00
# Re-read product to get the updated cost_price
2010-08-19 22:22:44 +02:00
product = product_obj.browse(vals['product'])
vals['cost_price'] = product.cost_price
2009-01-26 19:08:38 +01:00
elif vals.get('state') == 'assigned':
vals['effective_date'] = effective_date
uom = uom_obj.browse(vals['uom'])
internal_quantity = self._get_internal_quantity(vals['quantity'],
uom, product)
vals['internal_quantity'] = internal_quantity
2010-08-19 22:22:44 +02:00
return super(Move, self).create(vals)
2008-10-23 17:22:25 +02:00
2010-08-19 22:22:44 +02:00
def write(self, ids, vals):
2011-07-03 13:19:14 +02:00
date_obj = Pool().get('ir.date')
2008-05-19 17:50:15 +02:00
if isinstance(ids, (int, long)):
ids = [ids]
today = date_obj.today()
effective_date = vals.get('effective_date') or today
2008-05-19 17:50:15 +02:00
moves = self.browse(ids)
2008-05-13 13:11:53 +02:00
if 'state' in vals:
for move in moves:
2008-10-23 17:22:25 +02:00
if vals['state'] == 'cancel':
2008-10-23 22:55:06 +02:00
vals['effective_date'] = False
if move.from_location.type == 'supplier' \
and move.state != 'cancel' \
and move.product.cost_price_method == 'average':
2010-08-19 22:22:44 +02:00
self._update_product_cost_price(move.product.id,
-move.quantity, move.uom, move.unit_price,
move.currency, move.company, today)
if move.to_location.type == 'supplier' \
and move.state != 'cancel' \
and move.product.cost_price_method == 'average':
2010-08-19 22:22:44 +02:00
self._update_product_cost_price(move.product.id,
move.quantity, move.uom, move.unit_price,
move.currency, move.company, today)
2008-10-25 16:24:00 +02:00
2008-10-23 17:22:25 +02:00
elif vals['state'] == 'draft':
if move.state == 'done':
2010-08-19 22:22:44 +02:00
self.raise_user_error('set_state_draft')
2008-05-13 13:11:53 +02:00
elif vals['state'] == 'assigned':
if move.state in ('cancel', 'done'):
2010-08-19 22:22:44 +02:00
self.raise_user_error('set_state_assigned')
vals['effective_date'] = effective_date
2008-05-13 13:11:53 +02:00
elif vals['state'] == 'done':
if move.state in ('cancel'):
2010-08-19 22:22:44 +02:00
self.raise_user_error('set_state_done')
vals['effective_date'] = effective_date
2008-05-13 13:11:53 +02:00
if move.from_location.type == 'supplier' \
and move.state != 'done' \
and move.product.cost_price_method == 'average':
2010-08-19 22:22:44 +02:00
self._update_product_cost_price(move.product.id,
move.quantity, move.uom, move.unit_price,
move.currency, move.company, effective_date)
if move.to_location.type == 'supplier' \
and move.state != 'done' \
and move.product.cost_price_method == 'average':
2010-08-19 22:22:44 +02:00
self._update_product_cost_price( move.product.id,
-move.quantity, move.uom, move.unit_price,
move.currency, move.company, effective_date)
if reduce(lambda x, y: x or y in vals, ('product', 'uom', 'quantity',
'from_location', 'to_location', 'company', 'unit_price',
'currency'), False):
for move in moves:
if move.state in ('assigned', 'done', 'cancel'):
self.raise_user_error('modify_assigned_done_cancel')
if reduce(lambda x, y: x or y in vals,
('planned_date', 'effective_date'), False):
for move in moves:
if move.state in ('done', 'cancel'):
self.raise_user_error('modify_assigned_done_cancel')
2010-08-19 22:22:44 +02:00
res = super(Move, self).write(ids, vals)
2008-05-13 13:11:53 +02:00
if vals.get('state', '') == 'done':
#Re-read the move because cost_price has been updated
2010-08-19 22:22:44 +02:00
for move in self.browse(ids):
if not move.cost_price:
2010-08-19 22:22:44 +02:00
self.write(move.id, {
'cost_price': move.product.cost_price,
2010-08-19 22:22:44 +02:00
})
for move in self.browse(ids):
internal_quantity = self._get_internal_quantity(move.quantity,
move.uom, move.product)
if internal_quantity != move.internal_quantity:
# Use super to avoid infinite loop
super(Move, self).write(move.id, {
'internal_quantity': internal_quantity,
})
return res
2010-08-19 22:22:44 +02:00
def delete(self, ids):
for move in self.browse(ids):
2008-07-11 00:27:42 +02:00
if move.state not in ('draft', 'cancel'):
2010-08-19 22:22:44 +02:00
self.raise_user_error('del_draft_cancel')
return super(Move, self).delete(ids)
2010-08-19 22:22:44 +02:00
def pick_product(self, move, location_quantities):
"""
2009-01-26 18:59:29 +01:00
Pick the product across the location. Naive (fast) implementation.
:param move: a BrowseRecord of stock.move
:param location_quantities: a list of tuple (location, available_qty)
where location is a BrowseRecord of stock.location.
2009-01-26 18:59:29 +01:00
:return: a list of tuple (location, quantity) for quantities
that can be picked
"""
to_pick = []
needed_qty = move.quantity
for location, available_qty in location_quantities.iteritems():
# Ignore available_qty when too small
if available_qty < move.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:
if move.product.type == "consumable":
to_pick.append((move.from_location, needed_qty))
return to_pick
2009-01-26 18:59:29 +01:00
return to_pick
2010-08-19 22:22:44 +02:00
def assign_try(self, moves):
'''
Try to assign moves.
2009-01-26 18:59:29 +01:00
It will split the moves to assign as much possible.
:param moves: a BrowseRecordList of stock.move to assign
:return: True if succeed or False if not
'''
2011-07-03 13:19:14 +02:00
pool = Pool()
product_obj = pool.get('product.product')
uom_obj = pool.get('product.uom')
date_obj = pool.get('ir.date')
location_obj = pool.get('stock.location')
2010-08-19 22:22:44 +02:00
Transaction().cursor.lock(self._table)
2010-08-19 22:22:44 +02:00
location_ids = location_obj.search([
('parent', 'child_of', [x.from_location.id for x in moves]),
2010-08-19 22:22:44 +02:00
])
with Transaction().set_context(
stock_date_end=date_obj.today(),
stock_assign=True):
pbl = product_obj.products_by_location(location_ids=location_ids,
product_ids=[m.product.id for m in moves])
success = True
for move in moves:
if move.state != 'draft':
continue
to_location = move.to_location
location_qties = {}
2010-08-19 22:22:44 +02:00
child_ids = location_obj.search([
('parent', 'child_of', [move.from_location.id]),
2010-08-19 22:22:44 +02:00
])
for location in location_obj.browse(child_ids):
if (location.id, move.product.id) in pbl:
location_qties[location] = uom_obj.compute_qty(
2010-08-19 22:22:44 +02:00
move.product.default_uom,
pbl[(location.id, move.product.id)], move.uom,
round=False)
2010-08-19 22:22:44 +02:00
to_pick = self.pick_product(move, 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
2010-08-19 22:22:44 +02:00
self.write(move.id, {
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,
'state': 'assigned',
}
if first:
2010-08-19 22:22:44 +02:00
self.write(move.id, values)
first = False
else:
2010-08-19 22:22:44 +02:00
move_id = self.copy(move.id, default=values)
2010-08-19 22:22:44 +02:00
qty_default_uom = uom_obj.compute_qty(move.uom, qty,
move.product.default_uom, round=False)
pbl[(from_location.id, move.product.id)] = \
2009-01-27 14:53:37 +01:00
pbl.get((from_location.id, move.product.id), 0.0) - qty_default_uom
pbl[(to_location.id, move.product.id)]= \
2009-01-27 14:53:37 +01:00
pbl.get((to_location.id, move.product.id), 0.0) + qty_default_uom
return success
2008-04-08 14:27:53 +02:00
Move()