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
27 from _common import rounding
29 from tools import config
30 from tools.translate import _
35 #----------------------------------------------------------
37 #----------------------------------------------------------
39 class product_uom_categ(osv.osv):
40 _name = 'product.uom.categ'
41 _description = 'Product uom categ'
43 'name': fields.char('Name', size=64, required=True, translate=True),
47 class product_uom(osv.osv):
49 _description = 'Product Unit of Measure'
51 def _factor(self, cursor, user, ids, name, arg, context):
53 for uom in self.browse(cursor, user, ids, context=context):
55 if uom.factor_inv_data:
56 res[uom.id] = uom.factor_inv_data
58 res[uom.id] = round(1 / uom.factor, 6)
63 def _factor_inv(self, cursor, user, id, name, value, arg, context):
65 if 'read_delta' in ctx:
69 if round(1 / round(1/value, 6), 6) != value:
71 self.write(cursor, user, id, {
72 'factor': round(1/value, 6),
73 'factor_inv_data': data,
76 self.write(cursor, user, id, {
78 'factor_inv_data': 0.0,
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('Ratio', digits=(12, 6), required=True,
86 help='The coefficient for the formula:\n' \
87 '1 (base unit) = coeff (this unit). Ratio = 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', help="If the active field is set to true, it will allow you to hide the unit of measure without removing it."),
96 'uom_factor': fields.selection([('bigger','Bigger than the Default'),
97 ('smaller','Smaller than the Default'),
98 ('','')],'UoM Factor'),
102 'factor': lambda *a: 1.0,
103 'factor_inv': lambda *a: 1.0,
104 'active': lambda *a: 1,
105 'rounding': lambda *a: 0.01,
106 'uom_factor': lambda *a: 'smaller',
110 ('factor_gt_zero', 'CHECK (factor!=0)', 'Value of the factor can never be 0 !'),
111 ('factor_inv_data_gt_zero', 'CHECK (factor_inv_data!=0)', 'Value of the factor_inv_data can never be 0 !'),
114 def _compute_qty(self, cr, uid, from_uom_id, qty, to_uom_id=False):
115 if not from_uom_id or not qty or not to_uom_id:
117 uoms = self.browse(cr, uid, [from_uom_id, to_uom_id])
118 if uoms[0].id == from_uom_id:
119 from_unit, to_unit = uoms[0], uoms[-1]
121 from_unit, to_unit = uoms[-1], uoms[0]
122 return self._compute_qty_obj(cr, uid, from_unit, qty, to_unit)
124 def _compute_qty_obj(self, cr, uid, from_unit, qty, to_unit, context={}):
125 if from_unit.category_id.id <> to_unit.category_id.id:
127 if from_unit.factor_inv_data:
128 amount = qty * from_unit.factor_inv_data
130 amount = qty / from_unit.factor
132 if to_unit.factor_inv_data:
133 amount = rounding(amount / to_unit.factor_inv_data, to_unit.rounding)
135 amount = rounding(amount * to_unit.factor, to_unit.rounding)
138 def _compute_price(self, cr, uid, from_uom_id, price, to_uom_id=False):
139 if not from_uom_id or not price or not to_uom_id:
141 uoms = self.browse(cr, uid, [from_uom_id, to_uom_id])
142 if uoms[0].id == from_uom_id:
143 from_unit, to_unit = uoms[0], uoms[-1]
145 from_unit, to_unit = uoms[-1], uoms[0]
146 if from_unit.category_id.id <> to_unit.category_id.id:
148 if from_unit.factor_inv_data:
149 amount = price / from_unit.factor_inv_data
151 amount = price * from_unit.factor
153 if to_unit.factor_inv_data:
154 amount = amount * to_unit.factor_inv_data
156 amount = amount / to_unit.factor
159 def onchange_factor_inv(self, cursor, user, ids, value):
161 return {'value': {'factor': 0}}
162 return {'value': {'factor': round(1/value, 6)}}
164 def onchange_factor(self, cursor, user, ids, value):
166 return {'value': {'factor_inv': 0}}
167 return {'value': {'factor_inv': round(1/value, 6)}}
172 class product_ul(osv.osv):
174 _description = "Shipping Unit"
176 'name' : fields.char('Name', size=64,select=True, required=True, translate=True),
177 'type' : fields.selection([('unit','Unit'),('pack','Pack'),('box', 'Box'), ('pallet', 'Pallet')], 'Type', required=True),
182 #----------------------------------------------------------
184 #----------------------------------------------------------
185 class product_category(osv.osv):
187 def name_get(self, cr, uid, ids, context=None):
190 reads = self.read(cr, uid, ids, ['name','parent_id'], context)
193 name = record['name']
194 if record['parent_id']:
195 name = record['parent_id'][1]+' / '+name
196 res.append((record['id'], name))
199 def _name_get_fnc(self, cr, uid, ids, prop, unknow_none, context):
200 res = self.name_get(cr, uid, ids, context)
203 _name = "product.category"
204 _description = "Product Category"
206 'name': fields.char('Name', size=64, required=True, translate=True),
207 'complete_name': fields.function(_name_get_fnc, method=True, type="char", string='Name'),
208 'parent_id': fields.many2one('product.category','Parent Category', select=True),
209 'child_id': fields.one2many('product.category', 'parent_id', string='Child Categories'),
210 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of product categories."),
211 'type': fields.selection([('view','View'), ('normal','Normal')], 'Category Type'),
215 'type' : lambda *a : 'normal',
219 def _check_recursion(self, cr, uid, ids):
222 cr.execute('select distinct parent_id from product_category where id =ANY(%s)',(ids,))
223 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
230 (_check_recursion, 'Error ! You can not create recursive categories.', ['parent_id'])
232 def child_get(self, cr, uid, ids):
238 #----------------------------------------------------------
240 #----------------------------------------------------------
241 class product_template(osv.osv):
242 _name = "product.template"
243 _description = "Product Template"
244 def _calc_seller_delay(self, cr, uid, ids, name, arg, context={}):
246 for product in self.browse(cr, uid, ids, context):
247 if product.seller_ids:
248 result[product.id] = product.seller_ids[0].delay
250 result[product.id] = 1
254 'name': fields.char('Name', size=128, required=True, translate=True, select=True),
255 'product_manager': fields.many2one('res.users','Product Manager'),
256 'description': fields.text('Description',translate=True),
257 'description_purchase': fields.text('Purchase Description',translate=True),
258 'description_sale': fields.text('Sale Description',translate=True),
259 'type': fields.selection([('product','Stockable Product'),('consu', 'Consumable'),('service','Service')], 'Product Type', required=True, help="Will change the way requisitions are processed. Consumables are stockable products with infinite stock, or for use when you have no inventory management in the system."),
260 '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."),
261 '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."),
262 '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 lead times will be summed for all levels and purchase orders."),
263 'procure_method': fields.selection([('make_to_stock','Make to Stock'),('make_to_order','Make to Order')], 'Requisition 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 requisition request."),
264 'rental': fields.boolean('Can be Rent'),
265 'categ_id': fields.many2one('product.category','Category', required=True, change_default=True, domain="[('type','=','normal')]"),
266 '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."),
267 '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."),
268 'volume': fields.float('Volume', help="The volume in m3."),
269 'weight': fields.float('Gross weight', help="The gross weight in Kg."),
270 'weight_net': fields.float('Net weight', help="The net weight in Kg."),
271 'cost_method': fields.selection([('standard','Standard Price'), ('average','Average Price')], 'Costing Method', required=True,
272 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."),
273 'warranty': fields.float('Warranty (months)'),
274 '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."),
275 '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."),
276 'state': fields.selection([('',''),('draft', 'In Development'),('sellable','In Production'),('end','End of Lifecycle'),('obsolete','Obsolete')], 'State', help="Tells the user if he can use the product or not."),
277 'uom_id': fields.many2one('product.uom', 'Default UoM', required=True, help="Default Unit of Measure used for all stock operation."),
278 'uom_po_id': fields.many2one('product.uom', 'Purchase UoM', required=True, help="Default Unit of Measure used for purchase orders. It must be in the same category than the default unit of measure."),
279 'uos_id' : fields.many2one('product.uom', 'Unit of Sale',
280 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.'),
281 'uos_coeff': fields.float('UOM -> UOS Coeff', digits=(16,4),
282 help='Coefficient to convert UOM to UOS\n'
283 ' uom = uos * coeff'),
284 'mes_type': fields.selection((('fixed', 'Fixed'), ('variable', 'Variable')), 'Measure Type', required=True),
285 '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."),
286 'seller_ids': fields.one2many('product.supplierinfo', 'product_id', 'Partners'),
287 'loc_rack': fields.char('Rack', size=16),
288 'loc_row': fields.char('Row', size=16),
289 'loc_case': fields.char('Case', size=16),
290 'company_id': fields.many2one('res.company', 'Company',select=1),
293 def _get_uom_id(self, cr, uid, *args):
294 cr.execute('select id from product_uom order by id limit 1')
296 return res and res[0] or False
298 def _default_category(self, cr, uid, context={}):
299 if 'categ_id' in context and context['categ_id']:
300 return context['categ_id']
303 def onchange_uom(self, cursor, user, ids, uom_id,uom_po_id):
304 if uom_id and uom_po_id:
305 uom_obj=self.pool.get('product.uom')
306 uom=uom_obj.browse(cursor,user,[uom_id])[0]
307 uom_po=uom_obj.browse(cursor,user,[uom_po_id])[0]
308 if uom.category_id.id != uom_po.category_id.id:
309 return {'value': {'uom_po_id': uom_id}}
313 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'product.template', context=c),
314 # 'company_id': lambda self, cr, uid, context: False, # Visible by all
315 'type': lambda *a: 'product',
316 'list_price': lambda *a: 1,
317 'cost_method': lambda *a: 'standard',
318 'supply_method': lambda *a: 'buy',
319 'standard_price': lambda *a: 1,
320 'sale_ok': lambda *a: 1,
321 'sale_delay': lambda *a: 7,
322 'produce_delay': lambda *a: 1,
323 'purchase_ok': lambda *a: 1,
324 'procure_method': lambda *a: 'make_to_stock',
325 'uom_id': _get_uom_id,
326 'uom_po_id': _get_uom_id,
327 'uos_coeff' : lambda *a: 1.0,
328 'mes_type' : lambda *a: 'fixed',
329 'categ_id' : _default_category,
332 def _check_uom(self, cursor, user, ids):
333 for product in self.browse(cursor, user, ids):
334 if product.uom_id.category_id.id <> product.uom_po_id.category_id.id:
338 def _check_uos(self, cursor, user, ids):
339 for product in self.browse(cursor, user, ids):
341 and product.uos_id.category_id.id \
342 == product.uom_id.category_id.id:
347 (_check_uos, 'Error: UOS must be in a different category than the UOM', ['uos_id']),
348 (_check_uom, 'Error: The default UOM and the purchase UOM must be in the same category.', ['uom_id']),
351 def name_get(self, cr, user, ids, context={}):
352 if 'partner_id' in context:
354 return super(product_template, self).name_get(cr, user, ids, context)
358 class product_product(osv.osv):
359 def view_header_get(self, cr, uid, view_id, view_type, context):
360 res = super(product_product, self).view_header_get(cr, uid, view_id, view_type, context)
361 if (context.get('categ_id', False)):
362 return _('Products: ')+self.pool.get('product.category').browse(cr, uid, context['categ_id'], context).name
365 def _product_price(self, cr, uid, ids, name, arg, context={}):
367 quantity = context.get('quantity', 1)
368 pricelist = context.get('pricelist', False)
372 price = self.pool.get('product.pricelist').price_get(cr,uid,[pricelist], id, quantity, context=context)[pricelist]
377 res.setdefault(id, 0.0)
380 def _get_product_available_func(states, what):
381 def _product_available(self, cr, uid, ids, name, arg, context={}):
382 return {}.fromkeys(ids, 0.0)
383 return _product_available
385 _product_qty_available = _get_product_available_func(('done',), ('in', 'out'))
386 _product_virtual_available = _get_product_available_func(('confirmed','waiting','assigned','done'), ('in', 'out'))
387 _product_outgoing_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('out',))
388 _product_incoming_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('in',))
390 def _product_lst_price(self, cr, uid, ids, name, arg, context=None):
392 product_uom_obj = self.pool.get('product.uom')
394 res.setdefault(id, 0.0)
395 for product in self.browse(cr, uid, ids, context=context):
397 uom = product.uos_id or product.uom_id
398 res[product.id] = product_uom_obj._compute_price(cr, uid,
399 uom.id, product.list_price, context['uom'])
401 res[product.id] = product.list_price
402 res[product.id] = (res[product.id] or 0.0) * (product.price_margin or 1.0) + product.price_extra
405 def _get_partner_code_name(self, cr, uid, ids, product_id, partner_id, context={}):
406 product = self.browse(cr, uid, [product_id], context)[0]
407 for supinfo in product.seller_ids:
408 if supinfo.name.id == partner_id:
409 return {'code': supinfo.product_code, 'name': supinfo.product_name, 'variants': ''}
410 return {'code' : product.default_code, 'name' : product.name, 'variants': product.variants}
412 def _product_code(self, cr, uid, ids, name, arg, context={}):
414 for p in self.browse(cr, uid, ids, context):
415 res[p.id] = self._get_partner_code_name(cr, uid, [], p.id, context.get('partner_id', None), context)['code']
418 def _product_partner_ref(self, cr, uid, ids, name, arg, context={}):
420 for p in self.browse(cr, uid, ids, context):
421 data = self._get_partner_code_name(cr, uid, [], p.id, context.get('partner_id', None), context)
422 if not data['variants']:
423 data['variants'] = p.variants
425 data['code'] = p.code
427 data['name'] = p.name
428 res[p.id] = (data['code'] and ('['+data['code']+'] ') or '') + \
429 (data['name'] or '') + (data['variants'] and (' - '+data['variants']) or '')
433 'active': lambda *a: 1,
434 'price_extra': lambda *a: 0.0,
435 'price_margin': lambda *a: 1.0,
438 _name = "product.product"
439 _description = "Product"
440 _table = "product_product"
441 _inherits = {'product.template': 'product_tmpl_id'}
443 'qty_available': fields.function(_product_qty_available, method=True, type='float', string='Real Stock'),
444 'virtual_available': fields.function(_product_virtual_available, method=True, type='float', string='Virtual Stock'),
445 'incoming_qty': fields.function(_product_incoming_qty, method=True, type='float', string='Incoming'),
446 'outgoing_qty': fields.function(_product_outgoing_qty, method=True, type='float', string='Outgoing'),
447 'price': fields.function(_product_price, method=True, type='float', string='Customer Price', digits_compute=dp.get_precision('Sale Price')),
448 'lst_price' : fields.function(_product_lst_price, method=True, type='float', string='List Price', digits_compute=dp.get_precision('Sale Price')),
449 'code': fields.function(_product_code, method=True, type='char', string='Code'),
450 'partner_ref' : fields.function(_product_partner_ref, method=True, type='char', string='Customer ref'),
451 'default_code' : fields.char('Code', size=64),
452 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the product without removing it."),
453 'variants': fields.char('Variants', size=64),
454 'product_tmpl_id': fields.many2one('product.template', 'Product Template', required=False),
455 'ean13': fields.char('EAN13', size=13),
456 '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."),
457 'price_extra': fields.float('Variant Price Extra', digits_compute=dp.get_precision('Sale Price')),
458 'price_margin': fields.float('Variant Price Margin', digits_compute=dp.get_precision('Sale Price')),
459 'pricelist_id': fields.dummy(string='Pricelist',relation='product.pricelist', type='many2one'),
462 def onchange_uom(self, cursor, user, ids, uom_id,uom_po_id):
463 if uom_id and uom_po_id:
464 uom_obj=self.pool.get('product.uom')
465 uom=uom_obj.browse(cursor,user,[uom_id])[0]
466 uom_po=uom_obj.browse(cursor,user,[uom_po_id])[0]
467 if uom.category_id.id != uom_po.category_id.id:
468 return {'value': {'uom_po_id': uom_id}}
471 def _check_ean_key(self, cr, uid, ids):
472 for partner in self.browse(cr, uid, ids):
473 if not partner.ean13:
475 if len(partner.ean13) <> 13:
484 sum += int(partner.ean13[i])
486 sum += 3 * int(partner.ean13[i])
487 check = int(math.ceil(sum / 10.0) * 10 - sum)
488 if check != int(partner.ean13[12]):
492 _constraints = [(_check_ean_key, 'Error: Invalid ean code', ['ean13'])]
494 def on_order(self, cr, uid, ids, orderline, quantity):
497 def name_get(self, cr, user, ids, context={}):
501 #name = self._product_partner_ref(cr, user, [d['id']], '', '', context)[d['id']]
502 #code = self._product_code(cr, user, [d['id']], '', '', context)[d['id']]
503 name = d.get('name','')
504 code = d.get('default_code',False)
506 name = '[%s] %s' % (code,name)
508 name = name + ' - %s' % (d['variants'],)
509 return (d['id'], name)
510 result = map(_name_get, self.read(cr, user, ids, ['variants','name','default_code'], context))
513 def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
519 ids = self.search(cr, user, [('default_code','=',name)]+ args, limit=limit, context=context)
521 ids = self.search(cr, user, [('ean13','=',name)]+ args, limit=limit, context=context)
523 ids = self.search(cr, user, [('default_code',operator,name)]+ args, limit=limit, context=context)
524 ids += self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
526 ids = self.search(cr, user, args, limit=limit, context=context)
527 result = self.name_get(cr, user, ids, context)
531 # Could be overrided for variants matrices prices
533 def price_get(self, cr, uid, ids, ptype='list_price', context={}):
535 product_uom_obj = self.pool.get('product.uom')
536 for product in self.browse(cr, uid, ids, context=context):
537 res[product.id] = product[ptype] or 0.0
538 if ptype == 'list_price':
539 res[product.id] = (res[product.id] * (product.price_margin or 1.0)) + \
542 uom = product.uos_id or product.uom_id
543 res[product.id] = product_uom_obj._compute_price(cr, uid,
544 uom.id, res[product.id], context['uom'])
545 # Convert from price_type currency to asked one
546 if 'currency_id' in context:
547 pricetype_obj = self.pool.get('product.price.type')
548 # Take the price_type currency from the product field
549 # This is right cause a field cannot be in more than one currency
550 price_type_id = pricetype_obj.search(cr,ui,['field','=',ptype])[0]
551 price_type_currency_id = pricetype_obj.browse(cr,uid,price_type_id).currency_id.id
552 res[product.id] = self.pool.get('res.currency').compute(cr, uid, price_type_currency_id,
553 context['currency_id'], res[product.id],context=context)
557 def copy(self, cr, uid, id, default=None, context=None):
561 product = self.read(cr, uid, id, ['name'], context=context)
564 default = default.copy()
565 default['name'] = product['name'] + _(' (copy)')
567 if context.get('variant',False):
568 fields = ['product_tmpl_id', 'active', 'variants', 'default_code',
569 'price_margin', 'price_extra']
570 data = self.read(cr, uid, id, fields=fields, context=context)
574 data['product_tmpl_id'] = data.get('product_tmpl_id', False) \
575 and data['product_tmpl_id'][0]
577 return self.create(cr, uid, data)
579 return super(product_product, self).copy(cr, uid, id, default=default,
583 class product_packaging(osv.osv):
584 _name = "product.packaging"
585 _description = "Packaging"
588 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of packaging."),
589 'name' : fields.char('Description', size=64),
590 'qty' : fields.float('Quantity by Package',
591 help="The total number of products you can put by pallet or box."),
592 'ul' : fields.many2one('product.ul', 'Type of Package', required=True),
593 'ul_qty' : fields.integer('Package by layer', help='The number of packages by layer'),
594 'rows' : fields.integer('Number of Layers', required=True,
595 help='The number of layers on a pallet or box'),
596 'product_id' : fields.many2one('product.product', 'Product', select=1, ondelete='cascade', required=True),
597 'ean' : fields.char('EAN', size=14,
598 help="The EAN code of the package unit."),
599 'code' : fields.char('Code', size=14,
600 help="The code of the transport unit."),
601 'weight': fields.float('Total Package Weight',
602 help='The weight of a full package, pallet or box.'),
603 'weight_ul': fields.float('Empty Package Weight',
604 help='The weight of the empty UL'),
605 'height': fields.float('Height', help='The height of the package'),
606 'width': fields.float('Width', help='The width of the package'),
607 'length': fields.float('Length', help='The length of the package'),
612 def name_get(self, cr, uid, ids, context={}):
616 for pckg in self.browse(cr, uid, ids,context=context):
617 p_name = pckg.ean and '[' + pckg.ean + '] ' or ''
618 p_name += pckg.ul.name
619 res.append((pckg.id,p_name))
622 def _get_1st_ul(self, cr, uid, context={}):
623 cr.execute('select id from product_ul order by id asc limit 1')
625 return (res and res[0]) or False
628 'rows' : lambda *a : 3,
629 'sequence' : lambda *a : 1,
634 salt = '31' * 6 + '3'
636 for ean_part, salt_part in zip(ean, salt):
637 sum += int(ean_part) * int(salt_part)
638 return (10 - (sum % 10)) % 10
639 checksum = staticmethod(checksum)
644 class product_supplierinfo(osv.osv):
645 _name = "product.supplierinfo"
646 _description = "Information about a product supplier"
648 'name' : fields.many2one('res.partner', 'Partner', required=True, ondelete='cascade', help="Supplier of this product"),
649 'product_name': fields.char('Partner Product Name', size=128, help="This partner's product name will be used when printing a request for quotation. Keep empty to use the internal one."),
650 'product_code': fields.char('Partner Product Code', size=64, help="This partner's product code will be used when printing a request for quotation. Keep empty to use the internal one."),
651 'sequence' : fields.integer('Priority', help="Assigns the priority to the list of product supplier."),
652 'qty' : fields.float('Minimal Quantity', required=True, help="The minimal quantity to purchase to this supplier, expressed in the default unit of measure."),
653 'product_id' : fields.many2one('product.template', 'Product', required=True, ondelete='cascade', select=True),
654 '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."),
655 'pricelist_ids': fields.one2many('pricelist.partnerinfo', 'suppinfo_id', 'Supplier Pricelist'),
656 'company_id':fields.many2one('res.company','Company',select=1),
659 'qty': lambda *a: 0.0,
660 'sequence': lambda *a: 1,
661 'delay': lambda *a: 1,
662 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'product.supplierinfo', context=c)
665 product_supplierinfo()
668 class pricelist_partnerinfo(osv.osv):
669 _name = 'pricelist.partnerinfo'
671 'name': fields.char('Description', size=64),
672 'suppinfo_id': fields.many2one('product.supplierinfo', 'Partner Information', required=True, ondelete='cascade'),
673 'min_quantity': fields.float('Quantity', required=True),
674 'price': fields.float('Unit Price', required=True, digits_compute=dp.get_precision('Purchase Price')),
676 _order = 'min_quantity asc'
677 pricelist_partnerinfo()
681 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: