[REF] getproductquantity
[odoo/odoo.git] / addons / stock / product.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 from openerp.osv import fields, osv
23 from openerp.tools.translate import _
24 import openerp.addons.decimal_precision as dp
25
26 class product_product(osv.osv):
27     _inherit = "product.product"
28
29     def _stock_move_count(self, cr, uid, ids, field_name, arg, context=None):
30         res = dict([(id, {'reception_count': 0, 'delivery_count': 0}) for id in ids])
31         move_pool=self.pool.get('stock.move')
32         moves = move_pool.read_group(cr, uid, [
33             ('product_id', 'in', ids),
34             ('picking_id.type', '=', 'in'),
35             ('state','in',('confirmed','assigned','pending'))
36         ], ['product_id'], ['product_id'])
37         for move in moves:
38             product_id = move['product_id'][0]
39             res[product_id]['reception_count'] = move['product_id_count']
40         moves = move_pool.read_group(cr, uid, [
41             ('product_id', 'in', ids),
42             ('picking_id.type', '=', 'out'),
43             ('state','in',('confirmed','assigned','pending'))
44         ], ['product_id'], ['product_id'])
45         for move in moves:
46             product_id = move['product_id'][0]
47             res[product_id]['delivery_count'] = move['product_id_count']
48         return res
49
50     def get_product_accounts(self, cr, uid, product_id, context=None):
51         """ To get the stock input account, stock output account and stock journal related to product.
52         @param product_id: product id
53         @return: dictionary which contains information regarding stock input account, stock output account and stock journal
54         """
55         if context is None:
56             context = {}
57         product_obj = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
58
59         stock_input_acc = product_obj.property_stock_account_input and product_obj.property_stock_account_input.id or False
60         if not stock_input_acc:
61             stock_input_acc = product_obj.categ_id.property_stock_account_input_categ and product_obj.categ_id.property_stock_account_input_categ.id or False
62
63         stock_output_acc = product_obj.property_stock_account_output and product_obj.property_stock_account_output.id or False
64         if not stock_output_acc:
65             stock_output_acc = product_obj.categ_id.property_stock_account_output_categ and product_obj.categ_id.property_stock_account_output_categ.id or False
66
67         journal_id = product_obj.categ_id.property_stock_journal and product_obj.categ_id.property_stock_journal.id or False
68         account_valuation = product_obj.categ_id.property_stock_valuation_account_id and product_obj.categ_id.property_stock_valuation_account_id.id or False
69         return {
70             'stock_account_input': stock_input_acc,
71             'stock_account_output': stock_output_acc,
72             'stock_journal': journal_id,
73             'property_stock_valuation_account_id': account_valuation
74         }
75
76     def do_change_standard_price(self, cr, uid, ids, datas, context=None):
77         """ Changes the Standard Price of Product and creates an account move accordingly.
78         @param datas : dict. contain default datas like new_price, stock_output_account, stock_input_account, stock_journal
79         @param context: A standard dictionary
80         @return:
81
82         """
83         location_obj = self.pool.get('stock.location')
84         move_obj = self.pool.get('account.move')
85         move_line_obj = self.pool.get('account.move.line')
86         if context is None:
87             context = {}
88
89         new_price = datas.get('new_price', 0.0)
90         stock_output_acc = datas.get('stock_output_account', False)
91         stock_input_acc = datas.get('stock_input_account', False)
92         journal_id = datas.get('stock_journal', False)
93         product_obj=self.browse(cr, uid, ids, context=context)[0]
94         account_valuation = product_obj.categ_id.property_stock_valuation_account_id
95         account_valuation_id = account_valuation and account_valuation.id or False
96         if not account_valuation_id: raise osv.except_osv(_('Error!'), _('Specify valuation Account for Product Category: %s.') % (product_obj.categ_id.name))
97         move_ids = []
98         loc_ids = location_obj.search(cr, uid,[('usage','=','internal')])
99         for rec_id in ids:
100             for location in location_obj.browse(cr, uid, loc_ids, context=context):
101                 c = context.copy()
102                 c.update({
103                     'location': location.id,
104                     'compute_child': False
105                 })
106
107                 product = self.browse(cr, uid, rec_id, context=c)
108                 qty = product.qty_available
109                 diff = product.standard_price - new_price
110                 if not diff: raise osv.except_osv(_('Error!'), _("No difference between standard price and new price!"))
111                 if qty:
112                     company_id = location.company_id and location.company_id.id or False
113                     if not company_id: raise osv.except_osv(_('Error!'), _('Please specify company in Location.'))
114                     #
115                     # Accounting Entries
116                     #
117                     if not journal_id:
118                         journal_id = product.categ_id.property_stock_journal and product.categ_id.property_stock_journal.id or False
119                     if not journal_id:
120                         raise osv.except_osv(_('Error!'),
121                             _('Please define journal '\
122                               'on the product category: "%s" (id: %d).') % \
123                                 (product.categ_id.name,
124                                     product.categ_id.id,))
125                     move_id = move_obj.create(cr, uid, {
126                                 'journal_id': journal_id,
127                                 'company_id': company_id
128                                 })
129
130                     move_ids.append(move_id)
131
132
133                     if diff > 0:
134                         if not stock_input_acc:
135                             stock_input_acc = product.\
136                                 property_stock_account_input.id
137                         if not stock_input_acc:
138                             stock_input_acc = product.categ_id.\
139                                     property_stock_account_input_categ.id
140                         if not stock_input_acc:
141                             raise osv.except_osv(_('Error!'),
142                                     _('Please define stock input account ' \
143                                             'for this product: "%s" (id: %d).') % \
144                                             (product.name,
145                                                 product.id,))
146                         amount_diff = qty * diff
147                         move_line_obj.create(cr, uid, {
148                                     'name': product.name,
149                                     'account_id': stock_input_acc,
150                                     'debit': amount_diff,
151                                     'move_id': move_id,
152                                     })
153                         move_line_obj.create(cr, uid, {
154                                     'name': product.categ_id.name,
155                                     'account_id': account_valuation_id,
156                                     'credit': amount_diff,
157                                     'move_id': move_id
158                                     })
159                     elif diff < 0:
160                         if not stock_output_acc:
161                             stock_output_acc = product.\
162                                 property_stock_account_output.id
163                         if not stock_output_acc:
164                             stock_output_acc = product.categ_id.\
165                                     property_stock_account_output_categ.id
166                         if not stock_output_acc:
167                             raise osv.except_osv(_('Error!'),
168                                     _('Please define stock output account ' \
169                                             'for this product: "%s" (id: %d).') % \
170                                             (product.name,
171                                                 product.id,))
172                         amount_diff = qty * -diff
173                         move_line_obj.create(cr, uid, {
174                                         'name': product.name,
175                                         'account_id': stock_output_acc,
176                                         'credit': amount_diff,
177                                         'move_id': move_id
178                                     })
179                         move_line_obj.create(cr, uid, {
180                                         'name': product.categ_id.name,
181                                         'account_id': account_valuation_id,
182                                         'debit': amount_diff,
183                                         'move_id': move_id
184                                     })
185
186             self.write(cr, uid, rec_id, {'standard_price': new_price})
187
188         return move_ids
189
190     def view_header_get(self, cr, user, view_id, view_type, context=None):
191         if context is None:
192             context = {}
193         res = super(product_product, self).view_header_get(cr, user, view_id, view_type, context)
194         if res: return res
195         if (context.get('active_id', False)) and (context.get('active_model') == 'stock.location'):
196             return _('Products: ')+self.pool.get('stock.location').browse(cr, user, context['active_id'], context).name
197         return res
198
199     def _get_locations_from_context(self, cr, uid, ids, context=None):
200         if context is None:
201             context = {}
202         location_obj = self.pool.get('stock.location')
203         warehouse_obj = self.pool.get('stock.warehouse')
204         shop_obj = self.pool.get('sale.shop')
205         if context.get('shop', False):
206             warehouse_id = shop_obj.read(cr, uid, int(context['shop']), ['warehouse_id'])['warehouse_id'][0]
207             if warehouse_id:
208                 context['warehouse'] = warehouse_id
209
210         if context.get('warehouse', False):
211             lot_id = warehouse_obj.read(cr, uid, int(context['warehouse']), ['lot_stock_id'])['lot_stock_id'][0]
212             if lot_id:
213                 context['location'] = lot_id
214
215         if context.get('location', False):
216             if type(context['location']) == type(1):
217                 location_ids = [context['location']]
218             elif type(context['location']) in (type(''), type(u'')):
219                 location_ids = location_obj.search(cr, uid, [('name','ilike',context['location'])], context=context)
220             else:
221                 location_ids = context['location']
222         else:
223             location_ids = []
224             wids = warehouse_obj.search(cr, uid, [], context=context)
225             if not wids:
226                 return False
227             for w in warehouse_obj.browse(cr, uid, wids, context=context):
228                 location_ids.append(w.lot_stock_id.id)
229
230         # build the list of ids of children of the location given by id
231         if context.get('compute_child',True):
232             child_location_ids = location_obj.search(cr, uid, [('location_id', 'child_of', location_ids)])
233             location_ids = child_location_ids or location_ids
234             
235         return location_ids
236
237
238     def _get_date_query(self, cr, uid, ids, context):
239         from_date = context.get('from_date',False)
240         to_date = context.get('to_date',False)
241         date_str = False
242         date_values = False
243         whereadd = []
244         
245         if from_date and to_date:
246             date_str = "date>=%s and date<=%s"
247             whereadd.append(tuple([from_date]))
248             whereadd.append(tuple([to_date]))
249         elif from_date:
250             date_str = "date>=%s"
251             whereadd.append(tuple([from_date]))
252         elif to_date:
253             date_str = "date<=%s"
254             whereadd.append(tuple([to_date]))
255         return (whereadd, date_str)
256
257
258
259
260     def get_product_available(self, cr, uid, ids, context=None):
261         """ Finds whether product is available or not in particular warehouse.
262         @return: Dictionary of values
263         """
264         if context is None:
265             context = {}
266         location_obj = self.pool.get('stock.location')
267         warehouse_obj = self.pool.get('stock.warehouse')
268         shop_obj = self.pool.get('sale.shop')
269         
270         states = context.get('states',[])
271         what = context.get('what',())
272         if not ids:
273             ids = self.search(cr, uid, [])
274         res = {}.fromkeys(ids, 0.0)
275         if not ids:
276             return res
277         #set_context: refactor code here
278         location_ids = self._getlocations_from_context(cr, uid, ids, context=context)
279         if not location_ids: #in case of no locations, query will be empty anyways
280             return res
281         
282         # this will be a dictionary of the product UoM by product id
283         product2uom = {}
284         uom_ids = []
285         for product in self.read(cr, uid, ids, ['uom_id'], context=context):
286             product2uom[product['id']] = product['uom_id'][0]
287             uom_ids.append(product['uom_id'][0])
288         # this will be a dictionary of the UoM resources we need for conversion purposes, by UoM id
289         uoms_o = {}
290         for uom in self.pool.get('product.uom').browse(cr, uid, uom_ids, context=context):
291             uoms_o[uom.id] = uom
292
293         results = []
294         results2 = []
295
296         where = [tuple(location_ids),tuple(location_ids),tuple(ids),tuple(states)]
297
298         where_add, date_str = self._get_date_query(cr, uid, ids, context=context)
299         if where_add:
300             where += where_add
301
302         #It depends on the company of the user OR by using force_company in context
303         user = self.pool.get("res.users").browse(cr, uid, uid, context=context)
304         if context.get("force_company", False):
305             where.append(context['force_company'])
306         else:
307             where.append(user.company_id.id)
308
309         prodlot_id = context.get('prodlot_id', False)
310         prodlot_clause = ''
311         if prodlot_id:
312             prodlot_clause = ' and prodlot_id = %s '
313             where += [prodlot_id]
314
315         # TODO: perhaps merge in one query.
316         if 'in' in what:
317             # all moves from a location out of the set to a location in the set
318             cr.execute(
319                 'select sum(product_qty), product_id, product_uom '\
320                 'from stock_move '\
321                 'where location_id NOT IN %s '\
322                 'and location_dest_id IN %s '\
323                 'and product_id IN %s '\
324                 'and state IN %s ' + (date_str and 'and '+date_str+' ' or '') +' '\
325                 'and company_id = %s '\
326                 + prodlot_clause + 
327                 'group by product_id,product_uom',tuple(where))
328             results = cr.fetchall()
329         if 'out' in what:
330             # all moves from a location in the set to a location out of the set
331             cr.execute(
332                 'select sum(product_qty), product_id, product_uom '\
333                 'from stock_move '\
334                 'where location_id IN %s '\
335                 'and location_dest_id NOT IN %s '\
336                 'and product_id  IN %s '\
337                 'and state in %s ' + (date_str and 'and '+date_str+' ' or '') + ' '\
338                 'and company_id = %s '\
339                 + prodlot_clause + 
340                 'group by product_id,product_uom',tuple(where))
341             results2 = cr.fetchall()
342             
343         # Get the missing UoM resources
344         uom_obj = self.pool.get('product.uom')
345         uoms = map(lambda x: x[2], results) + map(lambda x: x[2], results2)
346         if context.get('uom', False):
347             uoms += [context['uom']]
348         uoms = filter(lambda x: x not in uoms_o.keys(), uoms)
349         if uoms:
350             uoms = uom_obj.browse(cr, uid, list(set(uoms)), context=context)
351             for o in uoms:
352                 uoms_o[o.id] = o
353                 
354         #TOCHECK: before change uom of product, stock move line are in old uom.
355         context.update({'raise-exception': False})
356         # Count the incoming quantities
357         for amount, prod_id, prod_uom in results:
358             amount = uom_obj._compute_qty_obj(cr, uid, uoms_o[prod_uom], amount,
359                      uoms_o[context.get('uom', False) or product2uom[prod_id]], context=context)
360             res[prod_id] += amount
361         # Count the outgoing quantities
362         for amount, prod_id, prod_uom in results2:
363             amount = uom_obj._compute_qty_obj(cr, uid, uoms_o[prod_uom], amount,
364                     uoms_o[context.get('uom', False) or product2uom[prod_id]], context=context)
365             res[prod_id] -= amount
366         return res
367
368     def _product_available(self, cr, uid, ids, field_names=None, arg=False, context=None):
369         """ Finds the incoming and outgoing quantity of product.
370         @return: Dictionary of values
371         """
372         if not field_names:
373             field_names = []
374         if context is None:
375             context = {}
376         res = {}
377         for id in ids:
378             res[id] = {}.fromkeys(field_names, 0.0)
379         for f in field_names:
380             c = context.copy()
381             if f == 'qty_available':
382                 c.update({ 'states': ('done',), 'what': ('in', 'out') })
383             if f == 'virtual_available':
384                 c.update({ 'states': ('confirmed','waiting','assigned','done'), 'what': ('in', 'out') })
385             if f == 'incoming_qty':
386                 c.update({ 'states': ('confirmed','waiting','assigned'), 'what': ('in',) })
387             if f == 'outgoing_qty':
388                 c.update({ 'states': ('confirmed','waiting','assigned'), 'what': ('out',) })
389             stock = self.get_product_available(cr, uid, ids, context=c)
390             for id in ids:
391                 res[id][f] = stock.get(id, 0.0)
392         return res
393
394     _columns = {
395         'reception_count': fields.function(_stock_move_count, string="Reception", type='integer', multi='pickings'),
396         'delivery_count': fields.function(_stock_move_count, string="Delivery", type='integer', multi='pickings'),
397         'qty_available': fields.function(_product_available, multi='qty_available',
398             type='float',  digits_compute=dp.get_precision('Product Unit of Measure'),
399             string='Quantity On Hand',
400             help="Current quantity of products.\n"
401                  "In a context with a single Stock Location, this includes "
402                  "goods stored at this Location, or any of its children.\n"
403                  "In a context with a single Warehouse, this includes "
404                  "goods stored in the Stock Location of this Warehouse, or any "
405                  "of its children.\n"
406                  "In a context with a single Shop, this includes goods "
407                  "stored in the Stock Location of the Warehouse of this Shop, "
408                  "or any of its children.\n"
409                  "Otherwise, this includes goods stored in any Stock Location "
410                  "with 'internal' type."),
411         'virtual_available': fields.function(_product_available, multi='qty_available',
412             type='float',  digits_compute=dp.get_precision('Product Unit of Measure'),
413             string='Forecasted Quantity',
414             help="Forecast quantity (computed as Quantity On Hand "
415                  "- Outgoing + Incoming)\n"
416                  "In a context with a single Stock Location, this includes "
417                  "goods stored in this location, or any of its children.\n"
418                  "In a context with a single Warehouse, this includes "
419                  "goods stored in the Stock Location of this Warehouse, or any "
420                  "of its children.\n"
421                  "In a context with a single Shop, this includes goods "
422                  "stored in the Stock Location of the Warehouse of this Shop, "
423                  "or any of its children.\n"
424                  "Otherwise, this includes goods stored in any Stock Location "
425                  "with 'internal' type."),
426         'incoming_qty': fields.function(_product_available, multi='qty_available',
427             type='float',  digits_compute=dp.get_precision('Product Unit of Measure'),
428             string='Incoming',
429             help="Quantity of products that are planned to arrive.\n"
430                  "In a context with a single Stock Location, this includes "
431                  "goods arriving to this Location, or any of its children.\n"
432                  "In a context with a single Warehouse, this includes "
433                  "goods arriving to the Stock Location of this Warehouse, or "
434                  "any of its children.\n"
435                  "In a context with a single Shop, this includes goods "
436                  "arriving to the Stock Location of the Warehouse of this "
437                  "Shop, or any of its children.\n"
438                  "Otherwise, this includes goods arriving to any Stock "
439                  "Location with 'internal' type."),
440         'outgoing_qty': fields.function(_product_available, multi='qty_available',
441             type='float',  digits_compute=dp.get_precision('Product Unit of Measure'),
442             string='Outgoing',
443             help="Quantity of products that are planned to leave.\n"
444                  "In a context with a single Stock Location, this includes "
445                  "goods leaving this Location, or any of its children.\n"
446                  "In a context with a single Warehouse, this includes "
447                  "goods leaving the Stock Location of this Warehouse, or "
448                  "any of its children.\n"
449                  "In a context with a single Shop, this includes goods "
450                  "leaving the Stock Location of the Warehouse of this "
451                  "Shop, or any of its children.\n"
452                  "Otherwise, this includes goods leaving any Stock "
453                  "Location with 'internal' type."),
454         'track_production': fields.boolean('Track Manufacturing Lots', help="Forces to specify a Serial Number for all moves containing this product and generated by a Manufacturing Order"),
455         'track_incoming': fields.boolean('Track Incoming Lots', help="Forces to specify a Serial Number for all moves containing this product and coming from a Supplier Location"),
456         'track_outgoing': fields.boolean('Track Outgoing Lots', help="Forces to specify a Serial Number for all moves containing this product and going to a Customer Location"),
457         'location_id': fields.dummy(string='Location', relation='stock.location', type='many2one'),
458         'warehouse_id': fields.dummy(string='Warehouse', relation='stock.warehouse', type='many2one'),
459         'valuation':fields.selection([('manual_periodic', 'Periodical (manual)'),
460                                         ('real_time','Real Time (automated)'),], 'Inventory Valuation',
461                                         help="If real-time valuation is enabled for a product, the system will automatically write journal entries corresponding to stock moves." \
462                                              "The inventory variation account set on the product category will represent the current inventory value, and the stock input and stock output account will hold the counterpart moves for incoming and outgoing products."
463                                         , required=True),
464     }
465
466     _defaults = {
467         'valuation': 'manual_periodic',
468     }
469
470     def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
471         res = super(product_product,self).fields_view_get(cr, uid, view_id, view_type, context, toolbar=toolbar, submenu=submenu)
472         if context is None:
473             context = {}
474         if ('location' in context) and context['location']:
475             location_info = self.pool.get('stock.location').browse(cr, uid, context['location'])
476             fields=res.get('fields',{})
477             if fields:
478                 if location_info.usage == 'supplier':
479                     if fields.get('virtual_available'):
480                         res['fields']['virtual_available']['string'] = _('Future Receptions')
481                     if fields.get('qty_available'):
482                         res['fields']['qty_available']['string'] = _('Received Qty')
483
484                 if location_info.usage == 'internal':
485                     if fields.get('virtual_available'):
486                         res['fields']['virtual_available']['string'] = _('Future Stock')
487
488                 if location_info.usage == 'customer':
489                     if fields.get('virtual_available'):
490                         res['fields']['virtual_available']['string'] = _('Future Deliveries')
491                     if fields.get('qty_available'):
492                         res['fields']['qty_available']['string'] = _('Delivered Qty')
493
494                 if location_info.usage == 'inventory':
495                     if fields.get('virtual_available'):
496                         res['fields']['virtual_available']['string'] = _('Future P&L')
497                     if fields.get('qty_available'):
498                         res['fields']['qty_available']['string'] = _('P&L Qty')
499
500                 if location_info.usage == 'procurement':
501                     if fields.get('virtual_available'):
502                         res['fields']['virtual_available']['string'] = _('Future Qty')
503                     if fields.get('qty_available'):
504                         res['fields']['qty_available']['string'] = _('Unplanned Qty')
505
506                 if location_info.usage == 'production':
507                     if fields.get('virtual_available'):
508                         res['fields']['virtual_available']['string'] = _('Future Productions')
509                     if fields.get('qty_available'):
510                         res['fields']['qty_available']['string'] = _('Produced Qty')
511         return res
512
513
514 class product_template(osv.osv):
515     _name = 'product.template'
516     _inherit = 'product.template'
517     _columns = {
518         'property_stock_procurement': fields.property(
519             'stock.location',
520             type='many2one',
521             relation='stock.location',
522             string="Procurement Location",
523             view_load=True,
524             domain=[('usage','like','procurement')],
525             help="This stock location will be used, instead of the default one, as the source location for stock moves generated by procurements."),
526         'property_stock_production': fields.property(
527             'stock.location',
528             type='many2one',
529             relation='stock.location',
530             string="Production Location",
531             view_load=True,
532             domain=[('usage','like','production')],
533             help="This stock location will be used, instead of the default one, as the source location for stock moves generated by manufacturing orders."),
534         'property_stock_inventory': fields.property(
535             'stock.location',
536             type='many2one',
537             relation='stock.location',
538             string="Inventory Location",
539             view_load=True,
540             domain=[('usage','like','inventory')],
541             help="This stock location will be used, instead of the default one, as the source location for stock moves generated when you do an inventory."),
542         'property_stock_account_input': fields.property('account.account',
543             type='many2one', relation='account.account',
544             string='Stock Input Account', view_load=True,
545             help="When doing real-time inventory valuation, counterpart journal items for all incoming stock moves will be posted in this account, unless "
546                  "there is a specific valuation account set on the source location. When not set on the product, the one from the product category is used."),
547         'property_stock_account_output': fields.property('account.account',
548             type='many2one', relation='account.account',
549             string='Stock Output Account', view_load=True,
550             help="When doing real-time inventory valuation, counterpart journal items for all outgoing stock moves will be posted in this account, unless "
551                  "there is a specific valuation account set on the destination location. When not set on the product, the one from the product category is used."),
552         'sale_delay': fields.float('Customer Lead Time', help="The average delay in days between the confirmation of the customer order and the delivery of the finished products. It's the time you promise to your customers."),
553         'loc_rack': fields.char('Rack', size=16),
554         'loc_row': fields.char('Row', size=16),
555         'loc_case': fields.char('Case', size=16),
556     }
557
558     _defaults = {
559         'sale_delay': 7,
560     }
561
562 class product_category(osv.osv):
563
564     _inherit = 'product.category'
565     _columns = {
566         'property_stock_journal': fields.property('account.journal',
567             relation='account.journal', type='many2one',
568             string='Stock Journal', view_load=True,
569             help="When doing real-time inventory valuation, this is the Accounting Journal in which entries will be automatically posted when stock moves are processed."),
570         'property_stock_account_input_categ': fields.property('account.account',
571             type='many2one', relation='account.account',
572             string='Stock Input Account', view_load=True,
573             help="When doing real-time inventory valuation, counterpart journal items for all incoming stock moves will be posted in this account, unless "
574                  "there is a specific valuation account set on the source location. This is the default value for all products in this category. It "
575                  "can also directly be set on each product"),
576         'property_stock_account_output_categ': fields.property('account.account',
577             type='many2one', relation='account.account',
578             string='Stock Output Account', view_load=True,
579             help="When doing real-time inventory valuation, counterpart journal items for all outgoing stock moves will be posted in this account, unless "
580                  "there is a specific valuation account set on the destination location. This is the default value for all products in this category. It "
581                  "can also directly be set on each product"),
582         'property_stock_valuation_account_id': fields.property('account.account',
583             type='many2one',
584             relation='account.account',
585             string="Stock Valuation Account",
586             view_load=True,
587             help="When real-time inventory valuation is enabled on a product, this account will hold the current value of the products.",),
588     }
589
590
591 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: