Files
tryton/inventory.py
T

334 lines
11 KiB
Python
Raw Normal View History

2008-10-08 12:19:58 +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.
2012-03-25 00:18:19 +01:00
from trytond.model import Workflow, ModelView, ModelSQL, fields
2010-01-20 11:04:28 +01:00
from trytond.pyson import Not, Equal, Eval, Or, Bool
from trytond.backend import TableHandler
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-05-06 11:24:28 +02:00
2012-09-24 22:18:17 +02:00
__all__ = ['Inventory', 'InventoryLine']
2008-05-07 02:18:29 +02:00
STATES = {
2010-01-20 11:04:28 +01:00
'readonly': Not(Equal(Eval('state'), 'draft')),
2008-05-07 02:18:29 +02:00
}
2011-08-04 16:57:42 +02:00
DEPENDS = ['state']
2008-05-06 11:24:28 +02:00
2008-05-08 17:33:44 +02:00
2012-03-25 00:18:19 +01:00
class Inventory(Workflow, ModelSQL, ModelView):
2008-05-06 11:24:28 +02:00
'Stock Inventory'
2012-09-24 22:18:17 +02:00
__name__ = 'stock.inventory'
2008-05-06 11:24:28 +02:00
location = fields.Many2One(
'stock.location', 'Location', required=True,
2008-11-11 23:33:38 +01:00
domain=[('type', '=', 'storage')], states={
2010-01-20 11:04:28 +01:00
'readonly': Or(Not(Equal(Eval('state'), 'draft')),
Bool(Eval('lines'))),
2011-08-04 16:57:42 +02:00
},
depends=['state', 'lines'])
date = fields.Date('Date', required=True, states={
2010-01-20 11:04:28 +01:00
'readonly': Or(Not(Equal(Eval('state'), 'draft')),
Bool(Eval('lines'))),
2011-08-04 16:57:42 +02:00
},
depends=['state', 'lines'])
2008-05-07 14:56:54 +02:00
lost_found = fields.Many2One(
'stock.location', 'Lost and Found', required=True,
2011-08-04 16:57:42 +02:00
domain=[('type', '=', 'lost_found')], states=STATES, depends=DEPENDS)
2008-05-06 11:24:28 +02:00
lines = fields.One2Many(
2011-08-04 16:57:42 +02:00
'stock.inventory.line', 'inventory', 'Lines', states=STATES,
depends=DEPENDS)
2010-01-20 11:04:28 +01:00
company = fields.Many2One('company.company', 'Company', required=True,
states={
'readonly': Or(Not(Equal(Eval('state'), 'draft')),
Bool(Eval('lines'))),
2011-08-04 16:57:42 +02:00
},
depends=['state', 'lines'])
2008-10-24 13:55:56 +02:00
state = fields.Selection([
2008-11-04 23:38:48 +01:00
('draft', 'Draft'),
2008-10-24 13:55:56 +02:00
('done', 'Done'),
2009-03-20 13:09:19 +01:00
('cancel', 'Canceled'),
2012-03-05 10:53:08 +01:00
], 'State', readonly=True, select=True)
2008-05-06 11:24:28 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
def __setup__(cls):
super(Inventory, cls).__setup__()
cls._order.insert(0, ('date', 'DESC'))
cls._error_messages.update({
'delete_cancel': 'Inventory "%s" must be cancelled before ' \
'deletion!',
})
2012-09-24 22:18:17 +02:00
cls._transitions |= set((
2012-03-25 00:18:19 +01:00
('draft', 'done'),
('draft', 'cancel'),
))
2012-09-24 22:18:17 +02:00
cls._buttons.update({
2012-03-25 00:18:19 +01:00
'confirm': {
'invisible': Eval('state').in_(['done', 'cancel']),
},
'cancel': {
'invisible': Eval('state').in_(['cancel', 'done']),
},
'complete_lines': {
'readonly': Eval('state') != 'draft',
},
})
2008-05-07 14:56:54 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
def __register__(cls, module_name):
super(Inventory, cls).__register__(module_name)
2010-08-19 22:22:44 +02:00
cursor = Transaction().cursor
# Add index on create_date
2012-09-24 22:18:17 +02:00
table = TableHandler(cursor, cls, module_name)
table.index_action('create_date', action='add')
2012-09-24 22:18:17 +02:00
@staticmethod
def default_state():
2008-11-04 23:38:48 +01:00
return 'draft'
2008-05-06 11:24:28 +02:00
2012-09-24 22:18:17 +02:00
@staticmethod
def default_date():
Date = Pool().get('ir.date')
return Date.today()
2008-05-07 14:56:54 +02:00
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-22 01:58:47 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
def default_lost_found(cls):
Location = Pool().get('stock.location')
locations = Location.search(cls.lost_found.domain)
if len(locations) == 1:
return locations[0].id
2008-11-11 23:33:38 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
def delete(cls, inventories):
# Cancel before delete
2012-09-24 22:18:17 +02:00
cls.cancel(inventories)
for inventory in inventories:
if inventory.state != 'cancel':
2012-09-24 22:18:17 +02:00
cls.raise_user_error('delete_cancel', inventory.rec_name)
super(Inventory, cls).delete(inventories)
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('done')
2012-09-24 22:18:17 +02:00
def confirm(self, inventories):
for inventory in inventories:
2012-03-25 00:18:19 +01:00
for line in inventory.lines:
2012-09-24 22:18:17 +02:00
line.create_move()
2008-10-08 12:19:58 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('cancel')
2012-09-24 22:18:17 +02:00
def cancel(self, inventories):
Line = Pool().get("stock.inventory.line")
Line.cancel_move([l for i in inventories for l in i.lines])
2008-05-07 14:56:54 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
def copy(cls, inventories, default=None):
pool = Pool()
Date = pool.get('ir.date')
Line = pool.get('stock.inventory.line')
2008-12-05 13:54:13 +01:00
2008-11-04 23:38:48 +01:00
if default is None:
default = {}
default = default.copy()
2012-09-24 22:18:17 +02:00
default['date'] = Date.today()
2012-03-27 10:44:04 +02:00
default['lines'] = None
2008-12-05 13:54:13 +01:00
2012-09-24 22:18:17 +02:00
new_inventories = []
for inventory in inventories:
new_inventory, = super(Inventory, cls).copy([inventory],
default=default)
Line.copy(inventory.lines,
default={
'inventory': new_inventory.id,
'move': None,
})
cls.complete_lines([new_inventory])
new_inventories.append(new_inventory)
return new_inventories
2008-12-05 13:54:13 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
def complete_lines(self, inventories):
'''
Complete or update the inventories
'''
2011-07-03 13:19:14 +02:00
pool = Pool()
2012-09-24 22:18:17 +02:00
Line = pool.get('stock.inventory.line')
Product = pool.get('product.product')
2008-05-08 14:16:28 +02:00
for inventory in inventories:
# Compute product quantities
2010-08-19 22:22:44 +02:00
with Transaction().set_context(stock_date_end=inventory.date):
2012-09-24 22:18:17 +02:00
pbl = Product.products_by_location([inventory.location.id])
# Index some data
product2uom = {}
product2type = {}
product2consumable = {}
2012-09-24 22:18:17 +02:00
for product in Product.browse([line[1] for line in pbl]):
2008-08-22 11:16:10 +02:00
product2uom[product.id] = product.default_uom.id
product2type[product.id] = product.type
product2consumable[product.id] = product.consumable
product_qty = {}
for (location, product), quantity in pbl.iteritems():
product_qty[product] = (quantity, product2uom[product])
# Update existing lines
for line in inventory.lines:
2009-09-23 16:42:40 +02:00
if not (line.product.active and
line.product.type == 'goods'
and not line.product.consumable):
2012-09-24 22:18:17 +02:00
Line.delete([line])
2009-09-23 16:42:40 +02:00
continue
if line.product.id in product_qty:
quantity, uom_id = product_qty.pop(line.product.id)
elif line.product.id in product2uom:
quantity, uom_id = 0.0, product2uom[line.product.id]
else:
quantity, uom_id = 0.0, line.product.default_uom.id
2012-09-24 22:18:17 +02:00
values = line.update_values4complete(quantity, uom_id)
if values:
2012-09-24 22:18:17 +02:00
Line.write([line], values)
# Create lines if needed
for product_id in product_qty:
if (product2type[product_id] != 'goods'
and not product2consumable[product_id]):
continue
quantity, uom_id = product_qty[product_id]
2012-09-24 22:18:17 +02:00
values = Line.create_values4complete(product_id, inventory,
quantity, uom_id)
Line.create(values)
2008-11-04 23:38:48 +01:00
2009-02-28 17:04:50 +01:00
class InventoryLine(ModelSQL, ModelView):
2008-11-04 23:38:48 +01:00
'Stock Inventory Line'
2012-09-24 22:18:17 +02:00
__name__ = 'stock.inventory.line'
2008-11-04 23:38:48 +01:00
_rec_name = 'product'
product = fields.Many2One('product.product', 'Product', required=True,
domain=[
('type', '=', 'goods'),
('consumable', '=', False),
],
on_change=['product'])
2010-03-11 16:54:32 +01:00
uom = fields.Function(fields.Many2One('product.uom', 'UOM'), 'get_uom')
unit_digits = fields.Function(fields.Integer('Unit Digits'),
'get_unit_digits')
expected_quantity = fields.Float('Expected Quantity', required=True,
2010-03-11 22:35:29 +01:00
digits=(16, Eval('unit_digits', 2)), readonly=True,
depends=['unit_digits'])
quantity = fields.Float('Quantity', required=True,
digits=(16, Eval('unit_digits', 2)), depends=['unit_digits'])
2008-11-04 23:38:48 +01:00
move = fields.Many2One('stock.move', 'Move', readonly=True)
2009-01-08 20:04:31 +01:00
inventory = fields.Many2One('stock.inventory', 'Inventory', required=True,
ondelete='CASCADE')
2008-11-04 23:38:48 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
def __setup__(cls):
super(InventoryLine, cls).__setup__()
cls._sql_constraints += [
2008-11-04 23:38:48 +01:00
('check_line_qty_pos',
'CHECK(quantity >= 0.0)', 'Line quantity must be positive!'),
('inventory_product_uniq', 'UNIQUE(inventory, product)',
'Product must be unique by inventory!'),
2012-09-24 22:18:17 +02:00
]
cls._order.insert(0, ('product', 'ASC'))
2008-11-04 23:38:48 +01:00
2012-09-24 22:18:17 +02:00
@staticmethod
def default_unit_digits():
2008-11-04 23:38:48 +01:00
return 2
2012-09-24 22:18:17 +02:00
@staticmethod
def default_expected_quantity():
return 0.
2012-09-24 22:18:17 +02:00
def on_change_product(self):
change = {}
change['unit_digits'] = 2
if self.product:
change['uom'] = self.product.default_uom.id
change['uom.rec_name'] = self.product.default_uom.rec_name
change['unit_digits'] = self.product.default_uom.digits
return change
2008-11-04 23:38:48 +01:00
2012-09-24 22:18:17 +02:00
def get_uom(self, name):
return self.product.default_uom.id
2008-11-04 23:38:48 +01:00
2012-09-24 22:18:17 +02:00
def get_unit_digits(self, name):
return self.product.default_uom.digits
2008-11-04 23:38:48 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
def cancel_move(cls, lines):
Move = Pool().get('stock.move')
Move.write([l.move for l in lines if l.move], {
2010-08-19 22:22:44 +02:00
'state': 'cancel',
})
2012-09-24 22:18:17 +02:00
Move.delete([l.move for l in lines if l.move])
cls.write([l for l in lines if l.move], {
2012-03-27 10:44:04 +02:00
'move': None,
2010-08-19 22:22:44 +02:00
})
2008-11-04 23:38:48 +01:00
2012-09-24 22:18:17 +02:00
def create_move(self):
'''
2012-09-24 22:18:17 +02:00
Create move for an inventory line and return id
'''
2012-09-24 22:18:17 +02:00
pool = Pool()
Move = pool.get('stock.move')
Uom = pool.get('product.uom')
2012-09-24 22:18:17 +02:00
delta_qty = Uom.compute_qty(self.uom,
self.expected_quantity - self.quantity,
self.uom)
2008-11-04 23:38:48 +01:00
if delta_qty == 0.0:
return
2012-09-24 22:18:17 +02:00
from_location = self.inventory.location.id
to_location = self.inventory.lost_found.id
2008-11-04 23:38:48 +01:00
if delta_qty < 0:
(from_location, to_location, delta_qty) = \
(to_location, from_location, -delta_qty)
2012-09-24 22:18:17 +02:00
move = Move.create({
2008-11-04 23:38:48 +01:00
'from_location': from_location,
'to_location': to_location,
'quantity': delta_qty,
2012-09-24 22:18:17 +02:00
'product': self.product.id,
'uom': self.uom.id,
'company': self.inventory.company.id,
2008-11-04 23:38:48 +01:00
'state': 'done',
2012-09-24 22:18:17 +02:00
'effective_date': self.inventory.date,
2010-08-19 22:22:44 +02:00
})
2012-09-24 22:18:17 +02:00
self.move = move
self.save()
return move.id
2008-11-04 23:38:48 +01:00
2012-09-24 22:18:17 +02:00
def update_values4complete(self, quantity, uom_id):
'''
Return update values to complete inventory
'''
2012-09-24 22:18:17 +02:00
values = {}
# if nothing changed, no update
2012-09-24 22:18:17 +02:00
if self.quantity == self.expected_quantity == quantity \
and self.uom.id == uom_id:
return values
values['expected_quantity'] = quantity
values['uom'] = uom_id
# update also quantity field if not edited
2012-09-24 22:18:17 +02:00
if self.quantity == self.expected_quantity:
values['quantity'] = max(quantity, 0.0)
return values
2012-09-24 22:18:17 +02:00
@classmethod
def create_values4complete(cls, product_id, inventory, quantity, uom_id):
'''
Return create values to complete inventory
'''
return {
'inventory': inventory.id,
'product': product_id,
'expected_quantity': quantity,
'quantity': max(quantity, 0.0),
'uom': uom_id,
}