Files
tryton/inventory.py
T

630 lines
21 KiB
Python
Raw Normal View History

2014-12-02 12:56:04 +01: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.
from sql import Null
2018-12-29 14:20:29 +01:00
from trytond.i18n import gettext
from trytond.model import Workflow, Model, ModelView, ModelSQL, fields, Check
2018-12-29 14:20:29 +01:00
from trytond.model.exceptions import AccessError
2018-07-27 17:07:31 +02:00
from trytond.pyson import Eval, Bool, If
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
2018-07-27 17:07:31 +02:00
from trytond.wizard import Wizard, StateView, StateTransition, Button
2008-05-06 11:24:28 +02:00
2018-12-29 14:20:29 +01:00
from .exceptions import InventoryValidationError, InventoryCountWarning
2018-07-27 17:07:31 +02:00
__all__ = ['Inventory', 'InventoryLine',
'Count', 'CountSearch', 'CountQuantity']
2012-09-24 22:18:17 +02:00
2008-05-07 02:18:29 +02:00
STATES = {
2015-06-28 23:45:31 +02:00
'readonly': Eval('state') != 'draft',
2008-05-07 02:18:29 +02:00
}
2011-08-04 16:57:42 +02:00
DEPENDS = ['state']
2016-06-30 18:54:34 +02:00
INVENTORY_STATES = [
('draft', 'Draft'),
('done', 'Done'),
('cancel', 'Canceled'),
]
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'
2016-03-22 17:00:58 +01:00
_rec_name = 'number'
2018-12-04 12:25:45 +01:00
number = fields.Char('Number', readonly=True,
help="The main identifier for the 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={
2015-06-28 23:45:31 +02:00
'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
2011-08-04 16:57:42 +02:00
},
2018-12-04 12:25:45 +01:00
depends=['state'],
help="The location inventoried.")
date = fields.Date('Date', required=True, states={
2015-06-28 23:45:31 +02:00
'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
2011-08-04 16:57:42 +02:00
},
2018-12-04 12:25:45 +01:00
depends=['state'],
help="The date of the stock count.")
2008-05-07 14:56:54 +02:00
lost_found = fields.Many2One(
'stock.location', 'Lost and Found', required=True,
2018-12-04 12:25:45 +01:00
domain=[('type', '=', 'lost_found')], states=STATES, depends=DEPENDS,
help="Used for the balancing entries needed when the stock is "
"corrected.")
2008-05-06 11:24:28 +02:00
lines = fields.One2Many(
'stock.inventory.line', 'inventory', 'Lines',
states={
'readonly': (STATES['readonly'] | ~Eval('location')
| ~Eval('date')),
},
depends=['location', 'date'] + DEPENDS)
empty_quantity = fields.Selection([
(None, ""),
('keep', "Keep"),
('empty', "Empty"),
2018-03-19 16:14:01 +01:00
], "Empty Quantity", states=STATES, depends=DEPENDS,
help="How lines without quantity are handled.")
2010-01-20 11:04:28 +01:00
company = fields.Many2One('company.company', 'Company', required=True,
states={
2015-06-28 23:45:31 +02:00
'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
2011-08-04 16:57:42 +02:00
},
2018-12-04 12:25:45 +01:00
depends=['state'],
help="The company the inventory is associated with.")
2016-06-30 18:54:34 +02:00
state = fields.Selection(
2018-12-04 12:25:45 +01:00
INVENTORY_STATES, 'State', readonly=True, select=True,
help="The current state of the inventory.")
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._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']),
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
'cancel': {
'invisible': Eval('state').in_(['cancel', 'done']),
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
'complete_lines': {
'readonly': Eval('state') != 'draft',
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
2018-07-27 17:07:31 +02:00
'count': {
'readonly': Eval('state') != 'draft',
'depends': ['state'],
},
2012-03-25 00:18:19 +01:00
})
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)
2018-07-24 22:01:08 +02:00
table = cls.__table_handler__(module_name)
# Add index on create_date
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
2019-09-20 11:47:15 +02:00
@classmethod
def view_attributes(cls):
return [
('/tree', 'visual', If(Eval('state') == 'cancel', 'muted', '')),
]
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':
2018-12-29 14:20:29 +01:00
raise AccessError(
gettext('stock.msg_inventory_delete_cancel',
inventory=inventory.rec_name))
2012-09-24 22:18:17 +02:00
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')
2014-12-09 23:33:17 +01:00
def confirm(cls, inventories):
2013-01-01 22:24:15 +01:00
Move = Pool().get('stock.move')
2013-11-28 23:21:45 +01:00
moves = []
2012-09-24 22:18:17 +02:00
for inventory in inventories:
2013-06-21 23:24:17 +02:00
keys = set()
2012-03-25 00:18:19 +01:00
for line in inventory.lines:
2013-06-21 23:24:17 +02:00
key = line.unique_key
if key in keys:
2018-12-29 14:20:29 +01:00
raise InventoryValidationError(
gettext('stock.msg_inventory_line_unique',
line=line.rec_name,
inventory=inventory.rec_name))
2013-06-21 23:24:17 +02:00
keys.add(key)
2013-11-28 23:21:45 +01:00
move = line.get_move()
if move:
moves.append(move)
if moves:
2014-11-01 11:54:45 +01:00
Move.save(moves)
2013-11-28 23:21:45 +01:00
Move.do(moves)
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')
2014-12-09 23:33:17 +01:00
def cancel(cls, inventories):
2012-09-24 22:18:17 +02:00
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
@classmethod
2016-03-22 17:00:58 +01:00
def create(cls, vlist):
pool = Pool()
Sequence = pool.get('ir.sequence')
Configuration = pool.get('stock.configuration')
config = Configuration(1)
vlist = [x.copy() for x in vlist]
for values in vlist:
if values.get('number') is None:
values['number'] = Sequence.get_id(
config.inventory_sequence.id)
inventories = super(Inventory, cls).create(vlist)
cls.complete_lines(inventories, fill=False)
return inventories
@classmethod
def write(cls, inventories, values):
super(Inventory, cls).write(inventories, values)
cls.complete_lines(inventories, fill=False)
2012-09-24 22:18:17 +02:00
@classmethod
def copy(cls, inventories, default=None):
pool = Pool()
Date = pool.get('ir.date')
2008-12-05 13:54:13 +01:00
2008-11-04 23:38:48 +01:00
if default is None:
default = {}
2018-09-01 01:02:04 +02:00
else:
default = default.copy()
default.setdefault('date', Date.today())
default.setdefault('lines.moves', None)
default.setdefault('number', None)
2008-12-05 13:54:13 +01:00
2018-09-01 01:02:04 +02:00
new_inventories = super().copy(inventories, default=default)
cls.complete_lines(new_inventories, fill=False)
2012-09-24 22:18:17 +02:00
return new_inventories
2008-12-05 13:54:13 +01:00
2012-12-25 11:28:05 +01:00
@staticmethod
def grouping():
return ('product',)
@classmethod
@ModelView.button
def complete_lines(cls, inventories, fill=True):
'''
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
grouping = cls.grouping()
2016-06-23 09:40:00 +02:00
to_create, to_write = [], []
2008-05-08 14:16:28 +02:00
for inventory in inventories:
# Once done computation is wrong because include created moves
if inventory.state == 'done':
continue
# Compute product quantities
if fill:
product_ids = None
else:
product_ids = [l.product.id for l in inventory.lines]
2010-08-19 22:22:44 +02:00
with Transaction().set_context(stock_date_end=inventory.date):
pbl = Product.products_by_location(
[inventory.location.id],
grouping=grouping,
grouping_filter=(product_ids,))
# Index some data
product2type = {}
product2consumable = {}
2012-09-24 22:18:17 +02:00
for product in Product.browse([line[1] for line in pbl]):
product2type[product.id] = product.type
product2consumable[product.id] = product.consumable
# Update existing lines
for line in inventory.lines:
2019-09-19 14:04:54 +02:00
if line.product.type != 'goods':
2012-09-24 22:18:17 +02:00
Line.delete([line])
2009-09-23 16:42:40 +02:00
continue
key = (inventory.location.id,) + line.unique_key
if key in pbl:
quantity = pbl.pop(key)
else:
quantity = 0.0
values = line.update_values4complete(quantity)
if values:
2016-06-23 09:40:00 +02:00
to_write.extend(([line], values))
if not fill:
continue
# Create lines if needed
2018-06-26 19:11:55 +02:00
for key, quantity in pbl.items():
product_id = key[grouping.index('product') + 1]
if (product2type[product_id] != 'goods'
2013-01-01 22:23:32 +01:00
or product2consumable[product_id]):
continue
if not quantity:
continue
values = Line.create_values4complete(inventory, quantity)
for i, fname in enumerate(grouping, 1):
values[fname] = key[i]
2013-01-06 22:18:13 +01:00
to_create.append(values)
2013-06-21 23:24:17 +02:00
if to_create:
Line.create(to_create)
2016-06-23 09:40:00 +02:00
if to_write:
Line.write(*to_write)
2008-11-04 23:38:48 +01:00
2018-07-27 17:07:31 +02:00
@classmethod
@ModelView.button_action('stock.wizard_inventory_count')
def count(cls, inventories):
cls.complete_lines(inventories)
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'
2016-06-30 18:54:34 +02:00
_states = {
'readonly': Eval('inventory_state') != 'draft',
}
_depends = ['inventory_state']
2008-11-04 23:38:48 +01:00
product = fields.Many2One('product.product', 'Product', required=True,
domain=[
('type', '=', 'goods'),
2016-06-30 18:54:34 +02:00
], states=_states, depends=_depends)
2018-12-04 12:25:45 +01:00
uom = fields.Function(fields.Many2One('product.uom', 'UOM',
help="The unit in which the quantity is specified."), 'get_uom')
2010-03-11 16:54:32 +01:00
unit_digits = fields.Function(fields.Integer('Unit Digits'),
'get_unit_digits')
expected_quantity = fields.Float('Expected Quantity', required=True,
digits=(16, Eval('unit_digits', 2)), readonly=True,
states={
'invisible': Eval('id', -1) < 0,
},
2018-12-04 12:25:45 +01:00
depends=['unit_digits'],
help="The quantity the system calculated should be in the location.")
quantity = fields.Float('Quantity',
2016-06-30 18:54:34 +02:00
digits=(16, Eval('unit_digits', 2)),
2018-12-04 12:25:45 +01:00
states=_states, depends=['unit_digits'] + _depends,
help="The actual quantity found in the location.")
2013-11-28 23:21:45 +01:00
moves = fields.One2Many('stock.move', 'origin', 'Moves', readonly=True)
2009-01-08 20:04:31 +01:00
inventory = fields.Many2One('stock.inventory', 'Inventory', required=True,
2016-06-30 18:54:34 +02:00
ondelete='CASCADE',
states={
'readonly': _states['readonly'] & Bool(Eval('inventory')),
},
2018-12-04 12:25:45 +01:00
depends=_depends,
help="The inventory the line belongs to.")
2016-06-30 18:54:34 +02:00
inventory_state = fields.Function(
fields.Selection(INVENTORY_STATES, 'Inventory State'),
'on_change_with_inventory_state')
2008-11-04 23:38:48 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
def __setup__(cls):
super(InventoryLine, cls).__setup__()
t = cls.__table__()
2012-09-24 22:18:17 +02:00
cls._sql_constraints += [
('check_line_qty_pos', Check(t, t.quantity >= 0),
2018-12-29 14:20:29 +01:00
'stock.msg_inventory_line_quantity_positive'),
2012-09-24 22:18:17 +02:00
]
cls._order.insert(0, ('product', 'ASC'))
2008-11-04 23:38:48 +01:00
2013-06-21 23:24:17 +02:00
@classmethod
def __register__(cls, module_name):
cursor = Transaction().connection.cursor()
2013-11-28 23:21:45 +01:00
pool = Pool()
Move = pool.get('stock.move')
sql_table = cls.__table__()
move_table = Move.__table__()
2013-06-21 23:24:17 +02:00
super(InventoryLine, cls).__register__(module_name)
2018-07-24 22:01:08 +02:00
table = cls.__table_handler__(module_name)
2013-06-21 23:24:17 +02:00
2013-11-28 23:21:45 +01:00
# Migration from 3.0: use Move origin
if table.column_exist('move'):
cursor.execute(*sql_table.select(sql_table.id, sql_table.move,
where=sql_table.move != Null))
2013-11-28 23:21:45 +01:00
for line_id, move_id in cursor.fetchall():
cursor.execute(*move_table.update(
columns=[move_table.origin],
values=['%s,%s' % (cls.__name__, line_id)],
where=move_table.id == move_id))
table.drop_column('move')
# Migration from 4.6: drop required on quantity
table.not_null_action('quantity', action='remove')
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.
2014-01-26 14:29:37 +01:00
@fields.depends('product')
2012-09-24 22:18:17 +02:00
def on_change_product(self):
2014-10-29 20:40:25 +01:00
self.unit_digits = 2
2012-09-24 22:18:17 +02:00
if self.product:
2014-10-29 20:40:25 +01:00
self.uom = self.product.default_uom
self.unit_digits = self.product.default_uom.digits
2008-11-04 23:38:48 +01:00
2016-06-30 18:54:34 +02:00
@fields.depends('inventory', '_parent_inventory.state')
def on_change_with_inventory_state(self, name=None):
if self.inventory:
return self.inventory.state
return 'draft'
2013-06-21 23:24:17 +02:00
def get_rec_name(self, name):
return self.product.rec_name
2017-07-26 23:43:27 +02:00
@classmethod
def search_rec_name(cls, name, clause):
2017-09-27 18:37:34 +02:00
return [('product.rec_name',) + tuple(clause[1:])]
2017-07-26 23:43:27 +02: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
2013-06-21 23:24:17 +02:00
@property
def unique_key(self):
key = []
for fname in self.inventory.grouping():
value = getattr(self, fname)
if isinstance(value, Model):
value = value.id
key.append(value)
return tuple(key)
2013-06-21 23:24:17 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
def cancel_move(cls, lines):
Move = Pool().get('stock.move')
2013-11-28 23:21:45 +01:00
moves = [m for l in lines for m in l.moves if l.moves]
Move.cancel(moves)
Move.delete(moves)
2008-11-04 23:38:48 +01:00
2013-11-28 23:21:45 +01:00
def get_move(self):
'''
2013-06-21 23:24:17 +02:00
Return Move instance for the inventory line
'''
2012-09-24 22:18:17 +02:00
pool = Pool()
Move = pool.get('stock.move')
Uom = pool.get('product.uom')
qty = self.quantity
if qty is None:
if self.inventory.empty_quantity is None:
2018-12-29 14:20:29 +01:00
raise InventoryValidationError(
gettext('stock.msg_inventory_missing_empty_quantity',
inventory=self.inventory.rec_name))
if self.inventory.empty_quantity == 'keep':
return
else:
qty = 0.0
2012-09-24 22:18:17 +02:00
delta_qty = Uom.compute_qty(self.uom,
self.expected_quantity - qty,
2012-09-24 22:18:17 +02:00
self.uom)
2008-11-04 23:38:48 +01:00
if delta_qty == 0.0:
return
2013-06-21 23:24:17 +02:00
from_location = self.inventory.location
to_location = self.inventory.lost_found
2008-11-04 23:38:48 +01:00
if delta_qty < 0:
(from_location, to_location, delta_qty) = \
(to_location, from_location, -delta_qty)
2013-11-28 23:21:45 +01:00
return Move(
2013-06-21 23:24:17 +02:00
from_location=from_location,
to_location=to_location,
quantity=delta_qty,
product=self.product,
uom=self.uom,
company=self.inventory.company,
effective_date=self.inventory.date,
2013-11-28 23:21:45 +01:00
origin=self,
2013-06-21 23:24:17 +02:00
)
2008-11-04 23:38:48 +01:00
def update_values4complete(self, quantity):
'''
Return update values to complete inventory
'''
2012-09-24 22:18:17 +02:00
values = {}
# if nothing changed, no update
2019-09-02 18:26:02 +02:00
if self.expected_quantity == quantity:
2012-09-24 22:18:17 +02:00
return values
values['expected_quantity'] = quantity
return values
2012-09-24 22:18:17 +02:00
@classmethod
def create_values4complete(cls, inventory, quantity):
'''
Return create values to complete inventory
'''
return {
'inventory': inventory.id,
'expected_quantity': quantity,
}
@classmethod
def delete(cls, lines):
for line in lines:
if line.inventory_state not in {'cancel', 'draft'}:
2018-12-29 14:20:29 +01:00
raise AccessError(
gettext('stock.msg_inventory_line_delete_cancel',
line=line.rec_name,
inventory=line.inventory.rec_name))
super(InventoryLine, cls).delete(lines)
2018-07-27 17:07:31 +02:00
class Count(Wizard):
"Stock Inventory Count"
__name__ = 'stock.inventory.count'
start_state = 'search'
search = StateView(
'stock.inventory.count.search',
'stock.inventory_count_search_view_form', [
Button("End", 'end', 'tryton-cancel'),
2018-08-19 20:02:58 +02:00
Button("Select", 'quantity', 'tryton-forward', default=True),
2018-07-27 17:07:31 +02:00
])
quantity = StateView(
'stock.inventory.count.quantity',
'stock.inventory_count_quantity_view_form', [
Button("Cancel", 'search', 'tryton-cancel'),
Button("Add", 'add', 'tryton-ok', default=True),
])
add = StateTransition()
def default_quantity(self, fields):
pool = Pool()
Inventory = pool.get('stock.inventory')
InventoryLine = pool.get('stock.inventory.line')
2018-12-29 14:20:29 +01:00
Warning = pool.get('res.user.warning')
2018-07-27 17:07:31 +02:00
context = Transaction().context
inventory = Inventory(context['active_id'])
values = {}
lines = InventoryLine.search(self.get_line_domain(inventory), limit=1)
if not lines:
warning_name = '%s.%s.count_create' % (
inventory, self.search.search)
2018-12-29 14:20:29 +01:00
if Warning.check(warning_name):
raise InventoryCountWarning(warning_name,
gettext('stock.msg_inventory_count_create_line',
search=self.search.search.rec_name))
2018-07-27 17:07:31 +02:00
line, = InventoryLine.create([self.get_line_values(inventory)])
else:
line, = lines
values['line'] = line.id
values['product'] = line.product.id
values['uom'] = line.uom.id
values['unit_digits'] = line.unit_digits
if line.uom.rounding == 1:
values['quantity_added'] = 1
return values
def get_line_domain(self, inventory):
pool = Pool()
Product = pool.get('product.product')
domain = [
('inventory', '=', inventory.id),
]
if isinstance(self.search.search, Product):
domain.append(('product', '=', self.search.search.id))
return domain
def get_line_values(self, inventory):
pool = Pool()
Product = pool.get('product.product')
InventoryLine = pool.get('stock.inventory.line')
values = InventoryLine.create_values4complete(inventory, 0)
if isinstance(self.search.search, Product):
values['product'] = self.search.search.id
return values
def transition_add(self):
if self.quantity.line and self.quantity.quantity_added:
line = self.quantity.line
if line.quantity:
line.quantity += self.quantity.quantity_added
else:
line.quantity = self.quantity.quantity_added
line.save()
return 'search'
class CountSearch(ModelView):
"Stock Inventory Count"
__name__ = 'stock.inventory.count.search'
search = fields.Reference(
"Search", [
('product.product', "Product"),
],
required=True,
domain=[If(Eval('search_model') == 'product.product',
[
('type', '=', 'goods'),
('consumable', '=', False),
],
[])],
2018-12-04 12:25:45 +01:00
depends=['search_model'],
help="The item that's counted.")
2018-07-27 17:07:31 +02:00
search_model = fields.Function(fields.Selection(
'get_search_models', "Search Model"),
'on_change_with_search_model')
@classmethod
def default_search(cls):
return 'product.product,-1'
@classmethod
def get_search_models(cls):
return cls.fields_get(['search'])['search']['selection']
@fields.depends('search')
def on_change_with_search_model(self, name=None):
if self.search:
return self.search.__name__
class CountQuantity(ModelView):
"Stock Inventory Count"
__name__ = 'stock.inventory.count.quantity'
line = fields.Many2One(
'stock.inventory.line', "Line", readonly=True, required=True)
product = fields.Many2One('product.product', "Product", readonly=True)
2018-12-04 12:25:45 +01:00
uom = fields.Many2One('product.uom', "UOM", readonly=True,
help="The unit in which the quantities are specified.")
total_quantity = fields.Float(
"Total Quantity", digits=(16, Eval('unit_digits', 2)),
2018-12-04 12:25:45 +01:00
readonly=True, depends=['unit_digits'],
help="The total amount of the line counted so far.")
2018-07-27 17:07:31 +02:00
quantity_added = fields.Float(
"Added Quantity", digits=(16, Eval('unit_digits', 2)), required=True,
2018-12-04 12:25:45 +01:00
depends=['unit_digits'],
help="The quantity to add to the existing count.")
2018-07-27 17:07:31 +02:00
unit_digits = fields.Integer("Unit Digits", readonly=True)
@fields.depends('quantity_added', 'line')
def on_change_quantity_added(self):
if self.line:
self.total_quantity = (
2018-07-27 17:07:31 +02:00
(self.line.quantity or 0) + (self.quantity_added or 0))