[REF] *: removed useless attributes on definition of property fields (first attribute...
[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         '''
201         Parses the context and returns a list of location_ids based on it.
202         It will return all stock locations when no parameters are given
203         Possible parameters are shop, warehouse, location, force_company, compute_child
204         '''
205         if context is None:
206             context = {}
207         location_obj = self.pool.get('stock.location')
208         warehouse_obj = self.pool.get('stock.warehouse')
209         shop_obj = self.pool.get('sale.shop')
210         if context.get('shop', False):
211             warehouse_id = shop_obj.read(cr, uid, int(context['shop']), ['warehouse_id'])['warehouse_id'][0]
212             if warehouse_id:
213                 context['warehouse'] = warehouse_id
214
215         if context.get('warehouse', False):
216             lot_id = warehouse_obj.read(cr, uid, int(context['warehouse']), ['lot_stock_id'])['lot_stock_id'][0]
217             if lot_id:
218                 context['location'] = lot_id
219
220         if context.get('location', False):
221             if type(context['location']) == type(1):
222                 location_ids = [context['location']]
223             elif type(context['location']) in (type(''), type(u'')):
224                 if context.get('force_company', False):
225                     location_ids = location_obj.search(cr, uid, [('name','ilike',context['location']), ('company_id', '=', context['force_company'])], context=context)
226                 else:
227                     location_ids = location_obj.search(cr, uid, [('name','ilike',context['location'])], context=context)
228             else:
229                 location_ids = context['location']
230         else:
231             location_ids = []
232             wids = warehouse_obj.search(cr, uid, [], context=context)
233             if not wids:
234                 return False
235             for w in warehouse_obj.browse(cr, uid, wids, context=context):
236                 if not context.get('force_company', False) or w.lot_stock_id.company_id.id == context['force_company']:
237                     location_ids.append(w.lot_stock_id.id)
238
239         # build the list of ids of children of the location given by id
240         if context.get('compute_child',True):
241             if context.get('force_company', False):
242                 child_location_ids = location_obj.search(cr, uid, [('location_id', 'child_of', location_ids), ('company_id', '=', context['force_company'])])
243             else:
244                 child_location_ids = location_obj.search(cr, uid, [('location_id', 'child_of', location_ids)])
245             location_ids = child_location_ids or location_ids
246         return location_ids
247
248
249     def _get_date_query(self, cr, uid, ids, context):
250         '''
251             Parses the context and returns the dates query string needed to be processed in _get_product_available
252             It searches for a from_date and to_date
253         '''
254         from_date = context.get('from_date',False)
255         to_date = context.get('to_date',False)
256         date_str = False
257         whereadd = []
258         
259         if from_date and to_date:
260             date_str = "date>=%s and date<=%s"
261             whereadd.append(tuple([from_date]))
262             whereadd.append(tuple([to_date]))
263         elif from_date:
264             date_str = "date>=%s"
265             whereadd.append(tuple([from_date]))
266         elif to_date:
267             date_str = "date<=%s"
268             whereadd.append(tuple([to_date]))
269         return (whereadd, date_str)
270
271
272
273
274     def get_product_available(self, cr, uid, ids, context=None):
275         """ Finds the quantity available of product(s) depending on parameters in the context
276         for date, location, state (allows e.g. for calculating future stock), what,
277         production lot
278         @return: Dictionary of values for every product id
279         """
280         #TODO complete the docstring with possible keys in context + their effect
281         if context is None:
282             context = {}
283         
284         states = context.get('states',[])
285         what = context.get('what',())
286         if not ids:
287             ids = self.search(cr, uid, [])
288         res = {}.fromkeys(ids, 0.0)
289         if not ids:
290             return res
291         #set_context: refactor code here
292         location_ids = self._get_locations_from_context(cr, uid, ids, context=context)
293         if not location_ids: #in case of no locations, query will be empty anyways
294             return res
295         
296         # this will be a dictionary of the product UoM by product id
297         product2uom = {}
298         uom_ids = []
299         for product in self.read(cr, uid, ids, ['uom_id'], context=context):
300             product2uom[product['id']] = product['uom_id'][0]
301             uom_ids.append(product['uom_id'][0])
302         # this will be a dictionary of the UoM resources we need for conversion purposes, by UoM id
303         uoms_o = {}
304         for uom in self.pool.get('product.uom').browse(cr, uid, uom_ids, context=context):
305             uoms_o[uom.id] = uom
306
307         results = []
308         results2 = []
309
310         where = [tuple(location_ids),tuple(location_ids),tuple(ids),tuple(states)]
311
312         where_add, date_str = self._get_date_query(cr, uid, ids, context=context)
313         if where_add:
314             where += where_add
315
316
317         prodlot_id = context.get('prodlot_id', False)
318         prodlot_clause = ''
319         if prodlot_id:
320             prodlot_clause = ' and prodlot_id = %s '
321             where += [prodlot_id]
322
323         # TODO: perhaps merge in one query.
324         if 'in' in what:
325             # all moves from a location out of the set to a location in the set
326             cr.execute(
327                 'select sum(product_qty), product_id, product_uom '\
328                 'from stock_move '\
329                 'where location_id NOT IN %s '\
330                 'and location_dest_id IN %s '\
331                 'and product_id IN %s '\
332                 'and state IN %s ' + (date_str and 'and '+date_str+' ' or '') +' '\
333                 + prodlot_clause + 
334                 'group by product_id,product_uom',tuple(where))
335             results = cr.fetchall()
336         if 'out' in what:
337             # all moves from a location in the set to a location out of the set
338             cr.execute(
339                 'select sum(product_qty), product_id, product_uom '\
340                 'from stock_move '\
341                 'where location_id IN %s '\
342                 'and location_dest_id NOT IN %s '\
343                 'and product_id  IN %s '\
344                 'and state in %s ' + (date_str and 'and '+date_str+' ' or '') + ' '\
345                 + prodlot_clause + 
346                 'group by product_id,product_uom',tuple(where))
347             results2 = cr.fetchall()
348             
349         # Get the missing UoM resources
350         uom_obj = self.pool.get('product.uom')
351         uoms = map(lambda x: x[2], results) + map(lambda x: x[2], results2)
352         if context.get('uom', False):
353             uoms += [context['uom']]
354         uoms = filter(lambda x: x not in uoms_o.keys(), uoms)
355         if uoms:
356             uoms = uom_obj.browse(cr, uid, list(set(uoms)), context=context)
357             for o in uoms:
358                 uoms_o[o.id] = o
359                 
360         #TOCHECK: before change uom of product, stock move line are in old uom.
361         context.update({'raise-exception': False})
362         # Count the incoming quantities
363         for amount, prod_id, prod_uom in results:
364             amount = uom_obj._compute_qty_obj(cr, uid, uoms_o[prod_uom], amount,
365                      uoms_o[context.get('uom', False) or product2uom[prod_id]], context=context)
366             res[prod_id] += amount
367         # Count the outgoing quantities
368         for amount, prod_id, prod_uom in results2:
369             amount = uom_obj._compute_qty_obj(cr, uid, uoms_o[prod_uom], amount,
370                     uoms_o[context.get('uom', False) or product2uom[prod_id]], context=context)
371             res[prod_id] -= amount
372         return res
373
374     def _product_available(self, cr, uid, ids, field_names=None, arg=False, context=None):
375         """ Finds the incoming and outgoing quantity of product.
376         @return: Dictionary of values
377         """
378         if not field_names:
379             field_names = []
380         if context is None:
381             context = {}
382         res = {}
383         for id in ids:
384             res[id] = {}.fromkeys(field_names, 0.0)
385         for f in field_names:
386             c = context.copy()
387             if f == 'qty_available':
388                 c.update({ 'states': ('done',), 'what': ('in', 'out') })
389             if f == 'virtual_available':
390                 c.update({ 'states': ('confirmed','waiting','assigned','done'), 'what': ('in', 'out') })
391             if f == 'incoming_qty':
392                 c.update({ 'states': ('confirmed','waiting','assigned'), 'what': ('in',) })
393             if f == 'outgoing_qty':
394                 c.update({ 'states': ('confirmed','waiting','assigned'), 'what': ('out',) })
395             stock = self.get_product_available(cr, uid, ids, context=c)
396             for id in ids:
397                 res[id][f] = stock.get(id, 0.0)
398         return res
399
400     _columns = {
401         'reception_count': fields.function(_stock_move_count, string="Reception", type='integer', multi='pickings'),
402         'delivery_count': fields.function(_stock_move_count, string="Delivery", type='integer', multi='pickings'),
403         'qty_available': fields.function(_product_available, multi='qty_available',
404             type='float',  digits_compute=dp.get_precision('Product Unit of Measure'),
405             string='Quantity On Hand',
406             help="Current quantity of products.\n"
407                  "In a context with a single Stock Location, this includes "
408                  "goods stored at this Location, or any of its children.\n"
409                  "In a context with a single Warehouse, this includes "
410                  "goods stored in the Stock Location of this Warehouse, or any "
411                  "of its children.\n"
412                  "In a context with a single Shop, this includes goods "
413                  "stored in the Stock Location of the Warehouse of this Shop, "
414                  "or any of its children.\n"
415                  "Otherwise, this includes goods stored in any Stock Location "
416                  "with 'internal' type."),
417         'virtual_available': fields.function(_product_available, multi='qty_available',
418             type='float',  digits_compute=dp.get_precision('Product Unit of Measure'),
419             string='Forecasted Quantity',
420             help="Forecast quantity (computed as Quantity On Hand "
421                  "- Outgoing + Incoming)\n"
422                  "In a context with a single Stock Location, this includes "
423                  "goods stored in this location, or any of its children.\n"
424                  "In a context with a single Warehouse, this includes "
425                  "goods stored in the Stock Location of this Warehouse, or any "
426                  "of its children.\n"
427                  "In a context with a single Shop, this includes goods "
428                  "stored in the Stock Location of the Warehouse of this Shop, "
429                  "or any of its children.\n"
430                  "Otherwise, this includes goods stored in any Stock Location "
431                  "with 'internal' type."),
432         'incoming_qty': fields.function(_product_available, multi='qty_available',
433             type='float',  digits_compute=dp.get_precision('Product Unit of Measure'),
434             string='Incoming',
435             help="Quantity of products that are planned to arrive.\n"
436                  "In a context with a single Stock Location, this includes "
437                  "goods arriving to this Location, or any of its children.\n"
438                  "In a context with a single Warehouse, this includes "
439                  "goods arriving to the Stock Location of this Warehouse, or "
440                  "any of its children.\n"
441                  "In a context with a single Shop, this includes goods "
442                  "arriving to the Stock Location of the Warehouse of this "
443                  "Shop, or any of its children.\n"
444                  "Otherwise, this includes goods arriving to any Stock "
445                  "Location with 'internal' type."),
446         'outgoing_qty': fields.function(_product_available, multi='qty_available',
447             type='float',  digits_compute=dp.get_precision('Product Unit of Measure'),
448             string='Outgoing',
449             help="Quantity of products that are planned to leave.\n"
450                  "In a context with a single Stock Location, this includes "
451                  "goods leaving this Location, or any of its children.\n"
452                  "In a context with a single Warehouse, this includes "
453                  "goods leaving the Stock Location of this Warehouse, or "
454                  "any of its children.\n"
455                  "In a context with a single Shop, this includes goods "
456                  "leaving the Stock Location of the Warehouse of this "
457                  "Shop, or any of its children.\n"
458                  "Otherwise, this includes goods leaving any Stock "
459                  "Location with 'internal' type."),
460         '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"),
461         '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"),
462         '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"),
463         'location_id': fields.dummy(string='Location', relation='stock.location', type='many2one'),
464         'warehouse_id': fields.dummy(string='Warehouse', relation='stock.warehouse', type='many2one'),
465         #TODO: why first arg is empty?
466         'valuation':fields.property(type='selection', selection=[('manual_periodic', 'Periodical (manual)'),
467                                         ('real_time','Real Time (automated)'),], string = 'Inventory Valuation',
468                                         help="If real-time valuation is enabled for a product, the system will automatically write journal entries corresponding to stock moves." \
469                                              "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."
470                                         , required=True),
471     }
472
473     _defaults = {
474         'valuation': 'manual_periodic',
475     }
476
477     def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
478         res = super(product_product,self).fields_view_get(cr, uid, view_id, view_type, context, toolbar=toolbar, submenu=submenu)
479         if context is None:
480             context = {}
481         if ('location' in context) and context['location']:
482             location_info = self.pool.get('stock.location').browse(cr, uid, context['location'])
483             fields=res.get('fields',{})
484             if fields:
485                 if location_info.usage == 'supplier':
486                     if fields.get('virtual_available'):
487                         res['fields']['virtual_available']['string'] = _('Future Receptions')
488                     if fields.get('qty_available'):
489                         res['fields']['qty_available']['string'] = _('Received Qty')
490
491                 if location_info.usage == 'internal':
492                     if fields.get('virtual_available'):
493                         res['fields']['virtual_available']['string'] = _('Future Stock')
494
495                 if location_info.usage == 'customer':
496                     if fields.get('virtual_available'):
497                         res['fields']['virtual_available']['string'] = _('Future Deliveries')
498                     if fields.get('qty_available'):
499                         res['fields']['qty_available']['string'] = _('Delivered Qty')
500
501                 if location_info.usage == 'inventory':
502                     if fields.get('virtual_available'):
503                         res['fields']['virtual_available']['string'] = _('Future P&L')
504                     if fields.get('qty_available'):
505                         res['fields']['qty_available']['string'] = _('P&L Qty')
506
507                 if location_info.usage == 'procurement':
508                     if fields.get('virtual_available'):
509                         res['fields']['virtual_available']['string'] = _('Future Qty')
510                     if fields.get('qty_available'):
511                         res['fields']['qty_available']['string'] = _('Unplanned Qty')
512
513                 if location_info.usage == 'production':
514                     if fields.get('virtual_available'):
515                         res['fields']['virtual_available']['string'] = _('Future Productions')
516                     if fields.get('qty_available'):
517                         res['fields']['qty_available']['string'] = _('Produced Qty')
518         return res
519
520
521 class product_template(osv.osv):
522     _name = 'product.template'
523     _inherit = 'product.template'
524     _columns = {
525         'property_stock_procurement': fields.property(
526             type='many2one',
527             relation='stock.location',
528             string="Procurement Location",
529             domain=[('usage','like','procurement')],
530             help="This stock location will be used, instead of the default one, as the source location for stock moves generated by procurements."),
531         'property_stock_production': fields.property(
532             type='many2one',
533             relation='stock.location',
534             string="Production Location",
535             domain=[('usage','like','production')],
536             help="This stock location will be used, instead of the default one, as the source location for stock moves generated by manufacturing orders."),
537         'property_stock_inventory': fields.property(
538             type='many2one',
539             relation='stock.location',
540             string="Inventory Location",
541             domain=[('usage','like','inventory')],
542             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."),
543         'property_stock_account_input': fields.property(
544             type='many2one',
545             relation='account.account',
546             string='Stock Input Account',
547             help="When doing real-time inventory valuation, counterpart journal items for all incoming stock moves will be posted in this account, unless "
548                  "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."),
549         'property_stock_account_output': fields.property(
550             type='many2one',
551             relation='account.account',
552             string='Stock Output Account',
553             help="When doing real-time inventory valuation, counterpart journal items for all outgoing stock moves will be posted in this account, unless "
554                  "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."),
555         '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."),
556         'loc_rack': fields.char('Rack', size=16),
557         'loc_row': fields.char('Row', size=16),
558         'loc_case': fields.char('Case', size=16),
559     }
560
561     _defaults = {
562         'sale_delay': 7,
563     }
564
565 class product_category(osv.osv):
566
567     _inherit = 'product.category'
568     _columns = {
569         'property_stock_journal': fields.property(
570             relation='account.journal',
571             type='many2one',
572             string='Stock Journal',
573             help="When doing real-time inventory valuation, this is the Accounting Journal in which entries will be automatically posted when stock moves are processed."),
574         'property_stock_account_input_categ': fields.property(
575             type='many2one',
576             relation='account.account',
577             string='Stock Input Account',
578             help="When doing real-time inventory valuation, counterpart journal items for all incoming stock moves will be posted in this account, unless "
579                  "there is a specific valuation account set on the source location. This is the default value for all products in this category. It "
580                  "can also directly be set on each product"),
581         'property_stock_account_output_categ': fields.property(
582             type='many2one',
583             relation='account.account',
584             string='Stock Output Account',
585             help="When doing real-time inventory valuation, counterpart journal items for all outgoing stock moves will be posted in this account, unless "
586                  "there is a specific valuation account set on the destination location. This is the default value for all products in this category. It "
587                  "can also directly be set on each product"),
588         'property_stock_valuation_account_id': fields.property(
589             type='many2one',
590             relation='account.account',
591             string="Stock Valuation Account",
592             help="When real-time inventory valuation is enabled on a product, this account will hold the current value of the products.",),
593     }
594
595
596 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: