imporve
[odoo/odoo.git] / addons / stock / product.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution   
5 #    Copyright (C) 2004-2008 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
25 class product_product(osv.osv):
26     _inherit = "product.product"
27     def view_header_get(self, cr, user, view_id, view_type, context):
28         res = super(product_product, self).view_header_get(cr, user, view_id, view_type, context)
29         if res: return res
30         if (context.get('location', False)):
31             return _('Products: ')+self.pool.get('stock.location').browse(cr, user, context['location'], context).name
32         return res
33
34     def get_product_available(self,cr,uid,ids,context=None):
35         if not context:
36             context = {}
37         states=context.get('states',[])
38         what=context.get('what',())
39         if not ids:
40             ids = self.search(cr, uid, [])
41         res = {}.fromkeys(ids, 0.0)
42         if not ids:
43             return res
44
45         if context.get('shop', False):
46             cr.execute('select warehouse_id from sale_shop where id=%d', (int(context['shop']),))
47             res2 = cr.fetchone()
48             if res2:
49                 context['warehouse'] = res2[0]
50
51         if context.get('warehouse', False):
52             cr.execute('select lot_stock_id from stock_warehouse where id=%d', (int(context['warehouse']),))
53             res2 = cr.fetchone()
54             if res2:
55                 context['location'] = res2[0]
56
57         if context.get('location', False):
58             if type(context['location']) == type(1):
59                 location_ids = [context['location']]
60             else:
61                 location_ids = context['location']
62         else:
63             cr.execute("select lot_stock_id from stock_warehouse")
64             location_ids = [id for (id,) in cr.fetchall()]
65
66         # build the list of ids of children of the location given by id
67         child_location_ids = self.pool.get('stock.location').search(cr, uid, [('location_id', 'child_of', location_ids)])
68         location_ids= len(child_location_ids) and child_location_ids or location_ids
69
70         states_str = ','.join(map(lambda s: "'%s'" % s, states))
71
72         product2uom = {}
73         for product in self.browse(cr, uid, ids, context=context):
74             product2uom[product.id] = product.uom_id.id
75
76         prod_ids_str = ','.join(map(str, ids))
77         location_ids_str = ','.join(map(str, location_ids))
78         results = []
79         results2 = []
80
81         from_date=context.get('from_date',False)
82         to_date=context.get('to_date',False)
83         date_str=False
84         if from_date and to_date:
85             date_str="date_planned>='%s' and date_planned<='%s'"%(from_date,to_date)
86         elif from_date:
87             date_str="date_planned>='%s'"%(from_date)
88         elif to_date:
89             date_str="date_planned<='%s'"%(to_date)
90
91         if 'in' in what:
92             # all moves from a location out of the set to a location in the set
93             cr.execute(
94                 'select sum(product_qty), product_id, product_uom '\
95                 'from stock_move '\
96                 'where location_id not in ('+location_ids_str+') '\
97                 'and location_dest_id in ('+location_ids_str+') '\
98                 'and product_id in ('+prod_ids_str+') '\
99                 'and state in ('+states_str+') '+ (date_str and 'and '+date_str+' ' or '') +''\
100                 'group by product_id,product_uom'
101             )
102             results = cr.fetchall()
103         if 'out' in what:
104             # all moves from a location in the set to a location out of the set
105             cr.execute(
106                 'select sum(product_qty), product_id, product_uom '\
107                 'from stock_move '\
108                 'where location_id in ('+location_ids_str+') '\
109                 'and location_dest_id not in ('+location_ids_str+') '\
110                 'and product_id in ('+prod_ids_str+') '\
111                 'and state in ('+states_str+') '+ (date_str and 'and '+date_str+' ' or '') + ''\
112                 'group by product_id,product_uom'
113             )
114             results2 = cr.fetchall()
115         uom_obj = self.pool.get('product.uom')
116         for amount, prod_id, prod_uom in results:
117             amount = uom_obj._compute_qty(cr, uid, prod_uom, amount,
118                     context.get('uom', False) or product2uom[prod_id])
119             res[prod_id] += amount
120         for amount, prod_id, prod_uom in results2:
121             amount = uom_obj._compute_qty(cr, uid, prod_uom, amount,
122                     context.get('uom', False) or product2uom[prod_id])
123             res[prod_id] -= amount
124         return res
125
126     def _product_available(self, cr, uid, ids, field_names=None, arg=False, context={}):
127         if not field_names:
128             field_names=[]
129         res = {}
130         for id in ids:
131             res[id] = {}.fromkeys(field_names, 0.0)
132         for f in field_names:
133             c = context.copy()
134             if f=='qty_available':
135                 c.update({ 'states':('done',), 'what':('in', 'out') })
136             if f=='virtual_available':
137                 c.update({ 'states':('confirmed','waiting','assigned','done'), 'what':('in', 'out') })
138             if f=='incoming_qty':
139                 c.update({ 'states':('confirmed','waiting','assigned'), 'what':('in',) })
140             if f=='outgoing_qty':
141                 c.update({ 'states':('confirmed','waiting','assigned'), 'what':('out',) })
142             stock=self.get_product_available(cr,uid,ids,context=c)
143             for id in ids:
144                 res[id][f] = stock.get(id, 0.0)
145         return res
146
147     _columns = {
148         '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'),
149         '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'),
150         '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'),
151         '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'),
152         'track_production' : fields.boolean('Track Production Lots' , help="Force to use a Production Lot during production order"),
153         'track_incoming' : fields.boolean('Track Incomming Lots', help="Force to use a Production Lot during receptions"),
154         'track_outgoing' : fields.boolean('Track Outging Lots', help="Force to use a Production Lot during deliveries"),
155     }
156     def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False):
157         res = super(product_product,self).fields_view_get(cr, uid, view_id, view_type, context, toolbar)
158         if ('location' in context) and context['location']:
159             location_info = self.pool.get('stock.location').browse(cr, uid, context['location'])
160             fields=res.get('fields',{})
161             if fields:
162                 if location_info.usage == 'supplier':
163                     if fields.get('virtual_available'):
164                         res['fields']['virtual_available']['string'] = 'Futur Receptions'
165                     if fields.get('qty_available'):
166                         res['fields']['qty_available']['string'] = 'Received Qty'
167
168                 if location_info.usage == 'internal':
169                     if fields.get('virtual_available'):
170                         res['fields']['virtual_available']['string'] = 'Futur Stock'
171
172                 if location_info.usage == 'customer':
173                     if fields.get('virtual_available'):
174                         res['fields']['virtual_available']['string'] = 'Futur Deliveries'
175                     if fields.get('qty_available'):
176                         res['fields']['qty_available']['string'] = 'Delivered Qty'
177
178                 if location_info.usage == 'inventory':
179                     if fields.get('virtual_available'):
180                         res['fields']['virtual_available']['string'] = 'Futur P&L'
181                     res['fields']['qty_available']['string'] = 'P&L Qty'
182
183                 if location_info.usage == 'procurement':
184                     if fields.get('virtual_available'):
185                         res['fields']['virtual_available']['string'] = 'Futur Qty'
186                     if fields.get('qty_available'):
187                         res['fields']['qty_available']['string'] = 'Unplanned Qty'
188
189                 if location_info.usage == 'production':
190                     if fields.get('virtual_available'):
191                         res['fields']['virtual_available']['string'] = 'Futur Productions'
192                     if fields.get('qty_available'):
193                         res['fields']['qty_available']['string'] = 'Produced Qty'
194
195         return res
196 product_product()
197
198
199 class product_template(osv.osv):
200     _name = 'product.template'
201     _inherit = 'product.template'
202     _columns = {
203         'property_stock_procurement': fields.property(
204             'stock.location',
205             type='many2one',
206             relation='stock.location',
207             string="Procurement Location",
208             method=True,
209             view_load=True,
210             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"),
211         'property_stock_production': fields.property(
212             'stock.location',
213             type='many2one',
214             relation='stock.location',
215             string="Production 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 production orders"),
219         'property_stock_inventory': fields.property(
220             'stock.location',
221             type='many2one',
222             relation='stock.location',
223             string="Inventory 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 when you do an inventory"),
227         'property_stock_account_input': fields.property('account.account',
228             type='many2one', relation='account.account',
229             string='Stock Input Account', method=True, view_load=True,
230             help='This account will be used, instead of the default one, to value input stock'),
231         'property_stock_account_output': fields.property('account.account',
232             type='many2one', relation='account.account',
233             string='Stock Output Account', method=True, view_load=True,
234             help='This account will be used, instead of the default one, to value output stock'),
235     }
236
237 product_template()
238
239
240 class product_category(osv.osv):
241     _inherit = 'product.category'
242     _columns = {
243         'property_stock_journal': fields.property('account.journal',
244             relation='account.journal', type='many2one',
245             string='Stock journal', method=True, view_load=True,
246             help="This journal will be used for the accounting move generated by stock move"),
247         'property_stock_account_input_categ': 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 to value the input stock'),
251         'property_stock_account_output_categ': 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 to value the output stock'),
255     }
256
257 product_category()
258
259 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
260