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