1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
22 from osv import osv, fields
23 import decimal_precision as dp
26 from _common import rounding
28 from tools.translate import _
33 #----------------------------------------------------------
35 #----------------------------------------------------------
37 class product_uom_categ(osv.osv):
38 _name = 'product.uom.categ'
39 _description = 'Product uom categ'
41 'name': fields.char('Name', size=64, required=True, translate=True),
45 class product_uom(osv.osv):
47 _description = 'Product Unit of Measure'
49 def _compute_factor_inv(self, factor):
50 return factor and round(1.0 / factor, 6) or 0.0
52 def _factor_inv(self, cursor, user, ids, name, arg, context):
54 for uom in self.browse(cursor, user, ids, context=context):
55 res[uom.id] = self._compute_factor_inv(uom.factor)
58 def _factor_inv_write(self, cursor, user, id, name, value, arg, context):
59 return self.write(cursor, user, id, {'factor': self._compute_factor_inv(value)}, context=context)
61 def create(self, cr, uid, data, context={}):
62 if 'factor_inv' in data:
63 if data['factor_inv'] <> 1:
64 data['factor'] = self._compute_factor_inv(data['factor_inv'])
65 del(data['factor_inv'])
66 return super(product_uom, self).create(cr, uid, data, context)
69 'name': fields.char('Name', size=64, required=True, translate=True),
70 'category_id': fields.many2one('product.uom.categ', 'UoM Category', required=True, ondelete='cascade',
71 help="Quantity conversions may happen automatically between Units of Measure in the same category, according to their respective ratios."),
72 'factor': fields.float('Ratio', required=True,digits=(12, 6),
73 help='How many times this UoM is smaller than the reference UoM in this category:\n'\
74 '1 * (reference unit) = ratio * (this unit)'),
75 'factor_inv': fields.function(_factor_inv, digits_compute=dp.get_precision('Product UoM'),
76 fnct_inv=_factor_inv_write,
77 method=True, string='Ratio',
78 help='How many times this UoM is bigger than the reference UoM in this category:\n'\
79 '1 * (this unit) = ratio * (reference unit)', required=True),
80 'rounding': fields.float('Rounding Precision', digits_compute=dp.get_precision('Product UoM'), required=True,
81 help="The computed quantity will be a multiple of this value. "\
82 "Use 1.0 for a UoM that cannot be further split, such as a piece."),
83 'active': fields.boolean('Active', help="By unchecking the active field you can disable a unit of measure without deleting it."),
84 'uom_type': fields.selection([('bigger','Bigger than the reference UoM'),
85 ('reference','Reference UoM for this category'),
86 ('smaller','Smaller than the reference UoM')],'UoM Type', required=1),
92 'uom_type': 'reference',
96 ('factor_gt_zero', 'CHECK (factor!=0)', 'The conversion ratio for a unit of measure cannot be 0!'),
99 def _compute_qty(self, cr, uid, from_uom_id, qty, to_uom_id=False):
100 if not from_uom_id or not qty or not to_uom_id:
102 uoms = self.browse(cr, uid, [from_uom_id, to_uom_id])
103 if uoms[0].id == from_uom_id:
104 from_unit, to_unit = uoms[0], uoms[-1]
106 from_unit, to_unit = uoms[-1], uoms[0]
107 return self._compute_qty_obj(cr, uid, from_unit, qty, to_unit)
109 def _compute_qty_obj(self, cr, uid, from_unit, qty, to_unit, context={}):
110 if from_unit.category_id.id <> to_unit.category_id.id:
112 amount = qty / from_unit.factor
114 amount = rounding(amount * to_unit.factor, to_unit.rounding)
117 def _compute_price(self, cr, uid, from_uom_id, price, to_uom_id=False):
118 if not from_uom_id or not price or not to_uom_id:
120 uoms = self.browse(cr, uid, [from_uom_id, to_uom_id])
121 if uoms[0].id == from_uom_id:
122 from_unit, to_unit = uoms[0], uoms[-1]
124 from_unit, to_unit = uoms[-1], uoms[0]
125 if from_unit.category_id.id <> to_unit.category_id.id:
127 amount = price * from_unit.factor
129 amount = amount / to_unit.factor
132 def onchange_type(self, cursor, user, ids, value):
133 if value == 'reference':
134 return {'value': {'factor': 1, 'factor_inv': 1}}
140 class product_ul(osv.osv):
142 _description = "Shipping Unit"
144 'name' : fields.char('Name', size=64,select=True, required=True, translate=True),
145 'type' : fields.selection([('unit','Unit'),('pack','Pack'),('box', 'Box'), ('pallet', 'Pallet')], 'Type', required=True),
150 #----------------------------------------------------------
152 #----------------------------------------------------------
153 class product_category(osv.osv):
155 def name_get(self, cr, uid, ids, context=None):
158 reads = self.read(cr, uid, ids, ['name','parent_id'], context)
161 name = record['name']
162 if record['parent_id']:
163 name = record['parent_id'][1]+' / '+name
164 res.append((record['id'], name))
167 def _name_get_fnc(self, cr, uid, ids, prop, unknow_none, context):
168 res = self.name_get(cr, uid, ids, context)
171 _name = "product.category"
172 _description = "Product Category"
174 'name': fields.char('Name', size=64, required=True, translate=True),
175 'complete_name': fields.function(_name_get_fnc, method=True, type="char", string='Name'),
176 'parent_id': fields.many2one('product.category','Parent Category', select=True),
177 'child_id': fields.one2many('product.category', 'parent_id', string='Child Categories'),
178 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of product categories."),
179 'type': fields.selection([('view','View'), ('normal','Normal')], 'Category Type'),
184 'type' : lambda *a : 'normal',
188 def _check_recursion(self, cr, uid, ids):
191 cr.execute('select distinct parent_id from product_category where id IN %s',(tuple(ids),))
192 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
199 (_check_recursion, 'Error ! You can not create recursive categories.', ['parent_id'])
201 def child_get(self, cr, uid, ids):
207 #----------------------------------------------------------
209 #----------------------------------------------------------
210 class product_template(osv.osv):
211 _name = "product.template"
212 _description = "Product Template"
213 def _calc_seller(self, cr, uid, ids, fields, arg, context={}):
215 for product in self.browse(cr, uid, ids, context):
217 result[product.id] = {field:False}
218 result[product.id]['seller_delay'] = 1
219 if product.seller_ids:
220 partner_list = sorted([(partner_id.sequence, partner_id) for partner_id in product.seller_ids if partner_id and partner_id.sequence])
221 main_supplier = partner_list and partner_list[0] and partner_list[0][1] or False
222 result[product.id]['seller_delay'] = main_supplier and main_supplier.delay or 1
223 result[product.id]['seller_qty'] = main_supplier and main_supplier.qty or 0.0
224 result[product.id]['seller_id'] = main_supplier and main_supplier.name.id or False
228 'name': fields.char('Name', size=128, required=True, translate=True, select=True),
229 'product_manager': fields.many2one('res.users','Product Manager',help="This is use as task responsible"),
230 'description': fields.text('Description',translate=True),
231 'description_purchase': fields.text('Purchase Description',translate=True),
232 'description_sale': fields.text('Sale Description',translate=True),
233 '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 inventory management in the system."),
234 '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."),
235 'sale_delay': fields.float('Customer Lead Time', help="This is the average delay in days between the confirmation of the customer order and the delivery of the finished products. It's the time you promise to your customers."),
236 'produce_delay': fields.float('Manufacturing Lead Time', help="Average delay in days 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 lead times will be summed for all levels and purchase orders."),
237 'procure_method': fields.selection([('make_to_stock','Make to Stock'),('make_to_order','Make to Order')], 'Procurement 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."),
238 'rental': fields.boolean('Can be Rent'),
239 'categ_id': fields.many2one('product.category','Category', required=True, change_default=True, domain="[('type','=','normal')]" ,help="Select category for the current product"),
240 'list_price': fields.float('Sale Price', digits_compute=dp.get_precision('Sale Price'), help="Base price for computing the customer price. Sometimes called the catalog price."),
241 'standard_price': fields.float('Cost Price', required=True, digits_compute=dp.get_precision('Account'), help="Product's cost for accounting stock valuation. It is the base price for the supplier price."),
242 'volume': fields.float('Volume', help="The volume in m3."),
243 'weight': fields.float('Gross weight', help="The gross weight in Kg."),
244 'weight_net': fields.float('Net weight', help="The net weight in Kg."),
245 'cost_method': fields.selection([('standard','Standard Price'), ('average','Average Price')], 'Costing Method', required=True,
246 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."),
247 'warranty': fields.float('Warranty (months)'),
248 'sale_ok': fields.boolean('Can be Sold', help="Determines if the product can be visible in the list of product within a selection from a sale order line."),
249 '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."),
250 'state': fields.selection([('',''),
251 ('draft', 'In Development'),
252 ('sellable','Normal'),
253 ('end','End of Lifecycle'),
254 ('obsolete','Obsolete')], 'Status', help="Tells the user if he can use the product or not."),
255 'uom_id': fields.many2one('product.uom', 'Default Unit Of Measure', required=True, help="Default Unit of Measure used for all stock operation."),
256 'uom_po_id': fields.many2one('product.uom', 'Purchase Unit of Measure', required=True, help="Default Unit of Measure used for purchase orders. It must be in the same category than the default unit of measure."),
257 'uos_id' : fields.many2one('product.uom', 'Unit of Sale',
258 help='Used by companies that manage two units of measure: invoicing and inventory management. For example, in food industries, you will manage a stock of ham but invoice in Kg. Keep empty to use the default UOM.'),
259 'uos_coeff': fields.float('UOM -> UOS Coeff', digits=(16,4),
260 help='Coefficient to convert UOM to UOS\n'
261 ' uos = uom * coeff'),
262 'mes_type': fields.selection((('fixed', 'Fixed'), ('variable', 'Variable')), 'Measure Type', required=True),
263 'seller_delay': fields.function(_calc_seller, method=True, type='integer', string='Supplier Lead Time', multi="seller_delay", 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."),
264 'seller_qty': fields.function(_calc_seller, method=True, type='float', string='Supplier Quantity', multi="seller_qty", help="This is minimum quantity to purchase from Main Supplier."),
265 'seller_id': fields.function(_calc_seller, method=True, type='many2one', relation="res.partner", string='Main Supplier', help="Main Supplier who has highest priority in Supplier List.", multi="seller_id"),
266 'seller_ids': fields.one2many('product.supplierinfo', 'product_id', 'Partners'),
267 'loc_rack': fields.char('Rack', size=16),
268 'loc_row': fields.char('Row', size=16),
269 'loc_case': fields.char('Case', size=16),
270 'company_id': fields.many2one('res.company', 'Company',select=1),
273 def _get_uom_id(self, cr, uid, *args):
274 cr.execute('select id from product_uom order by id limit 1')
276 return res and res[0] or False
278 def _default_category(self, cr, uid, context={}):
279 if 'categ_id' in context and context['categ_id']:
280 return context['categ_id']
281 md = self.pool.get('ir.model.data')
282 res = md.get_object_reference(cr, uid, 'product', 'cat0') or False
283 return res and res[1] or False
285 def onchange_uom(self, cursor, user, ids, uom_id,uom_po_id):
287 return {'value': {'uom_po_id': uom_id}}
291 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'product.template', context=c),
292 'type': lambda *a: 'product',
293 'list_price': lambda *a: 1,
294 'cost_method': lambda *a: 'standard',
295 'supply_method': lambda *a: 'buy',
296 'standard_price': lambda *a: 1,
297 'sale_ok': lambda *a: 1,
298 'sale_delay': lambda *a: 7,
299 'produce_delay': lambda *a: 1,
300 'purchase_ok': lambda *a: 1,
301 'procure_method': lambda *a: 'make_to_stock',
302 'uom_id': _get_uom_id,
303 'uom_po_id': _get_uom_id,
304 'uos_coeff' : lambda *a: 1.0,
305 'mes_type' : lambda *a: 'fixed',
306 'categ_id' : _default_category,
307 'type' : lambda *a: 'consu',
310 def _check_uom(self, cursor, user, ids):
311 for product in self.browse(cursor, user, ids):
312 if product.uom_id.category_id.id <> product.uom_po_id.category_id.id:
316 def _check_uos(self, cursor, user, ids):
317 for product in self.browse(cursor, user, ids):
319 and product.uos_id.category_id.id \
320 == product.uom_id.category_id.id:
325 (_check_uos, 'Error: UOS must be in a different category than the UOM', ['uos_id']),
326 (_check_uom, 'Error: The default UOM and the purchase UOM must be in the same category.', ['uom_id']),
329 def name_get(self, cr, user, ids, context={}):
330 if 'partner_id' in context:
332 return super(product_template, self).name_get(cr, user, ids, context)
336 class product_product(osv.osv):
337 def view_header_get(self, cr, uid, view_id, view_type, context):
338 res = super(product_product, self).view_header_get(cr, uid, view_id, view_type, context)
339 if (context.get('categ_id', False)):
340 return _('Products: ')+self.pool.get('product.category').browse(cr, uid, context['categ_id'], context).name
343 def _product_price(self, cr, uid, ids, name, arg, context={}):
345 quantity = context.get('quantity', 1)
346 pricelist = context.get('pricelist', False)
350 price = self.pool.get('product.pricelist').price_get(cr,uid,[pricelist], id, quantity, context=context)[pricelist]
355 res.setdefault(id, 0.0)
358 def _get_product_available_func(states, what):
359 def _product_available(self, cr, uid, ids, name, arg, context={}):
360 return {}.fromkeys(ids, 0.0)
361 return _product_available
363 _product_qty_available = _get_product_available_func(('done',), ('in', 'out'))
364 _product_virtual_available = _get_product_available_func(('confirmed','waiting','assigned','done'), ('in', 'out'))
365 _product_outgoing_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('out',))
366 _product_incoming_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('in',))
368 def _product_lst_price(self, cr, uid, ids, name, arg, context=None):
370 product_uom_obj = self.pool.get('product.uom')
372 res.setdefault(id, 0.0)
373 for product in self.browse(cr, uid, ids, context=context):
375 uom = product.uos_id or product.uom_id
376 res[product.id] = product_uom_obj._compute_price(cr, uid,
377 uom.id, product.list_price, context['uom'])
379 res[product.id] = product.list_price
380 res[product.id] = (res[product.id] or 0.0) * (product.price_margin or 1.0) + product.price_extra
383 def _get_partner_code_name(self, cr, uid, ids, product, partner_id, context={}):
384 for supinfo in product.seller_ids:
385 if supinfo.name.id == partner_id:
386 return {'code': supinfo.product_code or product.default_code, 'name': supinfo.product_name or product.name, 'variants': ''}
387 res = {'code': product.default_code, 'name': product.name, 'variants': product.variants}
390 def _product_code(self, cr, uid, ids, name, arg, context={}):
392 for p in self.browse(cr, uid, ids, context):
393 res[p.id] = self._get_partner_code_name(cr, uid, [], p, context.get('partner_id', None), context)['code']
396 def _product_partner_ref(self, cr, uid, ids, name, arg, context={}):
398 for p in self.browse(cr, uid, ids, context):
399 data = self._get_partner_code_name(cr, uid, [], p, context.get('partner_id', None), context)
400 if not data['variants']:
401 data['variants'] = p.variants
403 data['code'] = p.code
405 data['name'] = p.name
406 res[p.id] = (data['code'] and ('['+data['code']+'] ') or '') + \
407 (data['name'] or '') + (data['variants'] and (' - '+data['variants']) or '')
411 'active': lambda *a: 1,
412 'price_extra': lambda *a: 0.0,
413 'price_margin': lambda *a: 1.0,
416 _name = "product.product"
417 _description = "Product"
418 _table = "product_product"
419 _inherits = {'product.template': 'product_tmpl_id'}
421 'qty_available': fields.function(_product_qty_available, method=True, type='float', string='Real Stock'),
422 'virtual_available': fields.function(_product_virtual_available, method=True, type='float', string='Virtual Stock'),
423 'incoming_qty': fields.function(_product_incoming_qty, method=True, type='float', string='Incoming'),
424 'outgoing_qty': fields.function(_product_outgoing_qty, method=True, type='float', string='Outgoing'),
425 'price': fields.function(_product_price, method=True, type='float', string='Pricelist', digits_compute=dp.get_precision('Sale Price')),
426 'lst_price' : fields.function(_product_lst_price, method=True, type='float', string='List Price', digits_compute=dp.get_precision('Sale Price')),
427 'code': fields.function(_product_code, method=True, type='char', string='Reference'),
428 'partner_ref' : fields.function(_product_partner_ref, method=True, type='char', string='Customer ref'),
429 'default_code' : fields.char('Reference', size=64),
430 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the product without removing it."),
431 'variants': fields.char('Variants', size=64),
432 'product_tmpl_id': fields.many2one('product.template', 'Product Template', required=True, ondelete="cascade"),
433 'ean13': fields.char('EAN13', size=13),
434 '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 picking order and is mainly used if you use the EDI module."),
435 'price_extra': fields.float('Variant Price Extra', digits_compute=dp.get_precision('Sale Price')),
436 'price_margin': fields.float('Variant Price Margin', digits_compute=dp.get_precision('Sale Price')),
437 'pricelist_id': fields.dummy(string='Pricelist', relation='product.pricelist', type='many2one'),
440 def onchange_uom(self, cursor, user, ids, uom_id,uom_po_id):
441 if uom_id and uom_po_id:
442 uom_obj=self.pool.get('product.uom')
443 uom=uom_obj.browse(cursor,user,[uom_id])[0]
444 uom_po=uom_obj.browse(cursor,user,[uom_po_id])[0]
445 if uom.category_id.id != uom_po.category_id.id:
446 return {'value': {'uom_po_id': uom_id}}
449 def _check_ean_key(self, cr, uid, ids):
450 for partner in self.browse(cr, uid, ids):
451 if not partner.ean13:
453 if len(partner.ean13) <> 13:
462 sum += int(partner.ean13[i])
464 sum += 3 * int(partner.ean13[i])
465 check = int(math.ceil(sum / 10.0) * 10 - sum)
466 if check != int(partner.ean13[12]):
470 _constraints = [(_check_ean_key, 'Error: Invalid ean code', ['ean13'])]
472 def on_order(self, cr, uid, ids, orderline, quantity):
475 def name_get(self, cr, user, ids, context={}):
479 name = d.get('name','')
480 code = d.get('default_code',False)
482 name = '[%s] %s' % (code,name)
484 name = name + ' - %s' % (d['variants'],)
485 return (d['id'], name)
486 result = map(_name_get, self.read(cr, user, ids, ['variants','name','default_code'], context))
489 def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
495 ids = self.search(cr, user, [('default_code','=',name)]+ args, limit=limit, context=context)
497 ids = self.search(cr, user, [('ean13','=',name)]+ args, limit=limit, context=context)
499 ids = self.search(cr, user, [('default_code',operator,name)]+ args, limit=limit, context=context)
500 ids += self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
502 ptrn=re.compile('(\[(.*?)\])')
503 res = ptrn.search(str(name))
505 ids = self.search(cr, user, [('default_code','ilike',res.group(2))]+ args, limit=limit, context=context)
507 ids = self.search(cr, user, args, limit=limit, context=context)
508 result = self.name_get(cr, user, ids, context)
512 # Could be overrided for variants matrices prices
514 def price_get(self, cr, uid, ids, ptype='list_price', context=None):
518 if 'currency_id' in context:
519 pricetype_obj = self.pool.get('product.price.type')
520 price_type_id = pricetype_obj.search(cr, uid, [('field','=',ptype)])[0]
521 price_type_currency_id = pricetype_obj.browse(cr,uid,price_type_id).currency_id.id
524 product_uom_obj = self.pool.get('product.uom')
525 for product in self.browse(cr, uid, ids, context=context):
526 res[product.id] = product[ptype] or 0.0
527 if ptype == 'list_price':
528 res[product.id] = (res[product.id] * (product.price_margin or 1.0)) + \
531 uom = product.uos_id or product.uom_id
532 res[product.id] = product_uom_obj._compute_price(cr, uid,
533 uom.id, res[product.id], context['uom'])
534 # Convert from price_type currency to asked one
535 if 'currency_id' in context:
536 # Take the price_type currency from the product field
537 # This is right cause a field cannot be in more than one currency
538 res[product.id] = self.pool.get('res.currency').compute(cr, uid, price_type_currency_id,
539 context['currency_id'], res[product.id],context=context)
543 def copy(self, cr, uid, id, default=None, context=None):
547 product = self.read(cr, uid, id, ['name'], context=context)
550 default = default.copy()
551 default['name'] = product['name'] + _(' (copy)')
553 if context.get('variant',False):
554 fields = ['product_tmpl_id', 'active', 'variants', 'default_code',
555 'price_margin', 'price_extra']
556 data = self.read(cr, uid, id, fields=fields, context=context)
560 data['product_tmpl_id'] = data.get('product_tmpl_id', False) \
561 and data['product_tmpl_id'][0]
563 return self.create(cr, uid, data)
565 return super(product_product, self).copy(cr, uid, id, default=default,
569 class product_packaging(osv.osv):
570 _name = "product.packaging"
571 _description = "Packaging"
575 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of packaging."),
576 'name' : fields.text('Description', size=64),
577 'qty' : fields.float('Quantity by Package',
578 help="The total number of products you can put by pallet or box."),
579 'ul' : fields.many2one('product.ul', 'Type of Package', required=True),
580 'ul_qty' : fields.integer('Package by layer', help='The number of packages by layer'),
581 'rows' : fields.integer('Number of Layers', required=True,
582 help='The number of layers on a pallet or box'),
583 'product_id' : fields.many2one('product.product', 'Product', select=1, ondelete='cascade', required=True),
584 'ean' : fields.char('EAN', size=14,
585 help="The EAN code of the package unit."),
586 'code' : fields.char('Code', size=14,
587 help="The code of the transport unit."),
588 'weight': fields.float('Total Package Weight',
589 help='The weight of a full package, pallet or box.'),
590 'weight_ul': fields.float('Empty Package Weight',
591 help='The weight of the empty UL'),
592 'height': fields.float('Height', help='The height of the package'),
593 'width': fields.float('Width', help='The width of the package'),
594 'length': fields.float('Length', help='The length of the package'),
598 def name_get(self, cr, uid, ids, context={}):
602 for pckg in self.browse(cr, uid, ids,context=context):
603 p_name = pckg.ean and '[' + pckg.ean + '] ' or ''
604 p_name += pckg.ul.name
605 res.append((pckg.id,p_name))
608 def _get_1st_ul(self, cr, uid, context={}):
609 cr.execute('select id from product_ul order by id asc limit 1')
611 return (res and res[0]) or False
614 'rows' : lambda *a : 3,
615 'sequence' : lambda *a : 1,
620 salt = '31' * 6 + '3'
622 for ean_part, salt_part in zip(ean, salt):
623 sum += int(ean_part) * int(salt_part)
624 return (10 - (sum % 10)) % 10
625 checksum = staticmethod(checksum)
630 class product_supplierinfo(osv.osv):
631 _name = "product.supplierinfo"
632 _description = "Information about a product supplier"
633 def _calc_qty(self, cr, uid, ids, fields, arg, context={}):
635 product_uom_pool = self.pool.get('product.uom')
636 for supplier_info in self.browse(cr, uid, ids, context):
638 result[supplier_info.id] = {field:False}
639 if supplier_info.product_uom.id:
640 qty = product_uom_pool._compute_qty(cr, uid, supplier_info.product_uom.id, supplier_info.min_qty, to_uom_id=supplier_info.product_id.uom_id.id)
642 qty = supplier_info.min_qty
643 result[supplier_info.id]['qty'] = qty
646 def _get_uom_id(self, cr, uid, *args):
647 cr.execute('select id from product_uom order by id limit 1')
649 return res and res[0] or False
652 'name' : fields.many2one('res.partner', 'Supplier', required=True, ondelete='cascade', help="Supplier of this product"),
653 'product_name': fields.char('Supplier Product Name', size=128, help="This supplier's product name will be used when printing a request for quotation. Keep empty to use the internal one."),
654 'product_code': fields.char('Supplier Product Code', size=64, help="This supplier's product code will be used when printing a request for quotation. Keep empty to use the internal one."),
655 'sequence' : fields.integer('Sequence', help="Assigns the priority to the list of product supplier."),
656 'product_uom': fields.many2one('product.uom', string="UOM", help="Supplier Product UoM."),
657 'min_qty': fields.float('Minimal Quantity', required=True, help="The minimal quantity to purchase to this supplier, expressed in the default unit of measure."),
658 'qty': fields.function(_calc_qty, method=True, store=True, type='float', string='Quantity', multi="qty", help="This is a quantity which is converted into Default Uom."),
659 'product_id' : fields.many2one('product.template', 'Product', required=True, ondelete='cascade', select=True),
660 'delay' : fields.integer('Delivery Lead Time', required=True, help="Lead time 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."),
661 'pricelist_ids': fields.one2many('pricelist.partnerinfo', 'suppinfo_id', 'Supplier Pricelist'),
662 'company_id':fields.many2one('res.company','Company',select=1),
665 'qty': lambda *a: 0.0,
666 'sequence': lambda *a: 1,
667 'delay': lambda *a: 1,
668 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'product.supplierinfo', context=c)
670 def _check_uom(self, cr, uid, ids):
671 for supplier_info in self.browse(cr, uid, ids):
672 if supplier_info.product_uom and supplier_info.product_uom.category_id.id <> supplier_info.product_id.uom_id.category_id.id:
677 (_check_uom, 'Error: The default UOM and the Supplier Product UOM must be in the same category.', ['product_uom']),
679 def price_get(self, cr, uid, supplier_ids, product_id, product_qty=1, context=None):
681 Calculate price from supplier pricelist.
682 @param supplier_ids: Ids of res.partner object.
683 @param product_id: Id of product.
684 @param product_qty: specify quantity to purchase.
688 if type(supplier_ids) in (int,long,):
689 supplier_ids = [supplier_ids]
691 product_pool = self.pool.get('product.product')
692 partner_pool = self.pool.get('res.partner')
693 pricelist_pool = self.pool.get('product.pricelist')
694 currency_pool = self.pool.get('res.currency')
695 currency_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id.id
696 for supplier in partner_pool.browse(cr, uid, supplier_ids, context=context):
697 # Compute price from standard price of product
698 price = product_pool.price_get(cr, uid, [product_id], 'standard_price')[product_id]
700 # Compute price from Purchase pricelist of supplier
701 pricelist_id = supplier.property_product_pricelist_purchase.id
703 price = pricelist_pool.price_get(cr, uid, [pricelist_id], product_id, product_qty).setdefault(pricelist_id, 0)
704 price = currency_pool.compute(cr, uid, pricelist_pool.browse(cr, uid, pricelist_id).currency_id.id, currency_id, price)
706 # Compute price from supplier pricelist which are in Supplier Information
707 supplier_info_ids = self.search(cr, uid, [('name','=',supplier.id),('product_id','=',product_id)])
708 if supplier_info_ids:
709 cr.execute('SELECT * ' \
710 'FROM pricelist_partnerinfo ' \
711 'WHERE suppinfo_id IN %s' \
712 'AND min_quantity <= %s ' \
713 'ORDER BY min_quantity DESC LIMIT 1', (tuple(supplier_info_ids),product_qty,))
714 res2 = cr.dictfetchone()
716 price = res2['price']
717 res[supplier.id] = price
720 product_supplierinfo()
723 class pricelist_partnerinfo(osv.osv):
724 _name = 'pricelist.partnerinfo'
726 'name': fields.char('Description', size=64),
727 'suppinfo_id': fields.many2one('product.supplierinfo', 'Partner Information', required=True, ondelete='cascade'),
728 'min_quantity': fields.float('Quantity', required=True),
729 'price': fields.float('Unit Price', required=True, digits_compute=dp.get_precision('Purchase Price')),
731 _order = 'min_quantity asc'
732 pricelist_partnerinfo()
733 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
735 class res_users(osv.osv):
736 _inherit = 'res.users'
737 def _get_group(self, cr, uid, context):
738 result = super(res_users, self)._get_group(cr, uid, context)
739 dataobj = self.pool.get('ir.model.data')
741 dummy,group_id = dataobj.get_object_reference(cr, 1, 'product', 'group_product_manager')
742 result.append(group_id)
744 # If these groups does not exists anymore
749 'groups_id': _get_group,