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