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, 12),
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=None):
112 if from_unit.category_id.id <> to_unit.category_id.id:
113 if context.get('raise-exception', True):
114 raise osv.except_osv(_('Error !'), _('Conversion from Product UoM m to Default UoM PCE is not possible as they both belong to different Category!.'))
117 amount = qty / from_unit.factor
119 amount = rounding(amount * to_unit.factor, to_unit.rounding)
122 def _compute_price(self, cr, uid, from_uom_id, price, to_uom_id=False):
123 if not from_uom_id or not price or not to_uom_id:
125 uoms = self.browse(cr, uid, [from_uom_id, to_uom_id])
126 if uoms[0].id == from_uom_id:
127 from_unit, to_unit = uoms[0], uoms[-1]
129 from_unit, to_unit = uoms[-1], uoms[0]
130 if from_unit.category_id.id <> to_unit.category_id.id:
132 amount = price * from_unit.factor
134 amount = amount / to_unit.factor
137 def onchange_type(self, cursor, user, ids, value):
138 if value == 'reference':
139 return {'value': {'factor': 1, 'factor_inv': 1}}
145 class product_ul(osv.osv):
147 _description = "Shipping Unit"
149 'name' : fields.char('Name', size=64,select=True, required=True, translate=True),
150 'type' : fields.selection([('unit','Unit'),('pack','Pack'),('box', 'Box'), ('pallet', 'Pallet')], 'Type', required=True),
155 #----------------------------------------------------------
157 #----------------------------------------------------------
158 class product_category(osv.osv):
160 def name_get(self, cr, uid, ids, context=None):
163 reads = self.read(cr, uid, ids, ['name','parent_id'], context)
166 name = record['name']
167 if record['parent_id']:
168 name = record['parent_id'][1]+' / '+name
169 res.append((record['id'], name))
172 def _name_get_fnc(self, cr, uid, ids, prop, unknow_none, context):
173 res = self.name_get(cr, uid, ids, context)
176 _name = "product.category"
177 _description = "Product Category"
179 'name': fields.char('Name', size=64, required=True, translate=True),
180 'complete_name': fields.function(_name_get_fnc, method=True, type="char", string='Name'),
181 'parent_id': fields.many2one('product.category','Parent Category', select=True),
182 'child_id': fields.one2many('product.category', 'parent_id', string='Child Categories'),
183 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of product categories."),
184 'type': fields.selection([('view','View'), ('normal','Normal')], 'Category Type'),
189 'type' : lambda *a : 'normal',
193 def _check_recursion(self, cr, uid, ids):
196 cr.execute('select distinct parent_id from product_category where id IN %s',(tuple(ids),))
197 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
204 (_check_recursion, 'Error ! You can not create recursive categories.', ['parent_id'])
206 def child_get(self, cr, uid, ids):
212 #----------------------------------------------------------
214 #----------------------------------------------------------
215 class product_template(osv.osv):
216 _name = "product.template"
217 _description = "Product Template"
218 def _calc_seller(self, cr, uid, ids, fields, arg, context={}):
220 for product in self.browse(cr, uid, ids, context):
222 result[product.id] = {field:False}
223 result[product.id]['seller_delay'] = 1
224 if product.seller_ids:
225 partner_list = sorted([(partner_id.sequence, partner_id) for partner_id in product.seller_ids if partner_id and partner_id.sequence])
226 main_supplier = partner_list and partner_list[0] and partner_list[0][1] or False
227 result[product.id]['seller_delay'] = main_supplier and main_supplier.delay or 1
228 result[product.id]['seller_qty'] = main_supplier and main_supplier.qty or 0.0
229 result[product.id]['seller_id'] = main_supplier and main_supplier.name.id or False
233 'name': fields.char('Name', size=128, required=True, translate=True, select=True),
234 'product_manager': fields.many2one('res.users','Product Manager',help="This is use as task responsible"),
235 'description': fields.text('Description',translate=True),
236 'description_purchase': fields.text('Purchase Description',translate=True),
237 'description_sale': fields.text('Sale Description',translate=True),
238 '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."),
239 '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."),
240 '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."),
241 '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."),
242 '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."),
243 'rental': fields.boolean('Can be Rent'),
244 'categ_id': fields.many2one('product.category','Category', required=True, change_default=True, domain="[('type','=','normal')]" ,help="Select category for the current product"),
245 '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."),
246 '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."),
247 'volume': fields.float('Volume', help="The volume in m3."),
248 'weight': fields.float('Gross weight', help="The gross weight in Kg."),
249 'weight_net': fields.float('Net weight', help="The net weight in Kg."),
250 'cost_method': fields.selection([('standard','Standard Price'), ('average','Average Price')], 'Costing Method', required=True,
251 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."),
252 'warranty': fields.float('Warranty (months)'),
253 '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."),
254 '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."),
255 'state': fields.selection([('',''),
256 ('draft', 'In Development'),
257 ('sellable','Normal'),
258 ('end','End of Lifecycle'),
259 ('obsolete','Obsolete')], 'Status', help="Tells the user if he can use the product or not."),
260 'uom_id': fields.many2one('product.uom', 'Default Unit Of Measure', required=True, help="Default Unit of Measure used for all stock operation."),
261 '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."),
262 'uos_id' : fields.many2one('product.uom', 'Unit of Sale',
263 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.'),
264 'uos_coeff': fields.float('UOM -> UOS Coeff', digits=(16,4),
265 help='Coefficient to convert UOM to UOS\n'
266 ' uos = uom * coeff'),
267 'mes_type': fields.selection((('fixed', 'Fixed'), ('variable', 'Variable')), 'Measure Type', required=True),
268 '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."),
269 '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."),
270 '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"),
271 'seller_ids': fields.one2many('product.supplierinfo', 'product_id', 'Partners'),
272 'loc_rack': fields.char('Rack', size=16),
273 'loc_row': fields.char('Row', size=16),
274 'loc_case': fields.char('Case', size=16),
275 'company_id': fields.many2one('res.company', 'Company',select=1),
278 def _get_uom_id(self, cr, uid, *args):
279 cr.execute('select id from product_uom order by id limit 1')
281 return res and res[0] or False
283 def _default_category(self, cr, uid, context={}):
284 if 'categ_id' in context and context['categ_id']:
285 return context['categ_id']
286 md = self.pool.get('ir.model.data')
287 res = md.get_object_reference(cr, uid, 'product', 'cat0') or False
288 return res and res[1] or False
290 def onchange_uom(self, cursor, user, ids, uom_id,uom_po_id):
292 return {'value': {'uom_po_id': uom_id}}
296 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'product.template', context=c),
297 'type': lambda *a: 'product',
298 'list_price': lambda *a: 1,
299 'cost_method': lambda *a: 'standard',
300 'supply_method': lambda *a: 'buy',
301 'standard_price': lambda *a: 1,
302 'sale_ok': lambda *a: 1,
303 'sale_delay': lambda *a: 7,
304 'produce_delay': lambda *a: 1,
305 'purchase_ok': lambda *a: 1,
306 'procure_method': lambda *a: 'make_to_stock',
307 'uom_id': _get_uom_id,
308 'uom_po_id': _get_uom_id,
309 'uos_coeff' : lambda *a: 1.0,
310 'mes_type' : lambda *a: 'fixed',
311 'categ_id' : _default_category,
312 'type' : lambda *a: 'consu',
315 def _check_uom(self, cursor, user, ids):
316 for product in self.browse(cursor, user, ids):
317 if product.uom_id.category_id.id <> product.uom_po_id.category_id.id:
321 def _check_uos(self, cursor, user, ids):
322 for product in self.browse(cursor, user, ids):
324 and product.uos_id.category_id.id \
325 == product.uom_id.category_id.id:
330 (_check_uos, 'Error: UOS must be in a different category than the UOM', ['uos_id']),
331 (_check_uom, 'Error: The default UOM and the purchase UOM must be in the same category.', ['uom_id']),
334 def name_get(self, cr, user, ids, context={}):
335 if 'partner_id' in context:
337 return super(product_template, self).name_get(cr, user, ids, context)
341 class product_product(osv.osv):
342 def view_header_get(self, cr, uid, view_id, view_type, context):
343 res = super(product_product, self).view_header_get(cr, uid, view_id, view_type, context)
344 if (context.get('categ_id', False)):
345 return _('Products: ')+self.pool.get('product.category').browse(cr, uid, context['categ_id'], context).name
348 def _product_price(self, cr, uid, ids, name, arg, context={}):
350 quantity = context.get('quantity') or 1.0
351 pricelist = context.get('pricelist', False)
355 price = self.pool.get('product.pricelist').price_get(cr,uid,[pricelist], id, quantity, context=context)[pricelist]
360 res.setdefault(id, 0.0)
363 def _get_product_available_func(states, what):
364 def _product_available(self, cr, uid, ids, name, arg, context={}):
365 return {}.fromkeys(ids, 0.0)
366 return _product_available
368 _product_qty_available = _get_product_available_func(('done',), ('in', 'out'))
369 _product_virtual_available = _get_product_available_func(('confirmed','waiting','assigned','done'), ('in', 'out'))
370 _product_outgoing_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('out',))
371 _product_incoming_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('in',))
373 def _product_lst_price(self, cr, uid, ids, name, arg, context=None):
375 product_uom_obj = self.pool.get('product.uom')
377 res.setdefault(id, 0.0)
378 for product in self.browse(cr, uid, ids, context=context):
380 uom = product.uos_id or product.uom_id
381 res[product.id] = product_uom_obj._compute_price(cr, uid,
382 uom.id, product.list_price, context['uom'])
384 res[product.id] = product.list_price
385 res[product.id] = (res[product.id] or 0.0) * (product.price_margin or 1.0) + product.price_extra
388 def _get_partner_code_name(self, cr, uid, ids, product, partner_id, context={}):
389 for supinfo in product.seller_ids:
390 if supinfo.name.id == partner_id:
391 return {'code': supinfo.product_code or product.default_code, 'name': supinfo.product_name or product.name, 'variants': ''}
392 res = {'code': product.default_code, 'name': product.name, 'variants': product.variants}
395 def _product_code(self, cr, uid, ids, name, arg, context={}):
397 for p in self.browse(cr, uid, ids, context):
398 res[p.id] = self._get_partner_code_name(cr, uid, [], p, context.get('partner_id', None), context)['code']
401 def _product_partner_ref(self, cr, uid, ids, name, arg, context={}):
403 for p in self.browse(cr, uid, ids, context):
404 data = self._get_partner_code_name(cr, uid, [], p, context.get('partner_id', None), context)
405 if not data['variants']:
406 data['variants'] = p.variants
408 data['code'] = p.code
410 data['name'] = p.name
411 res[p.id] = (data['code'] and ('['+data['code']+'] ') or '') + \
412 (data['name'] or '') + (data['variants'] and (' - '+data['variants']) or '')
416 'active': lambda *a: 1,
417 'price_extra': lambda *a: 0.0,
418 'price_margin': lambda *a: 1.0,
421 _name = "product.product"
422 _description = "Product"
423 _table = "product_product"
424 _inherits = {'product.template': 'product_tmpl_id'}
426 'qty_available': fields.function(_product_qty_available, method=True, type='float', string='Real Stock'),
427 'virtual_available': fields.function(_product_virtual_available, method=True, type='float', string='Virtual Stock'),
428 'incoming_qty': fields.function(_product_incoming_qty, method=True, type='float', string='Incoming'),
429 'outgoing_qty': fields.function(_product_outgoing_qty, method=True, type='float', string='Outgoing'),
430 'price': fields.function(_product_price, method=True, type='float', string='Pricelist', digits_compute=dp.get_precision('Sale Price')),
431 'lst_price' : fields.function(_product_lst_price, method=True, type='float', string='List Price', digits_compute=dp.get_precision('Sale Price')),
432 'code': fields.function(_product_code, method=True, type='char', string='Reference'),
433 'partner_ref' : fields.function(_product_partner_ref, method=True, type='char', string='Customer ref'),
434 'default_code' : fields.char('Reference', size=64),
435 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the product without removing it."),
436 'variants': fields.char('Variants', size=64),
437 'product_tmpl_id': fields.many2one('product.template', 'Product Template', required=True, ondelete="cascade"),
438 'ean13': fields.char('EAN13', size=13),
439 '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."),
440 'price_extra': fields.float('Variant Price Extra', digits_compute=dp.get_precision('Sale Price')),
441 'price_margin': fields.float('Variant Price Margin', digits_compute=dp.get_precision('Sale Price')),
442 'pricelist_id': fields.dummy(string='Pricelist', relation='product.pricelist', type='many2one'),
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}}
454 def _check_ean_key(self, cr, uid, ids):
455 for partner in self.browse(cr, uid, ids):
456 if not partner.ean13:
458 if len(partner.ean13) <> 13:
467 sum += int(partner.ean13[i])
469 sum += 3 * int(partner.ean13[i])
470 check = int(math.ceil(sum / 10.0) * 10 - sum)
471 if check != int(partner.ean13[12]):
475 _constraints = [(_check_ean_key, 'Error: Invalid ean code', ['ean13'])]
477 def on_order(self, cr, uid, ids, orderline, quantity):
480 def name_get(self, cr, user, ids, context={}):
484 name = d.get('name','')
485 code = d.get('default_code',False)
487 name = '[%s] %s' % (code,name)
489 name = name + ' - %s' % (d['variants'],)
490 return (d['id'], name)
491 result = map(_name_get, self.read(cr, user, ids, ['variants','name','default_code'], context))
494 def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
500 ids = self.search(cr, user, [('default_code',operator,name)]+ args, limit=limit, context=context)
502 ids = self.search(cr, user, [('ean13','=',name)]+ args, limit=limit, context=context)
504 ids = self.search(cr, user, [('default_code',operator,name)]+ args, limit=limit, context=context)
505 ids += self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
507 ptrn=re.compile('(\[(.*?)\])')
508 res = ptrn.search(str(name))
510 ids = self.search(cr, user, [('default_code','ilike',res.group(2))]+ args, limit=limit, context=context)
512 ids = self.search(cr, user, args, limit=limit, context=context)
513 result = self.name_get(cr, user, ids, context)
517 # Could be overrided for variants matrices prices
519 def price_get(self, cr, uid, ids, ptype='list_price', context=None):
523 if 'currency_id' in context:
524 pricetype_obj = self.pool.get('product.price.type')
525 price_type_id = pricetype_obj.search(cr, uid, [('field','=',ptype)])[0]
526 price_type_currency_id = pricetype_obj.browse(cr,uid,price_type_id).currency_id.id
529 product_uom_obj = self.pool.get('product.uom')
530 for product in self.browse(cr, uid, ids, context=context):
531 res[product.id] = product[ptype] or 0.0
532 if ptype == 'list_price':
533 res[product.id] = (res[product.id] * (product.price_margin or 1.0)) + \
536 uom = product.uos_id or product.uom_id
537 res[product.id] = product_uom_obj._compute_price(cr, uid,
538 uom.id, res[product.id], context['uom'])
539 # Convert from price_type currency to asked one
540 if 'currency_id' in context:
541 # Take the price_type currency from the product field
542 # This is right cause a field cannot be in more than one currency
543 res[product.id] = self.pool.get('res.currency').compute(cr, uid, price_type_currency_id,
544 context['currency_id'], res[product.id],context=context)
548 def copy(self, cr, uid, id, default=None, context=None):
552 product = self.read(cr, uid, id, ['name'], context=context)
555 default = default.copy()
556 default['name'] = product['name'] + _(' (copy)')
558 if context.get('variant',False):
559 fields = ['product_tmpl_id', 'active', 'variants', 'default_code',
560 'price_margin', 'price_extra']
561 data = self.read(cr, uid, id, fields=fields, context=context)
565 data['product_tmpl_id'] = data.get('product_tmpl_id', False) \
566 and data['product_tmpl_id'][0]
568 return self.create(cr, uid, data)
570 return super(product_product, self).copy(cr, uid, id, default=default,
574 class product_packaging(osv.osv):
575 _name = "product.packaging"
576 _description = "Packaging"
580 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of packaging."),
581 'name' : fields.text('Description', size=64),
582 'qty' : fields.float('Quantity by Package',
583 help="The total number of products you can put by pallet or box."),
584 'ul' : fields.many2one('product.ul', 'Type of Package', required=True),
585 'ul_qty' : fields.integer('Package by layer', help='The number of packages by layer'),
586 'rows' : fields.integer('Number of Layers', required=True,
587 help='The number of layers on a pallet or box'),
588 'product_id' : fields.many2one('product.product', 'Product', select=1, ondelete='cascade', required=True),
589 'ean' : fields.char('EAN', size=14,
590 help="The EAN code of the package unit."),
591 'code' : fields.char('Code', size=14,
592 help="The code of the transport unit."),
593 'weight': fields.float('Total Package Weight',
594 help='The weight of a full package, pallet or box.'),
595 'weight_ul': fields.float('Empty Package Weight',
596 help='The weight of the empty UL'),
597 'height': fields.float('Height', help='The height of the package'),
598 'width': fields.float('Width', help='The width of the package'),
599 'length': fields.float('Length', help='The length of the package'),
603 def name_get(self, cr, uid, ids, context={}):
607 for pckg in self.browse(cr, uid, ids,context=context):
608 p_name = pckg.ean and '[' + pckg.ean + '] ' or ''
609 p_name += pckg.ul.name
610 res.append((pckg.id,p_name))
613 def _get_1st_ul(self, cr, uid, context={}):
614 cr.execute('select id from product_ul order by id asc limit 1')
616 return (res and res[0]) or False
619 'rows' : lambda *a : 3,
620 'sequence' : lambda *a : 1,
625 salt = '31' * 6 + '3'
627 for ean_part, salt_part in zip(ean, salt):
628 sum += int(ean_part) * int(salt_part)
629 return (10 - (sum % 10)) % 10
630 checksum = staticmethod(checksum)
635 class product_supplierinfo(osv.osv):
636 _name = "product.supplierinfo"
637 _description = "Information about a product supplier"
638 def _calc_qty(self, cr, uid, ids, fields, arg, context={}):
640 product_uom_pool = self.pool.get('product.uom')
641 for supplier_info in self.browse(cr, uid, ids, context):
643 result[supplier_info.id] = {field:False}
644 if supplier_info.product_uom.id:
645 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)
647 qty = supplier_info.min_qty
648 result[supplier_info.id]['qty'] = qty
651 def _get_uom_id(self, cr, uid, *args):
652 cr.execute('select id from product_uom order by id limit 1')
654 return res and res[0] or False
657 'name' : fields.many2one('res.partner', 'Supplier', required=True,domain = [('supplier','=',True)], ondelete='cascade', help="Supplier of this product"),
658 '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."),
659 '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."),
660 'sequence' : fields.integer('Sequence', help="Assigns the priority to the list of product supplier."),
661 'product_uom': fields.many2one('product.uom', string="UOM", help="Supplier Product UoM."),
662 'min_qty': fields.float('Minimal Quantity', required=True, help="The minimal quantity to purchase to this supplier, expressed in the default unit of measure."),
663 '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."),
664 'product_id' : fields.many2one('product.template', 'Product', required=True, ondelete='cascade', select=True),
665 '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."),
666 'pricelist_ids': fields.one2many('pricelist.partnerinfo', 'suppinfo_id', 'Supplier Pricelist'),
667 'company_id':fields.many2one('res.company','Company',select=1),
670 'qty': lambda *a: 0.0,
671 'sequence': lambda *a: 1,
672 'delay': lambda *a: 1,
673 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'product.supplierinfo', context=c)
675 def _check_uom(self, cr, uid, ids):
676 for supplier_info in self.browse(cr, uid, ids):
677 if supplier_info.product_uom and supplier_info.product_uom.category_id.id <> supplier_info.product_id.uom_id.category_id.id:
682 (_check_uom, 'Error: The default UOM and the Supplier Product UOM must be in the same category.', ['product_uom']),
684 def price_get(self, cr, uid, supplier_ids, product_id, product_qty=1, context=None):
686 Calculate price from supplier pricelist.
687 @param supplier_ids: Ids of res.partner object.
688 @param product_id: Id of product.
689 @param product_qty: specify quantity to purchase.
693 if type(supplier_ids) in (int,long,):
694 supplier_ids = [supplier_ids]
696 product_pool = self.pool.get('product.product')
697 partner_pool = self.pool.get('res.partner')
698 pricelist_pool = self.pool.get('product.pricelist')
699 currency_pool = self.pool.get('res.currency')
700 currency_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id.id
701 for supplier in partner_pool.browse(cr, uid, supplier_ids, context=context):
702 # Compute price from standard price of product
703 price = product_pool.price_get(cr, uid, [product_id], 'standard_price')[product_id]
705 # Compute price from Purchase pricelist of supplier
706 pricelist_id = supplier.property_product_pricelist_purchase.id
708 price = pricelist_pool.price_get(cr, uid, [pricelist_id], product_id, product_qty).setdefault(pricelist_id, 0)
709 price = currency_pool.compute(cr, uid, pricelist_pool.browse(cr, uid, pricelist_id).currency_id.id, currency_id, price)
711 # Compute price from supplier pricelist which are in Supplier Information
712 supplier_info_ids = self.search(cr, uid, [('name','=',supplier.id),('product_id','=',product_id)])
713 if supplier_info_ids:
714 cr.execute('SELECT * ' \
715 'FROM pricelist_partnerinfo ' \
716 'WHERE suppinfo_id IN %s' \
717 'AND min_quantity <= %s ' \
718 'ORDER BY min_quantity DESC LIMIT 1', (tuple(supplier_info_ids),product_qty,))
719 res2 = cr.dictfetchone()
721 price = res2['price']
722 res[supplier.id] = price
725 product_supplierinfo()
728 class pricelist_partnerinfo(osv.osv):
729 _name = 'pricelist.partnerinfo'
731 'name': fields.char('Description', size=64),
732 'suppinfo_id': fields.many2one('product.supplierinfo', 'Partner Information', required=True, ondelete='cascade'),
733 'min_quantity': fields.float('Quantity', required=True),
734 'price': fields.float('Unit Price', required=True, digits_compute=dp.get_precision('Purchase Price')),
736 _order = 'min_quantity asc'
737 pricelist_partnerinfo()
738 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
740 class res_users(osv.osv):
741 _inherit = 'res.users'
742 def _get_group(self, cr, uid, context):
743 result = super(res_users, self)._get_group(cr, uid, context)
744 dataobj = self.pool.get('ir.model.data')
746 dummy,group_id = dataobj.get_object_reference(cr, 1, 'product', 'group_product_manager')
747 result.append(group_id)
749 # If these groups does not exists anymore
754 'groups_id': _get_group,