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