[I18N] Add the Persian (as spoken in Iran) translation
[odoo/odoo.git] / addons / product / 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 osv, fields
24 import pooler
25
26 import math
27 from _common import rounding
28
29 from tools import config
30 from tools.translate import _
31
32 def is_pair(x):
33     return not x%2
34
35 #----------------------------------------------------------
36 # UOM
37 #----------------------------------------------------------
38
39 class product_uom_categ(osv.osv):
40     _name = 'product.uom.categ'
41     _description = 'Product uom categ'
42     _columns = {
43         'name': fields.char('Name', size=64, required=True, translate=True),
44     }
45 product_uom_categ()
46
47 class product_uom(osv.osv):
48     _name = 'product.uom'
49     _description = 'Product Unit of Measure'
50
51     def _factor(self, cursor, user, ids, name, arg, context):
52         res = {}
53         for uom in self.browse(cursor, user, ids, context=context):
54             if uom.factor:
55                 if uom.factor_inv_data:
56                     res[uom.id] = uom.factor_inv_data
57                 else:
58                     res[uom.id] = round(1 / uom.factor, 6)
59             else:
60                 res[uom.id] = 0.0
61         return res
62
63     def _factor_inv(self, cursor, user, id, name, value, arg, context):
64         ctx = context.copy()
65         if 'read_delta' in ctx:
66             del ctx['read_delta']
67         if value:
68             data = 0.0
69             if round(1 / round(1/value, 6), 6) != value:
70                 data = value
71             self.write(cursor, user, id, {
72                 'factor': round(1/value, 6),
73                 'factor_inv_data': data,
74                 }, context=ctx)
75         else:
76             self.write(cursor, user, id, {
77                 'factor': 0.0,
78                 'factor_inv_data': 0.0,
79                 }, context=ctx)
80
81     _columns = {
82         'name': fields.char('Name', size=64, required=True, translate=True),
83         'category_id': fields.many2one('product.uom.categ', 'UoM Category', required=True, ondelete='cascade',
84             help="Unit of Measure of a category can be converted between each others in the same category."),
85         'factor': fields.float('Rate', digits=(12, 6), required=True,
86             help='The coefficient for the formula:\n' \
87                     '1 (base unit) = coeff (this unit). Rate = 1 / Factor.'),
88         'factor_inv': fields.function(_factor, digits=(12, 6),
89             method=True, string='Factor',
90             help='The coefficient for the formula:\n' \
91                     'coeff (base unit) = 1 (this unit). Factor = 1 / Rate.'),
92         'factor_inv_data': fields.float('Factor', digits=(12, 6)),
93         'rounding': fields.float('Rounding Precision', digits=(16, 3), required=True,
94             help="The computed quantity will be a multiple of this value. Use 1.0 for products that can not be split."),
95         'active': fields.boolean('Active'),
96     }
97
98     _defaults = {
99         'factor': lambda *a: 1.0,
100         'factor_inv': lambda *a: 1.0,
101         'active': lambda *a: 1,
102         'rounding': lambda *a: 0.01,
103     }
104
105     def _compute_qty(self, cr, uid, from_uom_id, qty, to_uom_id=False):
106         if not from_uom_id or not qty or not to_uom_id:
107             return qty
108         uoms = self.browse(cr, uid, [from_uom_id, to_uom_id])
109         if uoms[0].id == from_uom_id:
110             from_unit, to_unit = uoms[0], uoms[-1]
111         else:
112             from_unit, to_unit = uoms[-1], uoms[0]
113         return self._compute_qty_obj(cr, uid, from_unit, qty, to_unit)
114
115     def _compute_qty_obj(self, cr, uid, from_unit, qty, to_unit, context={}):
116         if from_unit.category_id.id <> to_unit.category_id.id:
117             raise osv.except_osv(_('Warning !'),_('Conversion from Product UoM %s to Default UoM %s is not possible as they both belong to different Category!')% (from_unit.name,to_unit.name))
118 #            return qty
119         if from_unit.factor_inv_data:
120             amount = qty * from_unit.factor_inv_data
121         else:
122             amount = qty / from_unit.factor
123         if to_unit:
124             if to_unit.factor_inv_data:
125                 amount = rounding(amount / to_unit.factor_inv_data, to_unit.rounding)
126             else:
127                 amount = rounding(amount * to_unit.factor, to_unit.rounding)
128         return amount
129
130     def _compute_price(self, cr, uid, from_uom_id, price, to_uom_id=False):
131         if not from_uom_id or not price or not to_uom_id:
132             return price
133         uoms = self.browse(cr, uid, [from_uom_id, to_uom_id])
134         if uoms[0].id == from_uom_id:
135             from_unit, to_unit = uoms[0], uoms[-1]
136         else:
137             from_unit, to_unit = uoms[-1], uoms[0]
138         if from_unit.category_id.id <> to_unit.category_id.id:
139             raise osv.except_osv(_('Warning !'),_('Conversion from Product UoM %s to Default UoM %s is not possible as they both belong to different Category!')% (from_unit.name,to_unit.name))
140 #            return price
141         if from_unit.factor_inv_data:
142             amount = price / from_unit.factor_inv_data
143         else:
144             amount = price * from_unit.factor
145         if to_uom_id:
146             if to_unit.factor_inv_data:
147                 amount = amount * to_unit.factor_inv_data
148             else:
149                 amount = amount / to_unit.factor
150         return amount
151
152     def onchange_factor_inv(self, cursor, user, ids, value):
153         if value == 0.0:
154             return {'value': {'factor': 0}}
155         return {'value': {'factor': round(1/value, 6)}}
156
157     def onchange_factor(self, cursor, user, ids, value):
158         if value == 0.0:
159             return {'value': {'factor_inv': 0}}
160         return {'value': {'factor_inv': round(1/value, 6)}}
161
162 product_uom()
163
164
165 class product_ul(osv.osv):
166     _name = "product.ul"
167     _description = "Shipping Unit"
168     _columns = {
169         'name' : fields.char('Name', size=64,select=True, required=True, translate=True),
170         'type' : fields.selection([('unit','Unit'),('pack','Pack'),('box', 'Box'), ('palet', 'Pallet')], 'Type', required=True),
171     }
172 product_ul()
173
174
175 #----------------------------------------------------------
176 # Categories
177 #----------------------------------------------------------
178 class product_category(osv.osv):
179
180     def name_get(self, cr, uid, ids, context=None):
181         if not len(ids):
182             return []
183         reads = self.read(cr, uid, ids, ['name','parent_id'], context)
184         res = []
185         for record in reads:
186             name = record['name']
187             if record['parent_id']:
188                 name = record['parent_id'][1]+' / '+name
189             res.append((record['id'], name))
190         return res
191
192     def _name_get_fnc(self, cr, uid, ids, prop, unknow_none, context):
193         res = self.name_get(cr, uid, ids, context)
194         return dict(res)
195
196     _name = "product.category"
197     _description = "Product Category"
198     _columns = {
199         'name': fields.char('Name', size=64, required=True, translate=True),
200         'complete_name': fields.function(_name_get_fnc, method=True, type="char", string='Name'),
201         'parent_id': fields.many2one('product.category','Parent Category', select=True),
202         'child_id': fields.one2many('product.category', 'parent_id', string='Child Categories'),
203         'sequence': fields.integer('Sequence'),
204     }
205     _order = "sequence"
206     def _check_recursion(self, cr, uid, ids):
207         level = 100
208         while len(ids):
209             cr.execute('select distinct parent_id from product_category where id in ('+','.join(map(str,ids))+')')
210             ids = filter(None, map(lambda x:x[0], cr.fetchall()))
211             if not level:
212                 return False
213             level -= 1
214         return True
215
216     _constraints = [
217         (_check_recursion, 'Error ! You can not create recursive categories.', ['parent_id'])
218     ]
219     def child_get(self, cr, uid, ids):
220         return [ids]
221
222 product_category()
223
224
225 #----------------------------------------------------------
226 # Products
227 #----------------------------------------------------------
228 class product_template(osv.osv):
229     _name = "product.template"
230     _description = "Product Template"
231     def _calc_seller_delay(self, cr, uid, ids, name, arg, context={}):
232         result = {}
233         for product in self.browse(cr, uid, ids, context):
234             if product.seller_ids:
235                 result[product.id] = product.seller_ids[0].delay
236             else:
237                 result[product.id] = 1
238         return result
239
240     _columns = {
241         'name': fields.char('Name', size=128, required=True, translate=True, select=True),
242         'product_manager': fields.many2one('res.users','Product Manager'),
243         'description': fields.text('Description',translate=True),
244         'description_purchase': fields.text('Purchase Description',translate=True),
245         'description_sale': fields.text('Sale Description',translate=True),
246         'type': fields.selection([('product','Stockable Product'),('consu', 'Consumable'),('service','Service')], 'Product Type', required=True, help="Will change the way procurements are processed. Consumables are stockable products with infinite stock, or for use when you have no stock management in the system."),
247         'supply_method': fields.selection([('produce','Produce'),('buy','Buy')], 'Supply method', required=True, help="Produce will generate production order or tasks, according to the product type. Purchase will trigger purchase orders when requested."),
248         'sale_delay': fields.float('Customer Lead Time', help="This is the average time between the confirmation of the customer order and the delivery of the finished products. It's the time you promise to your customers."),
249         'produce_delay': fields.float('Manufacturing Lead Time', help="Average time to produce this product. This is only for the production order and, if it is a multi-level bill of material, it's only for the level of this product. Different delays will be summed for all levels and purchase orders."),
250         'procure_method': fields.selection([('make_to_stock','Make to Stock'),('make_to_order','Make to Order')], 'Procure Method', required=True, help="'Make to Stock': When needed, take from the stock or wait until re-supplying. 'Make to Order': When needed, purchase or produce for the procurement request."),
251         'rental': fields.boolean('Rentable Product'),
252         'categ_id': fields.many2one('product.category','Category', required=True, change_default=True),
253         'list_price': fields.float('Sale Price', digits=(16, int(config['price_accuracy'])), help="Base price for computing the customer price. Sometimes called the catalog price."),
254         'standard_price': fields.float('Cost Price', required=True, digits=(16, int(config['price_accuracy'])), help="The cost of the product for accounting stock valuation. It can serves as a base price for supplier price."),
255         'volume': fields.float('Volume', help="The volume in m3."),
256         'weight': fields.float('Gross weight', help="The gross weight in Kg."),
257         'weight_net': fields.float('Net weight', help="The net weight in Kg."),
258         'cost_method': fields.selection([('standard','Standard Price'), ('average','Average Price')], 'Costing Method', required=True,
259             help="Standard Price: the cost price is fixed and recomputed periodically (usually at the end of the year), Average Price: the cost price is recomputed at each reception of products."),
260         'warranty': fields.float('Warranty (months)'),
261         'sale_ok': fields.boolean('Can be sold', help="Determine if the product can be visible in the list of product within a selection from a sale order line."),
262         'purchase_ok': fields.boolean('Can be Purchased', help="Determine if the product is visible in the list of products within a selection from a purchase order line."),
263         'state': fields.selection([('',''),('draft', 'In Development'),('sellable','In Production'),('end','End of Lifecycle'),('obsolete','Obsolete')], 'Status', help="Tells the user if he can use the product or not."),
264         'uom_id': fields.many2one('product.uom', 'Default UoM', required=True, help="Default Unit of Measure used for all stock operation."),
265         'uom_po_id': fields.many2one('product.uom', 'Purchase UoM', required=True, help="Default Unit of Measure used for purchase orders. It must in the same category than the default unit of measure."),
266         'uos_id' : fields.many2one('product.uom', 'Unit of Sale',
267             help='Used by companies that manages two unit of measure: invoicing and stock management. For example, in food industries, you will manage a stock of ham but invoice in Kg. Keep empty to use the default UOM.'),
268         'uos_coeff': fields.float('UOM -> UOS Coeff', digits=(16,4),
269             help='Coefficient to convert UOM to UOS\n'
270             ' uos = uom * coeff'),
271         'mes_type': fields.selection((('fixed', 'Fixed'), ('variable', 'Variable')), 'Measure Type', required=True),
272         'seller_delay': fields.function(_calc_seller_delay, method=True, type='integer', string='Supplier Lead Time', help="This is the average delay in days between the purchase order confirmation and the reception of goods for this product and for the default supplier. It is used by the scheduler to order requests based on reordering delays."),
273         'seller_ids': fields.one2many('product.supplierinfo', 'product_id', 'Partners'),
274         'loc_rack': fields.char('Rack', size=16),
275         'loc_row': fields.char('Row', size=16),
276         'loc_case': fields.char('Case', size=16),
277         'company_id': fields.many2one('res.company', 'Company'),
278     }
279
280     def _get_uom_id(self, cr, uid, *args):
281         cr.execute('select id from product_uom order by id limit 1')
282         res = cr.fetchone()
283         return res and res[0] or False
284
285     def _default_category(self, cr, uid, context={}):
286         if 'categ_id' in context and context['categ_id']:
287             return context['categ_id']
288         return False
289
290     def onchange_uom(self, cursor, user, ids, uom_id,uom_po_id):
291         if uom_id and uom_po_id:
292             uom_obj=self.pool.get('product.uom')
293             uom=uom_obj.browse(cursor,user,[uom_id])[0]
294             uom_po=uom_obj.browse(cursor,user,[uom_po_id])[0]
295             if uom.category_id.id != uom_po.category_id.id:
296                 return {'value': {'uom_po_id': uom_id}}
297         return False
298
299     _defaults = {
300         'company_id': lambda self, cr, uid, context: False, # Visible by all
301         'type': lambda *a: 'product',
302         'list_price': lambda *a: 1,
303         'cost_method': lambda *a: 'standard',
304         'supply_method': lambda *a: 'buy',
305         'standard_price': lambda *a: 1,
306         'sale_ok': lambda *a: 1,
307         'sale_delay': lambda *a: 7,
308         'produce_delay': lambda *a: 1,
309         'purchase_ok': lambda *a: 1,
310         'procure_method': lambda *a: 'make_to_stock',
311         'uom_id': _get_uom_id,
312         'uom_po_id': _get_uom_id,
313         'uos_coeff' : lambda *a: 1.0,
314         'mes_type' : lambda *a: 'fixed',
315         'categ_id' : _default_category,
316     }
317
318     def _check_uom(self, cursor, user, ids):
319         for product in self.browse(cursor, user, ids):
320             if product.uom_id.category_id.id <> product.uom_po_id.category_id.id:
321                 return False
322         return True
323
324     def _check_uos(self, cursor, user, ids):
325         for product in self.browse(cursor, user, ids):
326             if product.uos_id \
327                     and product.uos_id.category_id.id \
328                     == product.uom_id.category_id.id:
329                 return False
330         return True
331
332     _constraints = [
333         (_check_uos, 'Error: UOS must be in a different category than the UOM', ['uos_id']),
334         (_check_uom, 'Error: The default UOM and the purchase UOM must be in the same category.', ['uom_id']),
335     ]
336
337     def name_get(self, cr, user, ids, context={}):
338         if 'partner_id' in context:
339             pass
340         return super(product_template, self).name_get(cr, user, ids, context)
341
342 product_template()
343
344 class product_product(osv.osv):
345     def view_header_get(self, cr, uid, view_id, view_type, context):
346         res = super(product_product, self).view_header_get(cr, uid, view_id, view_type, context)
347         if (context.get('categ_id', False)):
348             return _('Products: ')+self.pool.get('product.category').browse(cr, uid, context['categ_id'], context).name
349         return res
350
351     def _product_price(self, cr, uid, ids, name, arg, context={}):
352         res = {}
353         quantity = context.get('quantity', 1)
354         pricelist = context.get('pricelist', False)
355         if pricelist:
356             for id in ids:
357                 try:
358                     price = self.pool.get('product.pricelist').price_get(cr,uid,[pricelist], id, quantity, context=context)[pricelist]
359                 except:
360                     price = 0.0
361                 res[id] = price
362         for id in ids:
363             res.setdefault(id, 0.0)
364         return res
365
366     def _get_product_available_func(states, what):
367         def _product_available(self, cr, uid, ids, name, arg, context={}):
368             return {}.fromkeys(ids, 0.0)
369         return _product_available
370
371     _product_qty_available = _get_product_available_func(('done',), ('in', 'out'))
372     _product_virtual_available = _get_product_available_func(('confirmed','waiting','assigned','done'), ('in', 'out'))
373     _product_outgoing_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('out',))
374     _product_incoming_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('in',))
375
376     def _product_lst_price(self, cr, uid, ids, name, arg, context=None):
377         res = {}
378         product_uom_obj = self.pool.get('product.uom')
379         for id in ids:
380             res.setdefault(id, 0.0)
381         for product in self.browse(cr, uid, ids, context=context):
382             if 'uom' in context:
383                 uom = product.uos_id or product.uom_id
384                 res[product.id] = product_uom_obj._compute_price(cr, uid,
385                         uom.id, product.list_price, context['uom'])
386             else:
387                 res[product.id] = product.list_price
388             res[product.id] =  (res[product.id] or 0.0) * (product.price_margin or 1.0) + product.price_extra
389         return res
390
391     def _get_partner_code_name(self, cr, uid, ids, product_id, partner_id, context={}):
392         product = self.browse(cr, uid, [product_id], context)[0]
393         for supinfo in product.seller_ids:
394             if supinfo.name.id == partner_id:
395                 return {'code': supinfo.product_code, 'name': supinfo.product_name}
396         return {'code' : product.default_code, 'name' : product.name}
397
398     def _product_code(self, cr, uid, ids, name, arg, context={}):
399         res = {}
400         for p in self.browse(cr, uid, ids, context):
401             res[p.id] = self._get_partner_code_name(cr, uid, [], p.id, context.get('partner_id', None), context)['code']
402         return res
403
404     def _product_partner_ref(self, cr, uid, ids, name, arg, context={}):
405         res = {}
406         for p in self.browse(cr, uid, ids, context):
407             data = self._get_partner_code_name(cr, uid, [], p.id, context.get('partner_id', None), context)
408             if not data['code']:
409                 data['code'] = p.code
410             if not data['name']:
411                 data['name'] = p.name
412             res[p.id] = (data['code'] and ('['+data['code']+'] ') or '') + \
413                     (data['name'] or '')
414         return res
415
416     _defaults = {
417         'active': lambda *a: 1,
418         'price_extra': lambda *a: 0.0,
419         'price_margin': lambda *a: 1.0,
420     }
421
422     _name = "product.product"
423     _description = "Product"
424     _table = "product_product"
425     _inherits = {'product.template': 'product_tmpl_id'}
426     _columns = {
427         'qty_available': fields.function(_product_qty_available, method=True, type='float', string='Real Stock'),
428         'virtual_available': fields.function(_product_virtual_available, method=True, type='float', string='Virtual Stock'),
429         'incoming_qty': fields.function(_product_incoming_qty, method=True, type='float', string='Incoming'),
430         'outgoing_qty': fields.function(_product_outgoing_qty, method=True, type='float', string='Outgoing'),
431         'price': fields.function(_product_price, method=True, type='float', string='Customer Price', digits=(16, int(config['price_accuracy']))),
432         'lst_price' : fields.function(_product_lst_price, method=True, type='float', string='List Price', digits=(16, int(config['price_accuracy']))),
433         'code': fields.function(_product_code, method=True, type='char', string='Code'),
434         'partner_ref' : fields.function(_product_partner_ref, method=True, type='char', string='Customer ref'),
435         'default_code' : fields.char('Code', size=64),
436         'active': fields.boolean('Active'),
437         'variants': fields.char('Variants', size=64),
438         'product_tmpl_id': fields.many2one('product.template', 'Product Template', required=True),
439         'ean13': fields.char('EAN13', size=13),
440         'packaging' : fields.one2many('product.packaging', 'product_id', 'Logistical Units', help="Gives the different ways to package the same product. This has no impact on the packing order and is mainly used if you use the EDI module."),
441         'price_extra': fields.float('Variant Price Extra', digits=(16, int(config['price_accuracy']))),
442         'price_margin': fields.float('Variant Price Margin', digits=(16, int(config['price_accuracy']))),
443     }
444
445     def onchange_uom(self, cursor, user, ids, uom_id,uom_po_id):
446         if uom_id and uom_po_id:
447             uom_obj=self.pool.get('product.uom')
448             uom=uom_obj.browse(cursor,user,[uom_id])[0]
449             uom_po=uom_obj.browse(cursor,user,[uom_po_id])[0]
450             if uom.category_id.id != uom_po.category_id.id:
451                 return {'value': {'uom_po_id': uom_id}}
452         return False
453
454     def _check_ean_key(self, cr, uid, ids):
455         for partner in self.browse(cr, uid, ids):
456             if not partner.ean13:
457                 continue
458             if len(partner.ean13) <> 13:
459                 return False
460             try:
461                 int(partner.ean13)
462             except:
463                 return False
464             sum=0
465             for i in range(12):
466                 if is_pair(i):
467                     sum += int(partner.ean13[i])
468                 else:
469                     sum += 3 * int(partner.ean13[i])
470             check = int(math.ceil(sum / 10.0) * 10 - sum)
471             if check != int(partner.ean13[12]):
472                 return False
473         return True
474
475     _constraints = [(_check_ean_key, 'Error: Invalid ean code', ['ean13'])]
476
477     def on_order(self, cr, uid, ids, orderline, quantity):
478         pass
479
480     def name_get(self, cr, user, ids, context={}):
481         if not len(ids):
482             return []
483         def _name_get(d):
484             #name = self._product_partner_ref(cr, user, [d['id']], '', '', context)[d['id']]
485             #code = self._product_code(cr, user, [d['id']], '', '', context)[d['id']]
486             name = d.get('name','')
487             code = d.get('default_code',False)
488             if code:
489                 name = '[%s] %s' % (code,name)
490             if d['variants']:
491                 name = name + ' - %s' % (d['variants'],)
492             return (d['id'], name)
493         result = map(_name_get, self.read(cr, user, ids, ['variants','name','default_code'], context))
494         return result
495
496     def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=80):
497         if not args:
498             args=[]
499         if not context:
500             context={}
501         if name:
502             ids = self.search(cr, user, [('default_code','=',name)]+ args, limit=limit, context=context)
503             if not len(ids):
504                 ids = self.search(cr, user, [('ean13','=',name)]+ args, limit=limit, context=context)
505             if not len(ids):
506                 ids = self.search(cr, user, [('default_code',operator,name)]+ args, limit=limit, context=context)
507                 ids += self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
508         else:
509             ids = self.search(cr, user, args, limit=limit, context=context)
510         result = self.name_get(cr, user, ids, context)
511         return result
512
513     #
514     # Could be overrided for variants matrices prices
515     #
516     def price_get(self, cr, uid, ids, ptype='list_price', context={}):
517         res = {}
518         product_uom_obj = self.pool.get('product.uom')
519
520         for product in self.browse(cr, uid, ids, context=context):
521             res[product.id] = product[ptype] or 0.0
522             if ptype == 'list_price':
523                 res[product.id] = (res[product.id] * (product.price_margin or 1.0)) + \
524                         product.price_extra
525             if 'uom' in context:
526                 uom = product.uos_id or product.uom_id
527                 res[product.id] = product_uom_obj._compute_price(cr, uid,
528                         uom.id, res[product.id], context['uom'])
529         return res
530
531     def copy(self, cr, uid, id, default=None, context=None):
532         if not context:
533             context={}
534
535         if ('variant' in context) and context['variant']:
536             fields = ['product_tmpl_id', 'active', 'variants', 'default_code',
537                     'price_margin', 'price_extra']
538             data = self.read(cr, uid, id, fields=fields, context=context)
539             for f in fields:
540                 if f in default:
541                     data[f] = default[f]
542             data['product_tmpl_id'] = data.get('product_tmpl_id', False) \
543                     and data['product_tmpl_id'][0]
544             del data['id']
545             return self.create(cr, uid, data)
546         else:
547             return super(product_product, self).copy(cr, uid, id, default=default,
548                     context=context)
549 product_product()
550
551 class product_packaging(osv.osv):
552     _name = "product.packaging"
553     _description = "Packaging"
554     _rec_name = 'ean'
555     _columns = {
556         'sequence': fields.integer('Sequence'),
557         'name' : fields.char('Description', size=64),
558         'qty' : fields.float('Quantity by Package',
559             help="The total number of products you can put by palet or box."),
560         'ul' : fields.many2one('product.ul', 'Type of Package', required=True),
561         'ul_qty' : fields.integer('Package by layer'),
562         'rows' : fields.integer('Number of Layer', required=True,
563             help='The number of layer on a palet or box'),
564         'product_id' : fields.many2one('product.product', 'Product', select=1, ondelete='cascade', required=True),
565         'ean' : fields.char('EAN', size=14,
566             help="The EAN code of the package unit."),
567         'code' : fields.char('Code', size=14,
568             help="The code of the transport unit."),
569         'weight': fields.float('Total Package Weight',
570             help='The weight of a full of products palet or box.'),
571         'weight_ul': fields.float('Empty Package Weight',
572             help='The weight of the empty UL'),
573         'height': fields.float('Height', help='The height of the package'),
574         'width': fields.float('Width', help='The width of the package'),
575         'length': fields.float('Length', help='The length of the package'),
576     }
577
578     def _get_1st_ul(self, cr, uid, context={}):
579         cr.execute('select id from product_ul order by id asc limit 1')
580         res = cr.fetchone()
581         return (res and res[0]) or False
582
583     _defaults = {
584         'rows' : lambda *a : 3,
585         'sequence' : lambda *a : 1,
586         'ul' : _get_1st_ul,
587     }
588
589     def checksum(ean):
590         salt = '31' * 6 + '3'
591         sum = 0
592         for ean_part, salt_part in zip(ean, salt):
593             sum += int(ean_part) * int(salt_part)
594         return (10 - (sum % 10)) % 10
595     checksum = staticmethod(checksum)
596
597 product_packaging()
598
599
600 class product_supplierinfo(osv.osv):
601     _name = "product.supplierinfo"
602     _description = "Information about a product supplier"
603     _columns = {
604         'name' : fields.many2one('res.partner', 'Partner', required=True, ondelete='cascade', help="Supplier of this product"),
605         'product_name': fields.char('Partner Product Name', size=128, help="Name of the product for this partner, will be used when printing a request for quotation. Keep empty to use the internal one."),
606         'product_code': fields.char('Partner Product Code', size=64, help="Code of the product for this partner, will be used when printing a request for quotation. Keep empty to use the internal one."),
607         'sequence' : fields.integer('Priority'),
608         'qty' : fields.float('Minimal Quantity', required=True, help="The minimal quantity to purchase for this supplier, expressed in the default unit of measure."),
609         'product_id' : fields.many2one('product.template', 'Product', required=True, ondelete='cascade', select=True),
610         'delay' : fields.integer('Delivery Delay', required=True, help="Delay in days between the confirmation of the purchase order and the reception of the products in your warehouse. Used by the scheduler for automatic computation of the purchase order planning."),
611         'pricelist_ids': fields.one2many('pricelist.partnerinfo', 'suppinfo_id', 'Supplier Pricelist'),
612     }
613     _defaults = {
614         'qty': lambda *a: 0.0,
615         'sequence': lambda *a: 1,
616         'delay': lambda *a: 1,
617     }
618     _order = 'sequence'
619 product_supplierinfo()
620
621
622 class pricelist_partnerinfo(osv.osv):
623     _name = 'pricelist.partnerinfo'
624     _columns = {
625         'name': fields.char('Description', size=64),
626         'suppinfo_id': fields.many2one('product.supplierinfo', 'Partner Information', required=True, ondelete='cascade'),
627         'min_quantity': fields.float('Quantity', required=True),
628         'price': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
629     }
630     _order = 'min_quantity asc'
631 pricelist_partnerinfo()
632
633
634
635 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
636