[FIX] STock : Locations structure wizard improved
[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         res = super(product_product,self).fields_view_get(cr, uid, view_id, view_type, context, toolbar)
176         if ('location' in context) and context['location']:
177             location_info = self.pool.get('stock.location').browse(cr, uid, context['location'])
178             fields=res.get('fields',{})
179             if fields:
180                 if location_info.usage == 'supplier':
181                     if fields.get('virtual_available'):
182                         res['fields']['virtual_available']['string'] = _('Futur Receptions')
183                     if fields.get('qty_available'):
184                         res['fields']['qty_available']['string'] = _('Received Qty')
185
186                 if location_info.usage == 'internal':
187                     if fields.get('virtual_available'):
188                         res['fields']['virtual_available']['string'] = _('Futur Stock')
189
190                 if location_info.usage == 'customer':
191                     if fields.get('virtual_available'):
192                         res['fields']['virtual_available']['string'] = _('Futur Deliveries')
193                     if fields.get('qty_available'):
194                         res['fields']['qty_available']['string'] = _('Delivered Qty')
195
196                 if location_info.usage == 'inventory':
197                     if fields.get('virtual_available'):
198                         res['fields']['virtual_available']['string'] = _('Futur P&L')
199                     res['fields']['qty_available']['string'] = _('P&L Qty')
200
201                 if location_info.usage == 'procurement':
202                     if fields.get('virtual_available'):
203                         res['fields']['virtual_available']['string'] = _('Futur Qty')
204                     if fields.get('qty_available'):
205                         res['fields']['qty_available']['string'] = _('Unplanned Qty')
206
207                 if location_info.usage == 'production':
208                     if fields.get('virtual_available'):
209                         res['fields']['virtual_available']['string'] = _('Futur Productions')
210                     if fields.get('qty_available'):
211                         res['fields']['qty_available']['string'] = _('Produced Qty')
212
213         return res
214 product_product()
215
216
217 class product_template(osv.osv):
218     _name = 'product.template'
219     _inherit = 'product.template'
220     _columns = {
221         'property_stock_procurement': fields.property(
222             'stock.location',
223             type='many2one',
224             relation='stock.location',
225             string="Procurement Location",
226             method=True,
227             view_load=True,
228             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"),
229         'property_stock_production': fields.property(
230             'stock.location',
231             type='many2one',
232             relation='stock.location',
233             string="Production Location",
234             method=True,
235             view_load=True,
236             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"),
237         'property_stock_inventory': fields.property(
238             'stock.location',
239             type='many2one',
240             relation='stock.location',
241             string="Inventory Location",
242             method=True,
243             view_load=True,
244             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"),
245         'property_stock_account_input': fields.property('account.account',
246             type='many2one', relation='account.account',
247             string='Stock Input Account', method=True, view_load=True,
248             help='This account will be used, instead of the default one, to value input stock'),
249         'property_stock_account_output': fields.property('account.account',
250             type='many2one', relation='account.account',
251             string='Stock Output Account', method=True, view_load=True,
252             help='This account will be used, instead of the default one, to value output stock'),
253     }
254
255 product_template()
256
257
258 class product_category(osv.osv):
259     _inherit = 'product.category'
260     _columns = {
261         'property_stock_journal': fields.property('account.journal',
262             relation='account.journal', type='many2one',
263             string='Stock journal', method=True, view_load=True,
264             help="This journal will be used for the accounting move generated by stock move"),
265         'property_stock_account_input_categ': fields.property('account.account',
266             type='many2one', relation='account.account',
267             string='Stock Input Account', method=True, view_load=True,
268             help='This account will be used to value the input stock'),
269         'property_stock_account_output_categ': fields.property('account.account',
270             type='many2one', relation='account.account',
271             string='Stock Output Account', method=True, view_load=True,
272             help='This account will be used to value the output stock'),
273     }
274
275 product_category()
276
277 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
278