[FIX] stock: context lost in invoice wizard
[odoo/odoo.git] / addons / stock / product.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution   
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 from osv import fields, osv
24 from tools.translate import _
25
26
27 class product_product(osv.osv):
28     _inherit = "product.product"
29     def view_header_get(self, cr, user, view_id, view_type, context):
30         res = super(product_product, self).view_header_get(cr, user, view_id, view_type, context)
31         if res: return res
32         if (context.get('location', False)):
33             return _('Products: ')+self.pool.get('stock.location').browse(cr, user, context['location'], context).name
34         return res
35
36     def get_product_available(self,cr,uid,ids,context=None):
37         if not context:
38             context = {}
39         states=context.get('states',[])
40         what=context.get('what',())
41         if not ids:
42             ids = self.search(cr, uid, [])
43         res = {}.fromkeys(ids, 0.0)
44         if not ids:
45             return res
46
47         if context.get('shop', False):
48             cr.execute('select warehouse_id from sale_shop where id=%s', (int(context['shop']),))
49             res2 = cr.fetchone()
50             if res2:
51                 context['warehouse'] = res2[0]
52
53         if context.get('warehouse', False):
54             cr.execute('select lot_stock_id from stock_warehouse where id=%s', (int(context['warehouse']),))
55             res2 = cr.fetchone()
56             if res2:
57                 context['location'] = res2[0]
58
59         if context.get('location', False):
60             if type(context['location']) == type(1):
61                 location_ids = [context['location']]
62             else:
63                 location_ids = context['location']
64         else:
65             location_ids = []
66             wids = self.pool.get('stock.warehouse').search(cr, uid, [], context=context)
67             for w in self.pool.get('stock.warehouse').browse(cr, uid, wids, context=context):
68                 location_ids.append(w.lot_stock_id.id)
69
70         # build the list of ids of children of the location given by id
71         if context.get('compute_child',True):
72             child_location_ids = self.pool.get('stock.location').search(cr, uid, [('location_id', 'child_of', location_ids)])
73             location_ids= len(child_location_ids) and child_location_ids or location_ids
74         else:
75             location_ids= location_ids
76
77         states_str = ','.join(map(lambda s: "'%s'" % s, states))
78
79         uoms_o = {}
80         product2uom = {}
81         for product in self.browse(cr, uid, ids, context=context):
82             product2uom[product.id] = product.uom_id.id
83             uoms_o[product.uom_id.id] = product.uom_id
84
85         prod_ids_str = ','.join(map(str, ids))
86         location_ids_str = ','.join(map(str, location_ids))
87         results = []
88         results2 = []
89
90         from_date=context.get('from_date',False)
91         to_date=context.get('to_date',False)
92         date_str=False
93         if from_date and to_date:
94             date_str="date_planned>='%s' and date_planned<='%s'"%(from_date,to_date)
95         elif from_date:
96             date_str="date_planned>='%s'"%(from_date)
97         elif to_date:
98             date_str="date_planned<='%s'"%(to_date)
99
100         if 'in' in what:
101             # all moves from a location out of the set to a location in the set
102             cr.execute(
103                 'select sum(product_qty), product_id, product_uom '\
104                 'from stock_move '\
105                 'where location_id not in ('+location_ids_str+') '\
106                 'and location_dest_id in ('+location_ids_str+') '\
107                 'and product_id in ('+prod_ids_str+') '\
108                 'and state in ('+states_str+') '+ (date_str and 'and '+date_str+' ' or '') +''\
109                 'group by product_id,product_uom'
110             )
111             results = cr.fetchall()
112         if 'out' in what:
113             # all moves from a location in the set to a location out of the set
114             cr.execute(
115                 'select sum(product_qty), product_id, product_uom '\
116                 'from stock_move '\
117                 'where location_id in ('+location_ids_str+') '\
118                 'and location_dest_id not in ('+location_ids_str+') '\
119                 'and product_id in ('+prod_ids_str+') '\
120                 'and state in ('+states_str+') '+ (date_str and 'and '+date_str+' ' or '') + ''\
121                 'group by product_id,product_uom'
122             )
123             results2 = cr.fetchall()
124         uom_obj = self.pool.get('product.uom')
125         uoms = map(lambda x: x[2], results) + map(lambda x: x[2], results2)
126         if context.get('uom', False):
127             uoms += [context['uom']]
128
129         uoms = filter(lambda x: x not in uoms_o.keys(), uoms)
130         if uoms:
131             uoms = uom_obj.browse(cr, uid, list(set(uoms)), context=context)
132         for o in uoms:
133             uoms_o[o.id] = o
134         for amount, prod_id, prod_uom in results:
135             amount = uom_obj._compute_qty_obj(cr, uid, uoms_o[prod_uom], amount,
136                     uoms_o[context.get('uom', False) or product2uom[prod_id]])
137             res[prod_id] += amount
138         for amount, prod_id, prod_uom in results2:
139             amount = uom_obj._compute_qty_obj(cr, uid, uoms_o[prod_uom], amount,
140                     uoms_o[context.get('uom', False) or product2uom[prod_id]])
141             res[prod_id] -= amount
142         return res
143
144     def _product_available(self, cr, uid, ids, field_names=None, arg=False, context={}):
145         if not field_names:
146             field_names=[]
147         res = {}
148         for id in ids:
149             res[id] = {}.fromkeys(field_names, 0.0)
150         for f in field_names:
151             c = context.copy()
152             if f=='qty_available':
153                 c.update({ 'states':('done',), 'what':('in', 'out') })
154             if f=='virtual_available':
155                 c.update({ 'states':('confirmed','waiting','assigned','done'), 'what':('in', 'out') })
156             if f=='incoming_qty':
157                 c.update({ 'states':('confirmed','waiting','assigned'), 'what':('in',) })
158             if f=='outgoing_qty':
159                 c.update({ 'states':('confirmed','waiting','assigned'), 'what':('out',) })
160             stock=self.get_product_available(cr,uid,ids,context=c)
161             for id in ids:
162                 res[id][f] = stock.get(id, 0.0)
163         return res
164
165     _columns = {
166         'qty_available': fields.function(_product_available, method=True, type='float', string='Real Stock', help="Current quantities of products in selected locations or all internal if none have been selected.", multi='qty_available'),
167         'virtual_available': fields.function(_product_available, method=True, type='float', string='Virtual Stock', help="Futur stock for this product according to the selected location or all internal if none have been selected. Computed as: Real Stock - Outgoing + Incoming.", multi='qty_available'),
168         'incoming_qty': fields.function(_product_available, method=True, type='float', string='Incoming', help="Quantities of products that are planned to arrive in selected locations or all internal if none have been selected.", multi='qty_available'),
169         'outgoing_qty': fields.function(_product_available, method=True, type='float', string='Outgoing', help="Quantities of products that are planned to leave in selected locations or all internal if none have been selected.", multi='qty_available'),
170         'track_production' : fields.boolean('Track Production Lots' , help="Force to use a Production Lot during production order"),
171         'track_incoming' : fields.boolean('Track Incomming Lots', help="Force to use a Production Lot during receptions"),
172         'track_outgoing' : fields.boolean('Track Outging Lots', help="Force to use a Production Lot during deliveries"),
173     }
174     def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False):
175         if not context:
176             context = {}
177         res = super(product_product,self).fields_view_get(cr, uid, view_id, view_type, context, toolbar)
178         if context.get('location'):
179             location_info = self.pool.get('stock.location').browse(cr, uid, context['location'])
180             fields=res.get('fields',{})
181             if fields:
182                 if location_info.usage == 'supplier':
183                     if fields.get('virtual_available'):
184                         res['fields']['virtual_available']['string'] = _('Futur Receptions')
185                     if fields.get('qty_available'):
186                         res['fields']['qty_available']['string'] = _('Received Qty')
187
188                 if location_info.usage == 'internal':
189                     if fields.get('virtual_available'):
190                         res['fields']['virtual_available']['string'] = _('Futur Stock')
191
192                 if location_info.usage == 'customer':
193                     if fields.get('virtual_available'):
194                         res['fields']['virtual_available']['string'] = _('Futur Deliveries')
195                     if fields.get('qty_available'):
196                         res['fields']['qty_available']['string'] = _('Delivered Qty')
197
198                 if location_info.usage == 'inventory':
199                     if fields.get('virtual_available'):
200                         res['fields']['virtual_available']['string'] = _('Futur P&L')
201                     res['fields']['qty_available']['string'] = _('P&L Qty')
202
203                 if location_info.usage == 'procurement':
204                     if fields.get('virtual_available'):
205                         res['fields']['virtual_available']['string'] = _('Futur Qty')
206                     if fields.get('qty_available'):
207                         res['fields']['qty_available']['string'] = _('Unplanned Qty')
208
209                 if location_info.usage == 'production':
210                     if fields.get('virtual_available'):
211                         res['fields']['virtual_available']['string'] = _('Futur Productions')
212                     if fields.get('qty_available'):
213                         res['fields']['qty_available']['string'] = _('Produced Qty')
214
215         return res
216 product_product()
217
218
219 class product_template(osv.osv):
220     _name = 'product.template'
221     _inherit = 'product.template'
222     _columns = {
223         'property_stock_procurement': fields.property(
224             'stock.location',
225             type='many2one',
226             relation='stock.location',
227             string="Procurement Location",
228             method=True,
229             view_load=True,
230             help="For the current product (template), this stock location will be used, instead of the default one, as the source location for stock moves generated by procurements"),
231         'property_stock_production': fields.property(
232             'stock.location',
233             type='many2one',
234             relation='stock.location',
235             string="Production Location",
236             method=True,
237             view_load=True,
238             help="For the current product (template), this stock location will be used, instead of the default one, as the source location for stock moves generated by production orders"),
239         'property_stock_inventory': fields.property(
240             'stock.location',
241             type='many2one',
242             relation='stock.location',
243             string="Inventory Location",
244             method=True,
245             view_load=True,
246             help="For the current product (template), this stock location will be used, instead of the default one, as the source location for stock moves generated when you do an inventory"),
247         'property_stock_account_input': fields.property('account.account',
248             type='many2one', relation='account.account',
249             string='Stock Input Account', method=True, view_load=True,
250             help='This account will be used, instead of the default one, to value input stock'),
251         'property_stock_account_output': fields.property('account.account',
252             type='many2one', relation='account.account',
253             string='Stock Output Account', method=True, view_load=True,
254             help='This account will be used, instead of the default one, to value output stock'),
255     }
256
257 product_template()
258
259
260 class product_category(osv.osv):
261     _inherit = 'product.category'
262     _columns = {
263         'property_stock_journal': fields.property('account.journal',
264             relation='account.journal', type='many2one',
265             string='Stock journal', method=True, view_load=True,
266             help="This journal will be used for the accounting move generated by stock move"),
267         'property_stock_account_input_categ': fields.property('account.account',
268             type='many2one', relation='account.account',
269             string='Stock Input Account', method=True, view_load=True,
270             help='This account will be used to value the input stock'),
271         'property_stock_account_output_categ': fields.property('account.account',
272             type='many2one', relation='account.account',
273             string='Stock Output Account', method=True, view_load=True,
274             help='This account will be used to value the output stock'),
275     }
276
277 product_category()
278
279 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
280