[MIG] product: migrate addon to new API
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!--
|
||||
Resource: product.uom.categ
|
||||
@@ -198,4 +198,4 @@ parameter) will see those record just disappear.
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="product_category_2" model="product.category">
|
||||
@@ -717,4 +717,4 @@ QWERTY keyboard</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="false">
|
||||
|
||||
<record id="product_product_1" model="product.product">
|
||||
@@ -107,4 +107,4 @@
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -1,29 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp.osv import osv, fields
|
||||
from odoo import api, fields, models
|
||||
|
||||
class base_config_settings(osv.TransientModel):
|
||||
|
||||
class BaseConfigSettings(models.TransientModel):
|
||||
_inherit = 'base.config.settings'
|
||||
|
||||
_columns = {
|
||||
'company_share_product': fields.boolean('Share product to all companies',
|
||||
help="Share your product to all companies defined in your instance.\n"
|
||||
" * Checked : Product are visible for every company, even if a company is defined on the partner.\n"
|
||||
" * Unchecked : Each company can see only its product (product where company is defined). Product not related to a company are visible for all companies."),
|
||||
'group_product_variant': fields.selection([
|
||||
(0, "No variants on products"),
|
||||
(1, 'Products can have several attributes, defining variants (Example: size, color,...)')
|
||||
], "Product Variants",
|
||||
help='Work with product variant allows you to define some variant of the same products, an ease the product management in the ecommerce for example',
|
||||
implied_group='product.group_product_variant'),
|
||||
}
|
||||
company_share_product = fields.Boolean(
|
||||
'Share product to all companies',
|
||||
help="Share your product to all companies defined in your instance.\n"
|
||||
" * Checked : Product are visible for every company, even if a company is defined on the partner.\n"
|
||||
" * Unchecked : Each company can see only its product (product where company is defined). Product not related to a company are visible for all companies.")
|
||||
group_product_variant = fields.Selection([
|
||||
(0, "No variants on products"),
|
||||
(1, 'Products can have several attributes, defining variants (Example: size, color,...)')], "Product Variants",
|
||||
implied_group='product.group_product_variant',
|
||||
help='Work with product variant allows you to define some variant of the same products, an ease the product management in the ecommerce for example')
|
||||
|
||||
def get_default_company_share_product(self, cr, uid, fields, context=None):
|
||||
product_rule = self.pool['ir.model.data'].xmlid_to_object(cr, uid, 'product.product_comp_rule', context=context)
|
||||
@api.model
|
||||
def get_default_company_share_product(self, fields):
|
||||
product_rule = self.env.ref('product.product_comp_rule')
|
||||
return {
|
||||
'company_share_product': not bool(product_rule.active)
|
||||
}
|
||||
|
||||
def set_auth_company_share_product(self, cr, uid, ids, context=None):
|
||||
product_rule = self.pool['ir.model.data'].xmlid_to_object(cr, uid, 'product.product_comp_rule', context=context)
|
||||
for wizard in self.browse(cr, uid, ids, context=context):
|
||||
self.pool['ir.rule'].write(cr, uid, [product_rule.id], {'active': not bool(wizard.company_share_product)}, context=context)
|
||||
@api.multi
|
||||
def set_auth_company_share_product(self):
|
||||
self.ensure_one()
|
||||
product_rule = self.env.ref('product.product_comp_rule')
|
||||
product_rule.write({'active': not bool(self.company_share_product)})
|
||||
|
||||
@@ -1,37 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import re
|
||||
import time
|
||||
|
||||
import openerp
|
||||
from openerp import api, tools, SUPERUSER_ID
|
||||
from openerp.osv import osv, fields, expression
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FORMAT
|
||||
import psycopg2
|
||||
|
||||
import openerp.addons.decimal_precision as dp
|
||||
from openerp.tools.float_utils import float_round, float_compare
|
||||
from openerp.exceptions import UserError
|
||||
from openerp.exceptions import except_orm
|
||||
from odoo import api, models, tools, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class decimal_precision(osv.osv):
|
||||
class DecimalPrecision(models.Model):
|
||||
_inherit = 'decimal.precision'
|
||||
|
||||
def _check_main_currency_rounding(self, cr, uid, ids, context=None):
|
||||
cr.execute('SELECT id, digits FROM decimal_precision WHERE name like %s',('Account',))
|
||||
res = cr.fetchone()
|
||||
if res and len(res):
|
||||
account_precision_id, digits = res
|
||||
main_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id
|
||||
for decimal_precision in ids:
|
||||
if decimal_precision == account_precision_id:
|
||||
if float_compare(main_currency.rounding, 10 ** -digits, precision_digits=6) == -1:
|
||||
return False
|
||||
@api.constrains('digits')
|
||||
def _check_main_currency_rounding(self):
|
||||
if any(precision.name == 'Account' and
|
||||
tools.float_compare(self.env.user.company_id.currency_id.rounding, 10 ** - precision.digits, precision_digits=6) == -1
|
||||
for precision in self):
|
||||
raise ValidationError(_("You cannot define the decimal precision of 'Account' as greater than the rounding factor of the company's main currency"))
|
||||
return True
|
||||
|
||||
_constraints = [
|
||||
(_check_main_currency_rounding, 'Error! You cannot define the decimal precision of \'Account\' as greater than the rounding factor of the company\'s main currency', ['digits']),
|
||||
]
|
||||
|
||||
+441
-477
File diff suppressed because it is too large
Load Diff
@@ -1,124 +1,103 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import re
|
||||
import time
|
||||
|
||||
import openerp
|
||||
from openerp import api, tools, SUPERUSER_ID
|
||||
from openerp.osv import osv, fields, expression
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FORMAT
|
||||
import psycopg2
|
||||
|
||||
import openerp.addons.decimal_precision as dp
|
||||
from openerp.tools.float_utils import float_round, float_compare
|
||||
from openerp.exceptions import UserError
|
||||
from openerp.exceptions import except_orm
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.addons import decimal_precision as dp
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
class product_attribute(osv.osv):
|
||||
class ProductAttribute(models.Model):
|
||||
_name = "product.attribute"
|
||||
_description = "Product Attribute"
|
||||
_order = 'sequence, name'
|
||||
_columns = {
|
||||
'name': fields.char('Name', translate=True, required=True),
|
||||
'value_ids': fields.one2many('product.attribute.value', 'attribute_id', 'Values', copy=True),
|
||||
'sequence': fields.integer('Sequence', help="Determine the display order"),
|
||||
'attribute_line_ids': fields.one2many('product.attribute.line', 'attribute_id', 'Lines'),
|
||||
}
|
||||
|
||||
class product_attribute_value(osv.osv):
|
||||
name = fields.Char('Name', required=True, translate=True)
|
||||
value_ids = fields.One2many('product.attribute.value', 'attribute_id', 'Values', copy=True)
|
||||
sequence = fields.Integer('Sequence', help="Determine the display order")
|
||||
attribute_line_ids = fields.One2many('product.attribute.line', 'attribute_id', 'Lines')
|
||||
|
||||
|
||||
class ProductAttributevalue(models.Model):
|
||||
_name = "product.attribute.value"
|
||||
_order = 'sequence'
|
||||
def _get_price_extra(self, cr, uid, ids, name, args, context=None):
|
||||
result = dict.fromkeys(ids, 0)
|
||||
if not context.get('active_id'):
|
||||
return result
|
||||
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
for price_id in obj.price_ids:
|
||||
if price_id.product_tmpl_id.id == context.get('active_id'):
|
||||
result[obj.id] = price_id.price_extra
|
||||
break
|
||||
return result
|
||||
name = fields.Char('Value', required=True, translate=True)
|
||||
sequence = fields.Integer('Sequence', help="Determine the display order")
|
||||
attribute_id = fields.Many2one('product.attribute', 'Attribute', ondelete='cascade', required=True)
|
||||
product_ids = fields.Many2many('product.product', id1='att_id', id2='prod_id', string='Variants', readonly=True)
|
||||
price_extra = fields.Float(
|
||||
'Attribute Price Extra', compute='_compute_price_extra', inverse='_set_price_extra',
|
||||
default=0.0, digits_compute=dp.get_precision('Product Price'),
|
||||
help="Price Extra: Extra price for the variant with this attribute value on sale price. eg. 200 price extra, 1000 + 200 = 1200.")
|
||||
price_ids = fields.One2many('product.attribute.price', 'value_id', 'Attribute Prices', readonly=True)
|
||||
|
||||
def _set_price_extra(self, cr, uid, id, name, value, args, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if 'active_id' not in context:
|
||||
return None
|
||||
p_obj = self.pool['product.attribute.price']
|
||||
p_ids = p_obj.search(cr, uid, [('value_id', '=', id), ('product_tmpl_id', '=', context['active_id'])], context=context)
|
||||
if p_ids:
|
||||
p_obj.write(cr, uid, p_ids, {'price_extra': value}, context=context)
|
||||
else:
|
||||
p_obj.create(cr, uid, {
|
||||
'product_tmpl_id': context['active_id'],
|
||||
'value_id': id,
|
||||
'price_extra': value,
|
||||
}, context=context)
|
||||
|
||||
def name_get(self, cr, uid, ids, context=None):
|
||||
if context and not context.get('show_attribute', True):
|
||||
return super(product_attribute_value, self).name_get(cr, uid, ids, context=context)
|
||||
res = []
|
||||
for value in self.browse(cr, uid, ids, context=context):
|
||||
res.append([value.id, "%s: %s" % (value.attribute_id.name, value.name)])
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'sequence': fields.integer('Sequence', help="Determine the display order"),
|
||||
'name': fields.char('Value', translate=True, required=True),
|
||||
'attribute_id': fields.many2one('product.attribute', 'Attribute', required=True, ondelete='cascade'),
|
||||
'product_ids': fields.many2many('product.product', id1='att_id', id2='prod_id', string='Variants', readonly=True),
|
||||
'price_extra': fields.function(_get_price_extra, type='float', string='Attribute Price Extra',
|
||||
fnct_inv=_set_price_extra,
|
||||
digits_compute=dp.get_precision('Product Price'),
|
||||
help="Price Extra: Extra price for the variant with this attribute value on sale price. eg. 200 price extra, 1000 + 200 = 1200."),
|
||||
'price_ids': fields.one2many('product.attribute.price', 'value_id', string='Attribute Prices', readonly=True),
|
||||
}
|
||||
_sql_constraints = [
|
||||
('value_company_uniq', 'unique (name,attribute_id)', 'This attribute value already exists !')
|
||||
]
|
||||
_defaults = {
|
||||
'price_extra': 0.0,
|
||||
}
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
ctx = dict(context or {}, active_test=False)
|
||||
product_ids = self.pool['product.product'].search(cr, uid, [('attribute_value_ids', 'in', ids)], context=ctx)
|
||||
if product_ids:
|
||||
|
||||
@api.one
|
||||
def _compute_price_extra(self):
|
||||
if self._context.get('active_id'):
|
||||
price = self.price_ids.filtered(lambda price: price.product_tmpl_id.id == self._context['active_id'])
|
||||
self.price_extra = price.price_extra
|
||||
else:
|
||||
self.price_extra = 0.0
|
||||
|
||||
def _set_price_extra(self):
|
||||
if not self._context.get('active_id'):
|
||||
return
|
||||
|
||||
AttributePrice = self.env['product.attribute.price']
|
||||
prices = AttributePrice.search([('value_id', 'in', self.ids), ('product_tmpl_id', '=', self._context['active_id'])])
|
||||
updated = prices.mapped('value_id')
|
||||
if prices:
|
||||
prices.write({'price_extra': self.price_extra})
|
||||
else:
|
||||
for value in self - updated:
|
||||
AttributePrice.create({
|
||||
'product_tmpl_id': self._context['active_id'],
|
||||
'value_id': value.id,
|
||||
'price_extra': self.price_extra,
|
||||
})
|
||||
|
||||
@api.multi
|
||||
def name_get(self):
|
||||
if not self._context.get('show_attribute', True): # TDE FIXME: not used
|
||||
return super(ProductAttributevalue, self).name_get()
|
||||
return [(value.id, "%s: %s" % (value.attribute_id.name, value.name)) for value in self]
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
linked_products = self.env['product.product'].with_context(active_test=False).search([('attribute_value_ids', 'in', self.ids)])
|
||||
if linked_products:
|
||||
raise UserError(_('The operation cannot be completed:\nYou are trying to delete an attribute value with a reference on a product variant.'))
|
||||
return super(product_attribute_value, self).unlink(cr, uid, ids, context=context)
|
||||
return super(ProductAttributevalue, self).unlink()
|
||||
|
||||
class product_attribute_price(osv.osv):
|
||||
|
||||
class ProductAttributePrice(models.Model):
|
||||
_name = "product.attribute.price"
|
||||
_columns = {
|
||||
'product_tmpl_id': fields.many2one('product.template', 'Product Template', required=True, ondelete='cascade'),
|
||||
'value_id': fields.many2one('product.attribute.value', 'Product Attribute Value', required=True, ondelete='cascade'),
|
||||
'price_extra': fields.float('Price Extra', digits_compute=dp.get_precision('Product Price')),
|
||||
}
|
||||
|
||||
class product_attribute_line(osv.osv):
|
||||
product_tmpl_id = fields.Many2one('product.template', 'Product Template', ondelete='cascade', required=True)
|
||||
value_id = fields.Many2one('product.attribute.value', 'Product Attribute Value', ondelete='cascade', required=True)
|
||||
price_extra = fields.Float('Price Extra', digits_compute=dp.get_precision('Product Price'))
|
||||
|
||||
|
||||
class ProductAttributeLine(models.Model):
|
||||
_name = "product.attribute.line"
|
||||
_rec_name = 'attribute_id'
|
||||
_columns = {
|
||||
'product_tmpl_id': fields.many2one('product.template', 'Product Template', required=True, ondelete='cascade'),
|
||||
'attribute_id': fields.many2one('product.attribute', 'Attribute', required=True, ondelete='restrict'),
|
||||
'value_ids': fields.many2many('product.attribute.value', id1='line_id', id2='val_id', string='Attribute Values'),
|
||||
}
|
||||
|
||||
def _check_valid_attribute(self, cr, uid, ids, context=None):
|
||||
for obj_pal in self.browse(cr, uid, ids, context=context):
|
||||
if not (obj_pal.value_ids <= obj_pal.attribute_id.value_ids):
|
||||
return False
|
||||
product_tmpl_id = fields.Many2one('product.template', 'Product Template', ondelete='cascade', required=True)
|
||||
attribute_id = fields.Many2one('product.attribute', 'Attribute', ondelete='restrict', required=True)
|
||||
value_ids = fields.Many2many('product.attribute.value', id1='line_id', id2='val_id', string='Attribute Values')
|
||||
|
||||
@api.constrains('value_ids', 'attribute_id')
|
||||
def _check_valid_attribute(self):
|
||||
if any(line.value_ids > line.attribute_id.value_ids for line in self):
|
||||
raise ValidationError(_('Error ! You cannot use this attribute with the following value.'))
|
||||
return True
|
||||
|
||||
_constraints = [
|
||||
(_check_valid_attribute, 'Error ! You cannot use this attribute with the following value.', ['attribute_id'])
|
||||
]
|
||||
|
||||
def name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=100):
|
||||
@api.model
|
||||
def name_search(self, name='', args=None, operator='ilike', limit=100):
|
||||
# TDE FIXME: currently overriding the domain; however as it includes a
|
||||
# search on a m2o and one on a m2m, probably this will quickly become
|
||||
# difficult to compute - check if performance optimization is required
|
||||
@@ -126,7 +105,4 @@ class product_attribute_line(osv.osv):
|
||||
new_args = ['|', ('attribute_id', operator, name), ('value_ids', operator, name)]
|
||||
else:
|
||||
new_args = args
|
||||
return super(product_attribute_line, self).name_search(
|
||||
cr, uid, name=name,
|
||||
args=new_args,
|
||||
operator=operator, context=context, limit=limit)
|
||||
return super(ProductAttributeLine, self).name_search(name=name, args=new_args, operator=operator, limit=limit)
|
||||
|
||||
@@ -1,50 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from collections import namedtuple, defaultdict
|
||||
from itertools import chain
|
||||
import time
|
||||
|
||||
from openerp import tools
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
import openerp.addons.decimal_precision as dp
|
||||
from openerp.exceptions import UserError
|
||||
from openerp import api, models, fields as Fields
|
||||
import odoo.addons.decimal_precision as dp
|
||||
|
||||
#----------------------------------------------------------
|
||||
# Price lists
|
||||
#----------------------------------------------------------
|
||||
|
||||
class product_pricelist(osv.osv):
|
||||
|
||||
class Pricelist(models.Model):
|
||||
_name = "product.pricelist"
|
||||
_description = "Pricelist"
|
||||
_order = 'name'
|
||||
_columns = {
|
||||
'name': fields.char('Pricelist Name', required=True, translate=True),
|
||||
'active': fields.boolean('Active', help="If unchecked, it will allow you to hide the pricelist without removing it."),
|
||||
'item_ids': fields.one2many('product.pricelist.item', 'pricelist_id', 'Pricelist Items', copy=True),
|
||||
'currency_id': fields.many2one('res.currency', 'Currency', required=True),
|
||||
'company_id': fields.many2one('res.company', 'Company'),
|
||||
}
|
||||
|
||||
def name_get(self, cr, uid, ids, context=None):
|
||||
result= []
|
||||
if not all(ids):
|
||||
return result
|
||||
for pl in self.browse(cr, uid, ids, context=context):
|
||||
name = pl.name + ' ('+ pl.currency_id.name + ')'
|
||||
result.append((pl.id,name))
|
||||
return result
|
||||
def _get_default_currency_id(self):
|
||||
return self.env.user.company_id.currency_id.id
|
||||
|
||||
def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
|
||||
def _get_default_item_ids(self):
|
||||
vals = {}
|
||||
# ProductPricelistItem = self.pool.get('product.pricelist.item')
|
||||
# fields_list = ProductPricelistItem._defaults.keys()
|
||||
# vals = ProductPricelistItem.default_get(cr, uid, fields_list, context=context)
|
||||
vals['compute_price'] = 'formula'
|
||||
return [[0, False, vals]]
|
||||
|
||||
name = fields.Char('Pricelist Name', required=True, translate=True)
|
||||
active = fields.Boolean('Active', default=True, help="If unchecked, it will allow you to hide the pricelist without removing it.")
|
||||
item_ids = fields.One2many(
|
||||
'product.pricelist.item', 'pricelist_id', 'Pricelist Items',
|
||||
copy=True, default=_get_default_item_ids)
|
||||
currency_id = fields.Many2one('res.currency', 'Currency', default=_get_default_currency_id, required=True)
|
||||
company_id = fields.Many2one('res.company', 'Company')
|
||||
|
||||
@api.multi
|
||||
def name_get(self):
|
||||
return [(pricelist.id, '%s (%s)' % (pricelist.name, pricelist.currency_id.name)) for pricelist in self]
|
||||
|
||||
@api.model
|
||||
def name_search(self, name, args=None, operator='ilike', limit=100):
|
||||
if name and operator == '=' and not args:
|
||||
# search on the name of the pricelist and its currency, opposite of name_get(),
|
||||
# Used by the magic context filter in the product search view.
|
||||
query_args = {'name': name, 'limit': limit, 'lang': (context or {}).get('lang') or 'en_US'}
|
||||
query_args = {'name': name, 'limit': limit, 'lang': self._context.get('lang', 'en_US')}
|
||||
query = """SELECT p.id
|
||||
FROM ((
|
||||
SELECT pr.id, pr.name
|
||||
@@ -70,49 +68,31 @@ class product_pricelist(osv.osv):
|
||||
ORDER BY p.name"""
|
||||
if limit:
|
||||
query += " LIMIT %(limit)s"
|
||||
cr.execute(query, query_args)
|
||||
ids = [r[0] for r in cr.fetchall()]
|
||||
self._cr.execute(query, query_args)
|
||||
ids = [r[0] for r in self._cr.fetchall()]
|
||||
# regular search() to apply ACLs - may limit results below limit in some cases
|
||||
ids = self.search(cr, uid, [('id', 'in', ids)], limit=limit, context=context)
|
||||
if ids:
|
||||
return self.name_get(cr, uid, ids, context)
|
||||
return super(product_pricelist, self).name_search(
|
||||
cr, uid, name, args, operator=operator, context=context, limit=limit)
|
||||
pricelists = self.search([('id', 'in', ids)], limit=limit)
|
||||
if pricelists:
|
||||
return pricelists.name_get()
|
||||
return super(Pricelist, self).name_search(name, args, operator=operator, limit=limit)
|
||||
|
||||
def _get_currency(self, cr, uid, context=None):
|
||||
comp = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
|
||||
if not comp:
|
||||
comp_id = self.pool.get('res.company').search(cr, uid, [], context=context)[0]
|
||||
comp = self.pool.get('res.company').browse(cr, uid, comp_id, context=context)
|
||||
return comp.currency_id.id
|
||||
|
||||
def _get_item_ids(self, cr, uid, context=None):
|
||||
ProductPricelistItem = self.pool.get('product.pricelist.item')
|
||||
fields_list = ProductPricelistItem._defaults.keys()
|
||||
vals = ProductPricelistItem.default_get(cr, uid, fields_list, context=context)
|
||||
vals['compute_price'] = 'formula'
|
||||
return [[0, False, vals]]
|
||||
|
||||
_defaults = {
|
||||
'active': lambda *a: 1,
|
||||
"currency_id": _get_currency,
|
||||
'item_ids': _get_item_ids,
|
||||
}
|
||||
|
||||
def _compute_price_rule_multi(self, cr, uid, ids, products_qty_partner, date=False, uom_id=False, context=None):
|
||||
def _compute_price_rule_multi(self, products_qty_partner, date=False, uom_id=False):
|
||||
""" Low-level method - Multi pricelist, multi products
|
||||
Returns: dict{product_id: dict{pricelist_id: (price, suitable_rule)} }"""
|
||||
if not ids:
|
||||
ids = self.pool.get('product.pricelist').search(cr, uid, [], context=context)
|
||||
if not self.ids:
|
||||
pricelists = self.search([])
|
||||
else:
|
||||
pricelists = self
|
||||
results = {}
|
||||
for pricelist in self.browse(cr, uid, ids, context=context):
|
||||
subres = self._compute_price_rule(cr, uid, [pricelist.id], products_qty_partner, date=date, uom_id=uom_id, context=context)
|
||||
for pricelist in pricelists:
|
||||
subres = pricelist._compute_price_rule(products_qty_partner, date=date, uom_id=uom_id)
|
||||
for product_id, price in subres.items():
|
||||
results.setdefault(product_id, {})
|
||||
results[product_id][pricelist.id] = price
|
||||
return results
|
||||
|
||||
def _compute_price_rule(self, cr, uid, ids, products_qty_partner, date=False, uom_id=False, context=None):
|
||||
@api.multi
|
||||
def _compute_price_rule(self, products_qty_partner, date=False, uom_id=False):
|
||||
""" Low-level method - Mono pricelist, multi products
|
||||
Returns: dict{product_id: (price, suitable_rule) for the given pricelist}
|
||||
|
||||
@@ -122,23 +102,19 @@ class product_pricelist(osv.osv):
|
||||
:param datetime date: validity date
|
||||
:param ID uom_id: intermediate unit of measure
|
||||
"""
|
||||
context = context if context is not None else {}
|
||||
self.ensure_one()
|
||||
if not date:
|
||||
date = context.get('date', time.strftime(DEFAULT_SERVER_DATE_FORMAT))
|
||||
if not uom_id and context.get('uom'):
|
||||
uom_id = context['uom']
|
||||
date = self._context.get('date', fields.Date.today())
|
||||
if not uom_id and self._context.get('uom'):
|
||||
uom_id = self._context['uom']
|
||||
if uom_id:
|
||||
# rebrowse with uom if given
|
||||
product_ids = [item[0].id for item in products_qty_partner]
|
||||
products = self.pool['product.product'].browse(cr, uid, product_ids, context=dict(context, uom=uom_id))
|
||||
products = self.env['product.product'].with_context(uom=uom_id).browse(product_ids)
|
||||
products_qty_partner = [(products[index], data_struct[1], data_struct[2]) for index, data_struct in enumerate(products_qty_partner)]
|
||||
else:
|
||||
products = [item[0] for item in products_qty_partner]
|
||||
|
||||
pricelist = self.browse(cr, uid, ids[0], context=context)
|
||||
context = context if context is not None else {}
|
||||
product_uom_obj = self.pool.get('product.uom')
|
||||
|
||||
if not products:
|
||||
return {}
|
||||
|
||||
@@ -161,7 +137,7 @@ class product_pricelist(osv.osv):
|
||||
prod_tmpl_ids = [product.product_tmpl_id.id for product in products]
|
||||
|
||||
# Load all rules
|
||||
cr.execute(
|
||||
self._cr.execute(
|
||||
'SELECT item.id '
|
||||
'FROM product_pricelist_item AS item '
|
||||
'LEFT JOIN product_category AS categ '
|
||||
@@ -173,10 +149,10 @@ class product_pricelist(osv.osv):
|
||||
'AND (item.date_start IS NULL OR item.date_start<=%s) '
|
||||
'AND (item.date_end IS NULL OR item.date_end>=%s)'
|
||||
'ORDER BY item.applied_on, item.min_quantity desc, categ.parent_left desc',
|
||||
(prod_tmpl_ids, prod_ids, categ_ids, pricelist.id, date, date))
|
||||
(prod_tmpl_ids, prod_ids, categ_ids, self.id, date, date))
|
||||
|
||||
item_ids = [x[0] for x in cr.fetchall()]
|
||||
items = self.pool.get('product.pricelist.item').browse(cr, uid, item_ids, context=context)
|
||||
item_ids = [x[0] for x in self._cr.fetchall()]
|
||||
items = self.env['product.pricelist.item'].browse(item_ids)
|
||||
results = {}
|
||||
for product, qty, partner in products_qty_partner:
|
||||
results[product.id] = 0.0
|
||||
@@ -186,13 +162,12 @@ class product_pricelist(osv.osv):
|
||||
# An intermediary unit price may be computed according to a different UoM, in
|
||||
# which case the price_uom_id contains that UoM.
|
||||
# The final price will be converted to match `qty_uom_id`.
|
||||
qty_uom_id = context.get('uom') or product.uom_id.id
|
||||
qty_uom_id = self._context.get('uom') or product.uom_id.id
|
||||
price_uom_id = product.uom_id.id
|
||||
qty_in_product_uom = qty
|
||||
if qty_uom_id != product.uom_id.id:
|
||||
try:
|
||||
qty_in_product_uom = product_uom_obj._compute_quantity(
|
||||
cr, uid, [context['uom']], qty, product.uom_id)
|
||||
qty_in_product_uom = self.env['product.uom'].browse([self._context['uom']])._compute_quantity(qty, product.uom_id)
|
||||
except UserError:
|
||||
# Ignored - incompatible UoM in context, use default product UoM
|
||||
pass
|
||||
@@ -201,7 +176,7 @@ class product_pricelist(osv.osv):
|
||||
# TDE SURPRISE: product can actually be a template
|
||||
price = product.price_compute('list_price')[product.id]
|
||||
|
||||
price_uom = self.pool['product.uom'].browse(cr, uid, [qty_uom_id], context=context)
|
||||
price_uom = self.env['product.uom'].browse([qty_uom_id])
|
||||
for rule in items:
|
||||
if rule.min_quantity and qty_in_product_uom < rule.min_quantity:
|
||||
continue
|
||||
@@ -227,17 +202,14 @@ class product_pricelist(osv.osv):
|
||||
continue
|
||||
|
||||
if rule.base == 'pricelist' and rule.base_pricelist_id:
|
||||
price_tmp = self._compute_price_rule(cr, uid, [rule.base_pricelist_id.id], [(product, qty, partner)], context=context)[product.id][0] # TDE: 0 = price, 1 = rule
|
||||
ptype_src = rule.base_pricelist_id.currency_id.id
|
||||
price = self.pool['res.currency'].compute(cr, uid, ptype_src, pricelist.currency_id.id, price_tmp, round=False, context=context)
|
||||
price_tmp = rule.base_pricelist_id._compute_price_rule([(product, qty, partner)])[product.id][0] # TDE: 0 = price, 1 = rule
|
||||
price = rule.base_pricelist_id.currency_id.compute(price_tmp, self.currency_id, round=False)
|
||||
else:
|
||||
# if base option is public price take sale price else cost price of product
|
||||
# price_compute returns the price in the context UoM, i.e. qty_uom_id
|
||||
price = product.price_compute(rule.base)[product.id]
|
||||
|
||||
convert_to_price_uom = (lambda price: product_uom_obj._compute_price(
|
||||
cr, uid, [product.uom_id.id],
|
||||
price, price_uom))
|
||||
convert_to_price_uom = (lambda price: product.uom_id._compute_price(price, price_uom))
|
||||
|
||||
if price is not False:
|
||||
if rule.compute_price == 'fixed':
|
||||
@@ -245,7 +217,7 @@ class product_pricelist(osv.osv):
|
||||
elif rule.compute_price == 'percentage':
|
||||
price = (price - (price * (rule.percent_price / 100))) or 0.0
|
||||
else:
|
||||
#complete formula
|
||||
# complete formula
|
||||
price_limit = price
|
||||
price = (price - (price * (rule.price_discount / 100))) or 0.0
|
||||
if rule.price_round:
|
||||
@@ -266,140 +238,146 @@ class product_pricelist(osv.osv):
|
||||
break
|
||||
# Final price conversion into pricelist currency
|
||||
if suitable_rule and suitable_rule.compute_price != 'fixed' and suitable_rule.base != 'pricelist':
|
||||
price = self.pool['res.currency'].compute(cr, uid, product.currency_id.id, pricelist.currency_id.id, price, round=False, context=context)
|
||||
price = product.currency_id.compute(price, self.currency_id, round=False)
|
||||
|
||||
results[product.id] = (price, suitable_rule and suitable_rule.id or False)
|
||||
|
||||
return results
|
||||
|
||||
# New methods: product based
|
||||
def get_products_price(self, cr, uid, ids, products, quantities, partners, date=False, uom_id=False, context=None):
|
||||
def get_products_price(self, products, quantities, partners, date=False, uom_id=False):
|
||||
""" For a given pricelist, return price for products
|
||||
Returns: dict{product_id: product price}, in the given pricelist """
|
||||
pricelist = self.browse(cr, uid, ids[0], context=context)
|
||||
return dict((product_id, res_tuple[0]) for product_id, res_tuple in pricelist._compute_price_rule(zip(products, quantities, partners), date=date, uom_id=uom_id).iteritems())
|
||||
self.ensure_one()
|
||||
return dict((product_id, res_tuple[0]) for product_id, res_tuple in self._compute_price_rule(zip(products, quantities, partners), date=date, uom_id=uom_id).iteritems())
|
||||
|
||||
def get_product_price(self, cr, uid, ids, product, quantity, partner, date=False, uom_id=False, context=None):
|
||||
def get_product_price(self, product, quantity, partner, date=False, uom_id=False):
|
||||
""" For a given pricelist, return price for a given product """
|
||||
pricelist = self.browse(cr, uid, ids[0], context=context)
|
||||
return pricelist._compute_price_rule([(product, quantity, partner)], date=date, uom_id=uom_id)[product.id][0]
|
||||
self.ensure_one()
|
||||
return self._compute_price_rule([(product, quantity, partner)], date=date, uom_id=uom_id)[product.id][0]
|
||||
|
||||
def get_product_price_rule(self, cr, uid, ids, product, quantity, partner, date=False, uom_id=False, context=None):
|
||||
def get_product_price_rule(self, product, quantity, partner, date=False, uom_id=False):
|
||||
""" For a given pricelist, return price and rule for a given product """
|
||||
pricelist = self.browse(cr, uid, ids[0], context=context)
|
||||
return pricelist._compute_price_rule([(product, quantity, partner)], date=date, uom_id=uom_id)[product.id]
|
||||
self.ensure_one()
|
||||
return self._compute_price_rule([(product, quantity, partner)], date=date, uom_id=uom_id)[product.id]
|
||||
|
||||
# Compatibility to remove after v10 - DEPRECATED
|
||||
def _price_rule_get_multi(self, cr, uid, pricelist, products_by_qty_by_partner, context=None):
|
||||
@api.model
|
||||
def _price_rule_get_multi(self, pricelist, products_by_qty_by_partner):
|
||||
""" Low level method computing the result tuple for a given pricelist and multi products - return tuple """
|
||||
return self._compute_price_rule(cr, uid, [pricelist.id], products_by_qty_by_partner, context=context)
|
||||
return pricelist._compute_price_rule(products_by_qty_by_partner)
|
||||
|
||||
def price_get(self, cr, uid, ids, prod_id, qty, partner=None, context=None):
|
||||
@api.multi
|
||||
def price_get(self, prod_id, qty, partner=None):
|
||||
""" Multi pricelist, mono product - returns price per pricelist """
|
||||
return dict((key, price[0]) for key, price in self.price_rule_get(cr, uid, ids, prod_id, qty, partner=partner, context=context).items())
|
||||
return dict((key, price[0]) for key, price in self.price_rule_get(prod_id, qty, partner=partner).items())
|
||||
|
||||
def price_rule_get_multi(self, cr, uid, ids, products_by_qty_by_partner, context=None):
|
||||
@api.multi
|
||||
def price_rule_get_multi(self, products_by_qty_by_partner):
|
||||
""" Multi pricelist, multi product - return tuple """
|
||||
return self._compute_price_rule_multi(cr, uid, ids, products_by_qty_by_partner, context=context)
|
||||
return self._compute_price_rule_multi(products_by_qty_by_partner)
|
||||
|
||||
def price_rule_get(self, cr, uid, ids, prod_id, qty, partner=None, context=None):
|
||||
@api.multi
|
||||
def price_rule_get(self, prod_id, qty, partner=None):
|
||||
""" Multi pricelist, mono product - return tuple """
|
||||
product = self.pool.get('product.product').browse(cr, uid, prod_id, context=context)
|
||||
return self._compute_price_rule_multi(cr, uid, ids, [(product, qty, partner)], context=context)[prod_id]
|
||||
product = self.env['product.product'].browse([prod_id])
|
||||
return self._compute_price_rule_multi([(product, qty, partner)])[prod_id]
|
||||
|
||||
def _price_get_multi(self, cr, uid, pricelist, products_by_qty_by_partner, context=None):
|
||||
@api.model
|
||||
def _price_get_multi(self, pricelist, products_by_qty_by_partner):
|
||||
""" Mono pricelist, multi product - return price per product """
|
||||
return self.get_products_price(cr, uid, [pricelist.id], zip(**products_by_qty_by_partner), context=context)
|
||||
return pricelist.get_products_price(zip(**products_by_qty_by_partner))
|
||||
|
||||
|
||||
class product_pricelist_item(osv.osv):
|
||||
class PricelistItem(models.Model):
|
||||
_name = "product.pricelist.item"
|
||||
_description = "Pricelist item"
|
||||
_order = "applied_on, min_quantity desc, categ_id desc"
|
||||
|
||||
def _check_recursion(self, cr, uid, ids, context=None):
|
||||
for obj_list in self.browse(cr, uid, ids, context=context):
|
||||
if obj_list.base == 'pricelist':
|
||||
main_pricelist = obj_list.pricelist_id.id
|
||||
other_pricelist = obj_list.base_pricelist_id.id
|
||||
if main_pricelist == other_pricelist:
|
||||
return False
|
||||
product_tmpl_id = fields.Many2one(
|
||||
'product.template', 'Product Template', ondelete='cascade',
|
||||
help="Specify a template if this rule only applies to one product template. Keep empty otherwise.")
|
||||
product_id = fields.Many2one(
|
||||
'product.product', 'Product', ondelete='cascade',
|
||||
help="Specify a product if this rule only applies to one product. Keep empty otherwise.")
|
||||
categ_id = fields.Many2one(
|
||||
'product.category', 'Product Category', ondelete='cascade',
|
||||
help="Specify a product category if this rule only applies to products belonging to this category or its children categories. Keep empty otherwise.")
|
||||
min_quantity = fields.Integer(
|
||||
'Min. Quantity', default=1,
|
||||
help="For the rule to apply, bought/sold quantity must be greater "
|
||||
"than or equal to the minimum quantity specified in this field.\n"
|
||||
"Expressed in the default unit of measure of the product.")
|
||||
applied_on = fields.Selection([
|
||||
('3_global', 'Global'),
|
||||
('2_product_category', ' Product Category'),
|
||||
('1_product', 'Product'),
|
||||
('0_product_variant', 'Product Variant')], "Apply On",
|
||||
default='3_global', required=True,
|
||||
help='Pricelist Item applicable on selected option')
|
||||
sequence = fields.Integer(
|
||||
'Sequence', default=5, required=True,
|
||||
help="Gives the order in which the pricelist items will be checked. The evaluation gives highest priority to lowest sequence and stops as soon as a matching item is found.")
|
||||
base = fields.Selection([
|
||||
('list_price', 'Public Price'),
|
||||
('standard_price', 'Cost'),
|
||||
('pricelist', 'Other Pricelist')], "Based on",
|
||||
default='list_price', required=True,
|
||||
help='Base price for computation.\n'
|
||||
'Public Price: The base price will be the Sale/public Price.\n'
|
||||
'Cost Price : The base price will be the cost price.\n'
|
||||
'Other Pricelist : Computation of the base price based on another Pricelist.')
|
||||
base_pricelist_id = fields.Many2one('product.pricelist', 'Other Pricelist')
|
||||
pricelist_id = fields.Many2one('product.pricelist', 'Pricelist', index=True, ondelete='cascade')
|
||||
price_surcharge = fields.Float(
|
||||
'Price Surcharge', digits_compute=dp.get_precision('Product Price'),
|
||||
help='Specify the fixed amount to add or substract(if negative) to the amount calculated with the discount.')
|
||||
price_discount = fields.Float('Price Discount', default=0, digits=(16, 2))
|
||||
price_round = fields.Float(
|
||||
'Price Rounding', digits_compute=dp.get_precision('Product Price'),
|
||||
help="Sets the price so that it is a multiple of this value.\n"
|
||||
"Rounding is applied after the discount and before the surcharge.\n"
|
||||
"To have prices that end in 9.99, set rounding 10, surcharge -0.01")
|
||||
price_min_margin = fields.Float(
|
||||
'Min. Price Margin', digits_compute=dp.get_precision('Product Price'),
|
||||
help='Specify the minimum amount of margin over the base price.')
|
||||
price_max_margin = fields.Float(
|
||||
'Max. Price Margin', digits_compute=dp.get_precision('Product Price'),
|
||||
help='Specify the maximum amount of margin over the base price.')
|
||||
company_id = fields.Many2one(
|
||||
'res.company', 'Company',
|
||||
readonly=True, related='pricelist_id.company_id', store=True)
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', 'Currency',
|
||||
readonly=True, related='pricelist_id.currency_id', store=True)
|
||||
date_start = fields.Date('Start Date', help="Starting date for the pricelist item validation")
|
||||
date_end = fields.Date('End Date', help="Ending valid for the pricelist item validation")
|
||||
compute_price = fields.Selection([
|
||||
('fixed', 'Fix Price'),
|
||||
('percentage', 'Percentage (discount)'),
|
||||
('formula', 'Formula')], index=True, default='fixed')
|
||||
fixed_price = fields.Float('Fixed Price', digits_compute=dp.get_precision('Product Price'))
|
||||
percent_price = fields.Float('Percentage Price')
|
||||
# functional fields used for usability purposes
|
||||
name = fields.Char(
|
||||
'Name', compute='_get_pricelist_item_name_price',
|
||||
help="Explicit rule name for this pricelist line.")
|
||||
price = fields.Char(
|
||||
'Price', compute='_get_pricelist_item_name_price',
|
||||
help="Explicit rule name for this pricelist line.")
|
||||
|
||||
@api.constrains('base_pricelist_id', 'pricelist_id', 'base')
|
||||
def _check_recursion(self):
|
||||
if any(item.base == 'pricelist' and item.pricelist_id and item.pricelist_id == item.base_pricelist_id for item in self):
|
||||
raise ValidationError(_('Error! You cannot assign the Main Pricelist as Other Pricelist in PriceList Item!'))
|
||||
return True
|
||||
|
||||
@api.constrains('price_min_margin', 'price_max_margin')
|
||||
def _check_margin(self, cr, uid, ids, context=None):
|
||||
for item in self.browse(cr, uid, ids, context=context):
|
||||
if item.price_max_margin and item.price_min_margin and (item.price_min_margin > item.price_max_margin):
|
||||
return False
|
||||
if any(item.price_min_margin > item.price_max_margin for item in self):
|
||||
raise ValidationError(_('Error! The minimum margin should be lower than the maximum margin.'))
|
||||
return True
|
||||
|
||||
_columns = {
|
||||
'product_tmpl_id': fields.many2one('product.template', 'Product Template', ondelete='cascade', help="Specify a template if this rule only applies to one product template. Keep empty otherwise."),
|
||||
'product_id': fields.many2one('product.product', 'Product', ondelete='cascade', help="Specify a product if this rule only applies to one product. Keep empty otherwise."),
|
||||
'categ_id': fields.many2one('product.category', 'Product Category', ondelete='cascade', help="Specify a product category if this rule only applies to products belonging to this category or its children categories. Keep empty otherwise."),
|
||||
'min_quantity': fields.integer('Min. Quantity',
|
||||
help="For the rule to apply, bought/sold quantity must be greater "
|
||||
"than or equal to the minimum quantity specified in this field.\n"
|
||||
"Expressed in the default unit of measure of the product."
|
||||
),
|
||||
'applied_on': fields.selection([('3_global', 'Global'),('2_product_category', ' Product Category'), ('1_product', 'Product'), ('0_product_variant', 'Product Variant')], string="Apply On", required=True,
|
||||
help='Pricelist Item applicable on selected option'),
|
||||
'sequence': fields.integer('Sequence', required=True, help="Gives the order in which the pricelist items will be checked. The evaluation gives highest priority to lowest sequence and stops as soon as a matching item is found."),
|
||||
'base': fields.selection([('list_price', 'Public Price'), ('standard_price', 'Cost'), ('pricelist', 'Other Pricelist')], string="Based on", required=True,
|
||||
help='Base price for computation. \n Public Price: The base price will be the Sale/public Price. \n Cost Price : The base price will be the cost price. \n Other Pricelist : Computation of the base price based on another Pricelist.'),
|
||||
'base_pricelist_id': fields.many2one('product.pricelist', 'Other Pricelist'),
|
||||
'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', ondelete='cascade', select=True),
|
||||
'price_surcharge': fields.float('Price Surcharge',
|
||||
digits_compute= dp.get_precision('Product Price'), help='Specify the fixed amount to add or substract(if negative) to the amount calculated with the discount.'),
|
||||
'price_discount': fields.float('Price Discount', digits=(16,2)),
|
||||
'price_round': fields.float('Price Rounding',
|
||||
digits_compute= dp.get_precision('Product Price'),
|
||||
help="Sets the price so that it is a multiple of this value.\n" \
|
||||
"Rounding is applied after the discount and before the surcharge.\n" \
|
||||
"To have prices that end in 9.99, set rounding 10, surcharge -0.01" \
|
||||
),
|
||||
'price_min_margin': fields.float('Min. Price Margin',
|
||||
digits_compute= dp.get_precision('Product Price'), help='Specify the minimum amount of margin over the base price.'),
|
||||
'price_max_margin': fields.float('Max. Price Margin',
|
||||
digits_compute= dp.get_precision('Product Price'), help='Specify the maximum amount of margin over the base price.'),
|
||||
'company_id': fields.related('pricelist_id','company_id',type='many2one',
|
||||
readonly=True, relation='res.company', string='Company', store=True),
|
||||
'currency_id': fields.related('pricelist_id', 'currency_id', type='many2one',
|
||||
readonly=True, relation='res.currency', string='Currency', store=True),
|
||||
'date_start': fields.date('Start Date', help="Starting date for the pricelist item validation"),
|
||||
'date_end': fields.date('End Date', help="Ending valid for the pricelist item validation"),
|
||||
'compute_price': fields.selection([('fixed', 'Fix Price'), ('percentage', 'Percentage (discount)'), ('formula', 'Formula')], select=True, default='fixed'),
|
||||
'fixed_price': fields.float('Fixed Price', digits_compute=dp.get_precision('Product Price')),
|
||||
'percent_price': fields.float('Percentage Price'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'base': 'list_price',
|
||||
'min_quantity': 1,
|
||||
'sequence': 5,
|
||||
'price_discount': 0,
|
||||
'applied_on': '3_global',
|
||||
}
|
||||
_constraints = [
|
||||
(_check_recursion, 'Error! You cannot assign the Main Pricelist as Other Pricelist in PriceList Item!', ['base_pricelist_id']),
|
||||
(_check_margin, 'Error! The minimum margin should be lower than the maximum margin.', ['price_min_margin', 'price_max_margin'])
|
||||
]
|
||||
|
||||
|
||||
class product_pricelist_item_new(models.Model):
|
||||
_inherit = "product.pricelist.item"
|
||||
|
||||
_applied_on_field_map = {
|
||||
'0_product_variant': 'product_id',
|
||||
'1_product': 'product_tmpl_id',
|
||||
'2_product_category': 'categ_id',
|
||||
}
|
||||
|
||||
_compute_price_field_map = {
|
||||
'fixed': ['fixed_price'],
|
||||
'percentage': ['percent_price'],
|
||||
'formula': ['price_discount', 'price_surcharge', 'price_round', 'price_min_margin', 'price_max_margin'],
|
||||
}
|
||||
|
||||
@api.one
|
||||
@api.depends('categ_id', 'product_tmpl_id', 'product_id', 'compute_price', 'fixed_price', \
|
||||
'pricelist_id', 'percent_price', 'price_discount', 'price_surcharge')
|
||||
@@ -420,19 +398,26 @@ class product_pricelist_item_new(models.Model):
|
||||
else:
|
||||
self.price = _("%s %% discount and %s surcharge") % (abs(self.price_discount), self.price_surcharge)
|
||||
|
||||
#functional fields used for usability purposes
|
||||
name = Fields.Char(compute='_get_pricelist_item_name_price', string='Name', multi='item_name_price', help="Explicit rule name for this pricelist line.")
|
||||
price = Fields.Char(compute='_get_pricelist_item_name_price', string='Price', multi='item_name_price', help="Explicit rule name for this pricelist line.")
|
||||
|
||||
@api.onchange('applied_on')
|
||||
def _onchange_applied_on(self):
|
||||
for applied_on, field in self._applied_on_field_map.iteritems():
|
||||
if self.applied_on != applied_on:
|
||||
setattr(self, field, False)
|
||||
if self.applied_on != '0_product_variant':
|
||||
self.product_id = False
|
||||
if self.applied_on != '1_product':
|
||||
self.product_tmpl_id = False
|
||||
if self.applied_on != '2_product_category':
|
||||
self.categ_id = False
|
||||
|
||||
@api.onchange('compute_price')
|
||||
def _onchange_compute_price(self):
|
||||
for compute_price, field in self._compute_price_field_map.iteritems():
|
||||
if self.compute_price != compute_price:
|
||||
for f in field:
|
||||
setattr(self, f, 0.0)
|
||||
if self.compute_price != 'fixed':
|
||||
self.fixed_price = 0.0
|
||||
if self.compute_price != 'percentage':
|
||||
self.percent_price = 0.0
|
||||
if self.compute_price != 'formula':
|
||||
self.update({
|
||||
'price_discount': 0.0,
|
||||
'price_surcharge': 0.0,
|
||||
'price_round': 0.0,
|
||||
'price_min_margin': 0.0,
|
||||
'price_max_margin': 0.0,
|
||||
})
|
||||
|
||||
@@ -1,190 +1,309 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import re
|
||||
import time
|
||||
|
||||
import openerp
|
||||
from openerp import api, tools, SUPERUSER_ID
|
||||
from openerp.osv import osv, fields, expression
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FORMAT
|
||||
import psycopg2
|
||||
|
||||
import openerp.addons.decimal_precision as dp
|
||||
from openerp.tools.float_utils import float_round, float_compare
|
||||
from openerp.exceptions import UserError
|
||||
from openerp.exceptions import except_orm
|
||||
import odoo.addons.decimal_precision as dp
|
||||
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import ValidationError, except_orm
|
||||
|
||||
|
||||
class product_template(osv.osv):
|
||||
class ProductTemplate(models.Model):
|
||||
_name = "product.template"
|
||||
_inherit = ['mail.thread']
|
||||
_description = "Product Template"
|
||||
_order = "name"
|
||||
|
||||
def _product_template_price(self, cr, uid, ids, name, arg, context=None):
|
||||
plobj = self.pool.get('product.pricelist')
|
||||
res = {}
|
||||
quantity = context.get('quantity') or 1.0
|
||||
pricelist = context.get('pricelist', False)
|
||||
partner = context.get('partner', False)
|
||||
if pricelist:
|
||||
# Support context pricelists specified as display_name or ID for compatibility
|
||||
if isinstance(pricelist, basestring):
|
||||
pricelist_ids = plobj.name_search(
|
||||
cr, uid, pricelist, operator='=', context=context, limit=1)
|
||||
pricelist = pricelist_ids[0][0] if pricelist_ids else pricelist
|
||||
def _get_default_category_id(self):
|
||||
if self._context.get('categ_id') or self._context.get('default_categ_id'):
|
||||
return self._context.get('categ_id') or self._context.get('default_categ_id')
|
||||
category = self.env.ref('product.product_category_all', raise_if_not_found=False)
|
||||
return category and category.id or False
|
||||
|
||||
if isinstance(pricelist, (int, long)):
|
||||
products = self.browse(cr, uid, ids, context=context)
|
||||
quantities = [quantity] * len(products)
|
||||
partners = [partner] * len(products)
|
||||
pl = plobj.browse(cr, uid, pricelist, context=context)
|
||||
price = plobj.get_products_price(cr, uid, [pl.id], products, quantities, partners, context=context)
|
||||
for id in ids:
|
||||
res[id] = price.get(id, 0.0)
|
||||
for id in ids:
|
||||
res.setdefault(id, 0.0)
|
||||
return res
|
||||
def _get_default_uom_id(self):
|
||||
return self.env["product.uom"].search([], limit=1, order='id').id
|
||||
|
||||
def _set_product_template_price(self, cr, uid, id, name, value, args, context=None):
|
||||
product_uom_obj = self.pool.get('product.uom')
|
||||
name = fields.Char('Name', index=True, required=True, translate=True)
|
||||
sequence = fields.Integer('Sequence', default=1, help='Gives the sequence order when displaying a product list')
|
||||
product_manager = fields.Many2one('res.users', 'Product Manager')
|
||||
description = fields.Text(
|
||||
'Description', translate=True,
|
||||
help="A precise description of the Product, used only for internal information purposes.")
|
||||
description_purchase = fields.Text(
|
||||
'Purchase Description', translate=True,
|
||||
help="A description of the Product that you want to communicate to your vendors. "
|
||||
"This description will be copied to every Purchase Order, Receipt and Vendor Bill/Refund.")
|
||||
description_sale = fields.Text(
|
||||
'Sale Description', translate=True,
|
||||
help="A description of the Product that you want to communicate to your customers. "
|
||||
"This description will be copied to every Sale Order, Delivery Order and Customer Invoice/Refund")
|
||||
type = fields.Selection([
|
||||
('consu', _('Consumable')),
|
||||
('service', _('Service'))], string='Product Type', default='consu', required=True,
|
||||
help='A stockable product is a product for which you manage stock. The "Inventory" app has to be installed.\n'
|
||||
'A consumable product, on the other hand, is a product for which stock is not managed.\n'
|
||||
'A service is a non-material product you provide.\n'
|
||||
'A digital content is a non-material product you sell online. The files attached to the products are the one that are sold on '
|
||||
'the e-commerce such as e-books, music, pictures,... The "Digital Product" module has to be installed.')
|
||||
rental = fields.Boolean('Can be Rent')
|
||||
categ_id = fields.Many2one(
|
||||
'product.category', 'Internal Category',
|
||||
change_default=True, default=_get_default_category_id, domain="[('type','=','normal')]",
|
||||
required=True, help="Select category for the current product")
|
||||
|
||||
product = self.browse(cr, uid, id, context=context)
|
||||
if 'uom' in context:
|
||||
uom = product.uom_id
|
||||
value = product_uom_obj._compute_price(cr, uid,
|
||||
[context['uom']], value, uom, context=context)
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', 'Currency', compute='_compute_currency_id')
|
||||
|
||||
return product.write({'list_price': value})
|
||||
# price fields
|
||||
price = fields.Float(
|
||||
'Price', compute='_compute_template_price', inverse='_set_template_price',
|
||||
digits_compute=dp.get_precision('Product Price'))
|
||||
list_price = fields.Float(
|
||||
'Sale Price', default=1.0,
|
||||
digits_compute=dp.get_precision('Product Price'),
|
||||
help="Base price to compute the customer price. Sometimes called the catalog price.")
|
||||
lst_price = fields.Float(
|
||||
'Public Price', related='list_price',
|
||||
digits_compute=dp.get_precision('Product Price'))
|
||||
standard_price = fields.Float(
|
||||
'Cost', compute='_compute_standard_price',
|
||||
inverse='_set_standard_price', search='_search_standard_price',
|
||||
digits_compute=dp.get_precision('Product Price'), groups="base.group_user",
|
||||
help="Cost of the product, in the default unit of measure of the product.")
|
||||
|
||||
def _product_currency(self, cr, uid, ids, name, arg, context=None):
|
||||
uid = SUPERUSER_ID
|
||||
try:
|
||||
main_company = self.pool['ir.model.data'].get_object(cr, uid, 'base', 'main_company')
|
||||
except ValueError:
|
||||
company_ids = self.pool['res.company'].search(cr, uid, [], limit=1, order="id", context=context)
|
||||
main_company = self.pool['res.company'].browse(cr, uid, company_ids[0], context=context)
|
||||
res = {}
|
||||
for product in self.browse(cr, uid, ids, context=context):
|
||||
res[product.id] = product.company_id.currency_id.id or main_company.currency_id.id
|
||||
return res
|
||||
volume = fields.Float(
|
||||
'Volume', compute='_compute_volume', inverse='_set_volume',
|
||||
help="The volume in m3.", store=True)
|
||||
weight = fields.Float(
|
||||
'Weight', compute='_compute_weight', digits_compute=dp.get_precision('Stock Weight'),
|
||||
inverse='_set_weight', store=True,
|
||||
help="The weight of the contents in Kg, not including any packaging, etc.")
|
||||
|
||||
def _get_product_variant_count(self, cr, uid, ids, name, arg, context=None):
|
||||
res = {}
|
||||
for product in self.browse(cr, uid, ids, context=context):
|
||||
res[product.id] = len(product.product_variant_ids)
|
||||
return res
|
||||
warranty = fields.Float('Warranty')
|
||||
sale_ok = fields.Boolean(
|
||||
'Can be Sold', default=True,
|
||||
help="Specify if the product can be selected in a sales order line.")
|
||||
pricelist_id = fields.Many2one(
|
||||
'product.pricelist', 'Pricelist', store=False,
|
||||
help='Technical field. Used for searching on pricelists, not stored in database.')
|
||||
uom_id = fields.Many2one(
|
||||
'product.uom', 'Unit of Measure',
|
||||
default=_get_default_uom_id, required=True,
|
||||
help="Default Unit of Measure used for all stock operation.")
|
||||
uom_po_id = fields.Many2one(
|
||||
'product.uom', 'Purchase Unit of Measure',
|
||||
default=_get_default_uom_id, required=True,
|
||||
help="Default Unit of Measure used for purchase orders. It must be in the same category than the default unit of measure.")
|
||||
company_id = fields.Many2one(
|
||||
'res.company', 'Company',
|
||||
default=lambda self: self.env['res.company']._company_default_get('product.template'), index=1)
|
||||
packaging_ids = fields.One2many(
|
||||
'product.packaging', 'product_tmpl_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.")
|
||||
seller_ids = fields.One2many('product.supplierinfo', 'product_tmpl_id', 'Vendors')
|
||||
|
||||
def _compute_product_template_field(self, cr, uid, ids, names, arg, context=None):
|
||||
''' Compute the field from the product_variant if there is only one variant, otherwise returns 0.0 '''
|
||||
if isinstance(names, basestring):
|
||||
names = [names]
|
||||
res = {id: {} for id in ids}
|
||||
templates = self.browse(cr, uid, ids, context=context)
|
||||
unique_templates = [template.id for template in templates if template.product_variant_count == 1]
|
||||
for template in templates:
|
||||
for name in names:
|
||||
res[template.id][name] = getattr(template.product_variant_ids[0], name) if template.id in unique_templates else 0.0
|
||||
return res
|
||||
active = fields.Boolean('Active', default=True, help="If unchecked, it will allow you to hide the product without removing it.")
|
||||
color = fields.Integer('Color Index')
|
||||
|
||||
def _set_product_template_field(self, cr, uid, product_tmpl_id, name, value, args, context=None):
|
||||
''' Set the standard price modification on the variant if there is only one variant '''
|
||||
template = self.pool['product.template'].browse(cr, uid, product_tmpl_id, context=context)
|
||||
if template.product_variant_count == 1:
|
||||
variant = self.pool['product.product'].browse(cr, uid, template.product_variant_ids.id, context=context)
|
||||
return variant.write({name: value})
|
||||
return {}
|
||||
attribute_line_ids = fields.One2many('product.attribute.line', 'product_tmpl_id', 'Product Attributes')
|
||||
product_variant_ids = fields.One2many('product.product', 'product_tmpl_id', 'Products', required=True)
|
||||
product_variant_count = fields.Integer(
|
||||
'# Product Variants', compute='_compute_product_variant_count')
|
||||
|
||||
def _search_by_standard_price(self, cr, uid, obj, name, domain, context=None):
|
||||
r = self.pool['product.product'].search_read(cr, uid, domain, ['product_tmpl_id'],
|
||||
limit=None, context=context)
|
||||
return [('id', 'in', [x['product_tmpl_id'][0] for x in r])]
|
||||
# related to display product product information if is_product_variant
|
||||
barcode = fields.Char('Barcode', oldname='ean13', related='product_variant_ids.barcode')
|
||||
default_code = fields.Char(
|
||||
'Internal Reference', compute='_compute_default_code',
|
||||
inverse='_set_default_code', store=True)
|
||||
|
||||
def _get_template_id_from_product(self, cr, uid, ids, context=None):
|
||||
r = self.pool['product.product'].read(cr, uid, ids, ['product_tmpl_id'], context=context)
|
||||
return [x['product_tmpl_id'][0] for x in r]
|
||||
|
||||
def _get_product_template_type(self, cr, uid, context=None):
|
||||
return [('consu', _('Consumable')), ('service', _('Service'))]
|
||||
_get_product_template_type_wrapper = lambda self, *args, **kwargs: self._get_product_template_type(*args, **kwargs)
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', required=True, translate=True, select=True),
|
||||
'sequence': fields.integer('Sequence', help='Gives the sequence order when displaying a product list'),
|
||||
'product_manager': fields.many2one('res.users','Product Manager'),
|
||||
'description': fields.text('Description',translate=True,
|
||||
help="A precise description of the Product, used only for internal information purposes."),
|
||||
'description_purchase': fields.text('Purchase Description',translate=True,
|
||||
help="A description of the Product that you want to communicate to your vendors. "
|
||||
"This description will be copied to every Purchase Order, Receipt and Vendor Bill/Refund."),
|
||||
'description_sale': fields.text('Sale Description',translate=True,
|
||||
help="A description of the Product that you want to communicate to your customers. "
|
||||
"This description will be copied to every Sale Order, Delivery Order and Customer Invoice/Refund"),
|
||||
'type': fields.selection(_get_product_template_type_wrapper, 'Product Type', required=True,
|
||||
help='A stockable product is a product for which you manage stock. The "Inventory" app has to be installed.\n'
|
||||
'A consumable product, on the other hand, is a product for which stock is not managed.\n'
|
||||
'A service is a non-material product you provide.\n'
|
||||
'A digital content is a non-material product you sell online. The files attached to the products are the one that are sold on '
|
||||
'the e-commerce such as e-books, music, pictures,... The "Digital Product" module has to be installed.'),
|
||||
'rental': fields.boolean('Can be Rent'),
|
||||
'categ_id': fields.many2one('product.category','Internal Category', required=True, change_default=True, domain="[('type','=','normal')]" ,help="Select category for the current product"),
|
||||
'price': fields.function(_product_template_price, fnct_inv=_set_product_template_price, type='float', string='Price', digits_compute=dp.get_precision('Product Price')),
|
||||
'currency_id': fields.function(_product_currency, type='many2one', relation='res.currency', string='Currency'),
|
||||
'list_price': fields.float('Sale Price', digits_compute=dp.get_precision('Product Price'), help="Base price to compute the customer price. Sometimes called the catalog price."),
|
||||
'lst_price' : fields.related('list_price', type="float", string='Public Price', digits_compute=dp.get_precision('Product Price')),
|
||||
'standard_price': fields.function(_compute_product_template_field, fnct_inv=_set_product_template_field, fnct_search=_search_by_standard_price, multi='_compute_product_template_field', type='float', string='Cost', digits_compute=dp.get_precision('Product Price'),
|
||||
help="Cost of the product, in the default unit of measure of the product.", groups="base.group_user"),
|
||||
'volume': fields.function(_compute_product_template_field, fnct_inv=_set_product_template_field, multi='_compute_product_template_field', type='float', string='Volume', help="The volume in m3.", store={
|
||||
_name: (lambda s,c,u,i,t: i, ['product_variant_ids'], 10),
|
||||
'product.product': (_get_template_id_from_product, ['product_tmpl_id', 'volume'], 10),
|
||||
}),
|
||||
'weight': fields.function(_compute_product_template_field, fnct_inv=_set_product_template_field, multi='_compute_product_template_field', type='float', string='Weight', digits_compute=dp.get_precision('Stock Weight'), help="The weight of the contents in Kg, not including any packaging, etc.", store={
|
||||
_name: (lambda s,c,u,i,t: i, ['product_variant_ids'], 10),
|
||||
'product.product': (_get_template_id_from_product, ['product_tmpl_id', 'weight'], 10),
|
||||
}),
|
||||
'warranty': fields.float('Warranty'),
|
||||
'sale_ok': fields.boolean('Can be Sold', help="Specify if the product can be selected in a sales order line."),
|
||||
'pricelist_id': fields.dummy(string='Pricelist', relation='product.pricelist', type='many2one'),
|
||||
'uom_id': fields.many2one('product.uom', 'Unit of Measure', required=True, help="Default Unit of Measure used for all stock operation."),
|
||||
'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."),
|
||||
'company_id': fields.many2one('res.company', 'Company', select=1),
|
||||
'packaging_ids': fields.one2many(
|
||||
'product.packaging', 'product_tmpl_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."),
|
||||
'seller_ids': fields.one2many('product.supplierinfo', 'product_tmpl_id', 'Vendors'),
|
||||
|
||||
'active': fields.boolean('Active', help="If unchecked, it will allow you to hide the product without removing it."),
|
||||
'color': fields.integer('Color Index'),
|
||||
|
||||
'attribute_line_ids': fields.one2many('product.attribute.line', 'product_tmpl_id', 'Product Attributes'),
|
||||
'product_variant_ids': fields.one2many('product.product', 'product_tmpl_id', 'Products', required=True),
|
||||
'product_variant_count': fields.function(_get_product_variant_count, type='integer', string='# of Product Variants'),
|
||||
|
||||
# related to display product product information if is_product_variant
|
||||
'barcode': fields.related('product_variant_ids', 'barcode', type='char', string='Barcode', oldname='ean13'),
|
||||
'default_code': fields.function(_compute_product_template_field, fnct_inv=_set_product_template_field, multi='_compute_product_template_field', type='char', string='Internal Reference', store={
|
||||
_name: (lambda s,c,u,i,t: i, ['product_variant_ids'], 10),
|
||||
'product.product': (_get_template_id_from_product, ['product_tmpl_id', 'default_code'], 10),
|
||||
}),
|
||||
|
||||
'item_ids': fields.one2many('product.pricelist.item', 'product_tmpl_id', 'Pricelist Items'),
|
||||
}
|
||||
item_ids = fields.One2many('product.pricelist.item', 'product_tmpl_id', 'Pricelist Items')
|
||||
|
||||
# image: all image fields are base64 encoded and PIL-supported
|
||||
image = openerp.fields.Binary("Image", attachment=True,
|
||||
image = fields.Binary(
|
||||
"Image", attachment=True,
|
||||
help="This field holds the image used as image for the product, limited to 1024x1024px.")
|
||||
image_medium = openerp.fields.Binary("Medium-sized image", attachment=True,
|
||||
help="Medium-sized image of the product. It is automatically "\
|
||||
"resized as a 128x128px image, with aspect ratio preserved, "\
|
||||
image_medium = fields.Binary(
|
||||
"Medium-sized image", attachment=True,
|
||||
help="Medium-sized image of the product. It is automatically "
|
||||
"resized as a 128x128px image, with aspect ratio preserved, "
|
||||
"only when the image exceeds one of those sizes. Use this field in form views or some kanban views.")
|
||||
image_small = openerp.fields.Binary("Small-sized image", attachment=True,
|
||||
help="Small-sized image of the product. It is automatically "\
|
||||
"resized as a 64x64px image, with aspect ratio preserved. "\
|
||||
image_small = fields.Binary(
|
||||
"Small-sized image", attachment=True,
|
||||
help="Small-sized image of the product. It is automatically "
|
||||
"resized as a 64x64px image, with aspect ratio preserved. "
|
||||
"Use this field anywhere a small image is required.")
|
||||
|
||||
@api.multi
|
||||
def _compute_currency_id(self):
|
||||
try:
|
||||
main_company = self.sudo().env.ref('base.main_company')
|
||||
except ValueError:
|
||||
main_company = self.env['res.company'].sudo().search([], limit=1, order="id")
|
||||
for template in self:
|
||||
template.currency_id = template.company_id.currency_id.id or main_company.currency_id.id
|
||||
|
||||
@api.multi
|
||||
def _compute_template_price(self):
|
||||
prices = {}
|
||||
pricelist_id_or_name = self._context.get('pricelist')
|
||||
if pricelist_id_or_name:
|
||||
pricelist = None
|
||||
partner = self._context.get('partner')
|
||||
quantity = self._context.get('quantity')
|
||||
|
||||
# Support context pricelists specified as display_name or ID for compatibility
|
||||
if isinstance(pricelist_id_or_name, basestring):
|
||||
pricelist = self.env['product.pricelist'].name_search(pricelist_id_or_name, operator='=', limit=1)
|
||||
elif isinstance(pricelist_id_or_name, (int, long)):
|
||||
pricelist = self.env['product.pricelist'].browse(pricelist_id_or_name)
|
||||
|
||||
if pricelist:
|
||||
quantities = [quantity] * len(self)
|
||||
partners = [partner] * len(self)
|
||||
prices = pricelist.get_products_price(self, quantities, partners)
|
||||
|
||||
for template in self:
|
||||
template.price = prices.get(template.id, 0.0)
|
||||
|
||||
@api.multi
|
||||
def _set_template_price(self):
|
||||
if self._context.get('uom'):
|
||||
for template in self:
|
||||
value = self.env['product.uom'].browse(self._context['uom'])._compute_price(template.price, template.uom_id)
|
||||
template.write({'list_price': value})
|
||||
else:
|
||||
self.write({'list_price': self.price})
|
||||
|
||||
@api.depends('product_variant_count', 'product_variant_ids.standard_price')
|
||||
def _compute_standard_price(self):
|
||||
unique_variants = self.filtered(lambda template: template.product_variant_count == 1)
|
||||
for template in unique_variants:
|
||||
template.standard_price = template.product_variant_ids[0].standard_price
|
||||
for template in (self - unique_variants):
|
||||
template.standard_price = 0.0
|
||||
|
||||
@api.one
|
||||
def _set_standard_price(self):
|
||||
if self.product_variant_count == 1:
|
||||
self.product_variant_ids.standard_price = self.standard_price
|
||||
|
||||
def _search_standard_price(self, operator, value):
|
||||
products = self.env['product.product'].search([('standard_price', operator, value)], limit=None)
|
||||
return [('id', 'in', products.mapped('product_tmpl_id').ids)]
|
||||
|
||||
@api.depends('product_variant_count', 'product_variant_ids.volume')
|
||||
def _compute_volume(self):
|
||||
unique_variants = self.filtered(lambda template: template.product_variant_count == 1)
|
||||
for template in unique_variants:
|
||||
template.volume = template.product_variant_ids[0].volume
|
||||
for template in (self - unique_variants):
|
||||
template.volume = 0.0
|
||||
|
||||
@api.one
|
||||
def _set_volume(self):
|
||||
if self.product_variant_count == 1:
|
||||
self.product_variant_ids.volume = self.volume
|
||||
|
||||
@api.depends('product_variant_count', 'product_variant_ids.weight')
|
||||
def _compute_weight(self):
|
||||
unique_variants = self.filtered(lambda template: template.product_variant_count == 1)
|
||||
for template in unique_variants:
|
||||
template.weight = template.product_variant_ids[0].weight
|
||||
for template in (self - unique_variants):
|
||||
template.weight = 0.0
|
||||
|
||||
@api.one
|
||||
def _set_weight(self):
|
||||
if self.product_variant_count == 1:
|
||||
self.product_variant_ids.weight = self.weight
|
||||
|
||||
@api.one
|
||||
@api.depends('product_variant_ids.product_tmpl_id')
|
||||
def _compute_product_variant_count(self):
|
||||
self.product_variant_count = len(self.product_variant_ids)
|
||||
|
||||
@api.depends('product_variant_count', 'product_variant_ids.default_code')
|
||||
def _compute_default_code(self):
|
||||
unique_variants = self.filtered(lambda template: template.product_variant_count == 1)
|
||||
for template in unique_variants:
|
||||
template.default_code = template.product_variant_ids[0].default_code
|
||||
for template in (self - unique_variants):
|
||||
template.default_code = ''
|
||||
|
||||
@api.one
|
||||
def _set_default_code(self):
|
||||
if self.product_variant_count == 1:
|
||||
self.product_variant_ids.default_code = self.default_code
|
||||
|
||||
@api.constrains('uom_id', 'uom_po_id')
|
||||
def _check_uom(self):
|
||||
if any(template.uom_id and template.uom_po_id and template.uom_id.category_id != template.uom_po_id.category_id for template in self):
|
||||
raise ValidationError(_('Error: The default Unit of Measure and the purchase Unit of Measure must be in the same category.'))
|
||||
return True
|
||||
|
||||
@api.onchange('uom_id')
|
||||
def _onchange_uom_id(self):
|
||||
if self.uom_id:
|
||||
self.uom_po_id = self.uom_id.id
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
''' Store the initial standard price in order to be able to retrieve the cost of a product template for a given date'''
|
||||
# TDE FIXME: context brol
|
||||
tools.image_resize_images(vals)
|
||||
template = super(ProductTemplate, self).create(vals)
|
||||
if "create_product_product" not in self._context:
|
||||
template.create_variant_ids()
|
||||
return template
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
tools.image_resize_images(vals)
|
||||
res = super(ProductTemplate, self).write(vals)
|
||||
if 'attribute_line_ids' in vals or vals.get('active'):
|
||||
self.create_variant_ids()
|
||||
if 'active' in vals and not vals.get('active'):
|
||||
self.with_context(active_test=False).mapped('product_variant_ids').write({'active': vals.get('active')})
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def copy(self, default=None):
|
||||
# TDE FIXME: should probably be copy_data
|
||||
self.ensure_one()
|
||||
if default is None:
|
||||
default = {}
|
||||
if 'name' not in default:
|
||||
default['name'] = _("%s (copy)") % self.name
|
||||
return super(ProductTemplate, self).copy(default=default)
|
||||
|
||||
@api.multi
|
||||
def name_get(self):
|
||||
return [(template.id, '%s%s' % (template.default_code and '[%s] ' % template.default_code or '', template.name))
|
||||
for template in self]
|
||||
|
||||
@api.multi
|
||||
def name_search(self, name='', args=None, operator='ilike', limit=100):
|
||||
# Only use the product.product heuristics if there is a search term and the domain
|
||||
# does not specify a match on `product.template` IDs.
|
||||
if not name or any(term[0] == 'id' for term in (args or [])):
|
||||
return super(ProductTemplate, self).name_search(name=name, args=args, operator=operator, limit=limit)
|
||||
|
||||
Product = self.env['product.product']
|
||||
products = Product.search(name, args, operator=operator, limit=limit)
|
||||
templates = products.mapped('product_tmpl_id')
|
||||
while products and len(templates) < limit:
|
||||
domain = [('product_tmpl_id', 'not in', templates.ids)]
|
||||
args = args if args is not None else []
|
||||
products = Product.search(name, args+domain, operator=operator)
|
||||
templates |= products.mapped('product_tmpl_id')
|
||||
|
||||
# re-apply product.template order + name_get
|
||||
return super(ProductTemplate, self).name_search(
|
||||
'', args=[('id', 'in', templates.ids)],
|
||||
operator='ilike', limit=limit)
|
||||
|
||||
@api.multi
|
||||
def price_compute(self, price_type, uom=False, currency=False, company=False):
|
||||
# TDE FIXME: delegate to template or not ? fields are reencoded here ...
|
||||
@@ -216,43 +335,17 @@ class product_template(osv.osv):
|
||||
return prices
|
||||
|
||||
# compatibility to remove after v10 - DEPRECATED
|
||||
def _price_get(self, cr, uid, products, ptype='list_price', context=None):
|
||||
@api.model
|
||||
def _price_get(self, products, ptype='list_price'):
|
||||
return products.price_compute(ptype)
|
||||
|
||||
def _get_uom_id(self, cr, uid, *args):
|
||||
return self.pool["product.uom"].search(cr, uid, [], limit=1, order='id')[0]
|
||||
|
||||
def _default_category(self, cr, uid, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if 'categ_id' in context and context['categ_id']:
|
||||
return context['categ_id']
|
||||
md = self.pool.get('ir.model.data')
|
||||
res = False
|
||||
try:
|
||||
res = md.get_object_reference(cr, uid, 'product', 'product_category_all')[1]
|
||||
except ValueError:
|
||||
res = False
|
||||
return res
|
||||
|
||||
def onchange_type(self, cr, uid, ids, type, context=None):
|
||||
return {'value': {}}
|
||||
|
||||
def onchange_uom(self, cr, uid, ids, uom_id, uom_po_id):
|
||||
if uom_id:
|
||||
return {'value': {'uom_po_id': uom_id}}
|
||||
return {}
|
||||
|
||||
def create_variant_ids(self, cr, uid, ids, context=None):
|
||||
product_obj = self.pool.get("product.product")
|
||||
ctx = context and context.copy() or {}
|
||||
if ctx.get("create_product_variant"):
|
||||
@api.multi
|
||||
def create_variant_ids(self):
|
||||
Product = self.env["product.product"]
|
||||
if self._context.get("create_product_variant"):
|
||||
return None
|
||||
|
||||
ctx.update(active_test=False, create_product_variant=True)
|
||||
|
||||
tmpl_ids = self.browse(cr, uid, ids, context=ctx)
|
||||
for tmpl_id in tmpl_ids:
|
||||
for tmpl_id in self.with_context(active_test=False, create_product_variant=True):
|
||||
|
||||
# list of values combination
|
||||
variant_alone = []
|
||||
@@ -269,146 +362,41 @@ class product_template(osv.osv):
|
||||
|
||||
# adding an attribute with only one value should not recreate product
|
||||
# write this attribute on every product to make sure we don't lose them
|
||||
for variant_id in variant_alone:
|
||||
product_ids = []
|
||||
for product_id in tmpl_id.product_variant_ids:
|
||||
if not variant_id.attribute_id <= product_id.mapped('attribute_value_ids').mapped('attribute_id'):
|
||||
product_ids.append(product_id.id)
|
||||
product_obj.write(cr, uid, product_ids, {'attribute_value_ids': [(4, variant_id.id)]}, context=ctx)
|
||||
for value_id in variant_alone:
|
||||
updated_products = tmpl_id.product_variant_ids.filtered(lambda product: value_id.attribute_id not in product.mapped('attribute_value_ids.attribute_id'))
|
||||
updated_products.write({'attribute_value_ids': [(4, value_id.id)]})
|
||||
|
||||
# check product
|
||||
variant_ids_to_active = []
|
||||
variants_active_ids = []
|
||||
variants_inactive = []
|
||||
variants_to_activate = self.env['product.product']
|
||||
variants_active = self.env['product.product']
|
||||
variants_to_unlink = self.env['product.product']
|
||||
for product_id in tmpl_id.product_variant_ids:
|
||||
variants = sorted(map(int,product_id.attribute_value_ids))
|
||||
variants = sorted(map(int, product_id.attribute_value_ids))
|
||||
if variants in all_variants:
|
||||
variants_active_ids.append(product_id.id)
|
||||
variants_active |= product_id
|
||||
all_variants.pop(all_variants.index(variants))
|
||||
if not product_id.active:
|
||||
variant_ids_to_active.append(product_id.id)
|
||||
variants_to_activate |= product_id
|
||||
else:
|
||||
variants_inactive.append(product_id)
|
||||
if variant_ids_to_active:
|
||||
product_obj.write(cr, uid, variant_ids_to_active, {'active': True}, context=ctx)
|
||||
variants_to_unlink |= product_id
|
||||
if variants_to_activate:
|
||||
variants_to_activate.write({'active': True})
|
||||
|
||||
# create new product
|
||||
for variant_ids in all_variants:
|
||||
values = {
|
||||
new_variant = Product.create({
|
||||
'product_tmpl_id': tmpl_id.id,
|
||||
'attribute_value_ids': [(6, 0, variant_ids)]
|
||||
}
|
||||
id = product_obj.create(cr, uid, values, context=ctx)
|
||||
variants_active_ids.append(id)
|
||||
})
|
||||
variants_active |= new_variant
|
||||
|
||||
# unlink or inactive product
|
||||
for variant_id in map(int,variants_inactive):
|
||||
for variant in variants_to_activate:
|
||||
try:
|
||||
with cr.savepoint(), tools.mute_logger('openerp.sql_db'):
|
||||
product_obj.unlink(cr, uid, [variant_id], context=ctx)
|
||||
#We catch all kind of exception to be sure that the operation doesn't fail.
|
||||
with self._cr.savepoint(), tools.mute_logger('openerp.sql_db'):
|
||||
variant.unlink()
|
||||
# We catch all kind of exception to be sure that the operation doesn't fail.
|
||||
except (psycopg2.Error, except_orm):
|
||||
product_obj.write(cr, uid, [variant_id], {'active': False}, context=ctx)
|
||||
variant.write({'active': False})
|
||||
pass
|
||||
return True
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
''' Store the initial standard price in order to be able to retrieve the cost of a product template for a given date'''
|
||||
tools.image_resize_images(vals)
|
||||
product_template_id = super(product_template, self).create(cr, uid, vals, context=context)
|
||||
if not context or "create_product_product" not in context:
|
||||
self.create_variant_ids(cr, uid, [product_template_id], context=context)
|
||||
|
||||
# TODO: this is needed to set given values to first variant after creation
|
||||
# these fields should be moved to product as lead to confusion
|
||||
related_vals = {}
|
||||
if vals.get('barcode'):
|
||||
related_vals['barcode'] = vals['barcode']
|
||||
if vals.get('default_code'):
|
||||
related_vals['default_code'] = vals['default_code']
|
||||
if vals.get('standard_price'):
|
||||
related_vals['standard_price'] = vals['standard_price']
|
||||
if vals.get('volume'):
|
||||
related_vals['volume'] = vals['volume']
|
||||
if vals.get('weight'):
|
||||
related_vals['weight'] = vals['weight']
|
||||
if related_vals:
|
||||
self.write(cr, uid, product_template_id, related_vals, context=context)
|
||||
|
||||
return product_template_id
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
tools.image_resize_images(vals)
|
||||
res = super(product_template, self).write(cr, uid, ids, vals, context=context)
|
||||
if 'attribute_line_ids' in vals or vals.get('active'):
|
||||
self.create_variant_ids(cr, uid, ids, context=context)
|
||||
if 'active' in vals and not vals.get('active'):
|
||||
ctx = context and context.copy() or {}
|
||||
ctx.update(active_test=False)
|
||||
product_ids = []
|
||||
for product in self.browse(cr, uid, ids, context=ctx):
|
||||
product_ids += map(int, product.product_variant_ids)
|
||||
self.pool.get("product.product").write(cr, uid, product_ids, {'active': vals.get('active')}, context=ctx)
|
||||
return res
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
if 'name' not in default:
|
||||
template = self.browse(cr, uid, id, context=context)
|
||||
default['name'] = _("%s (copy)") % (template['name'])
|
||||
return super(product_template, self).copy(cr, uid, id, default=default, context=context)
|
||||
|
||||
_defaults = {
|
||||
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'product.template', context=c),
|
||||
'list_price': 1,
|
||||
'standard_price': 0.0,
|
||||
'sale_ok': 1,
|
||||
'uom_id': _get_uom_id,
|
||||
'uom_po_id': _get_uom_id,
|
||||
'categ_id' : _default_category,
|
||||
'type' : 'consu',
|
||||
'active': True,
|
||||
'sequence': 1,
|
||||
}
|
||||
|
||||
def _check_uom(self, cr, uid, ids, context=None):
|
||||
for product in self.browse(cr, uid, ids, context=context):
|
||||
if product.uom_id.category_id.id != product.uom_po_id.category_id.id:
|
||||
return False
|
||||
return True
|
||||
|
||||
_constraints = [
|
||||
(_check_uom, 'Error: The default Unit of Measure and the purchase Unit of Measure must be in the same category.', ['uom_id', 'uom_po_id']),
|
||||
]
|
||||
|
||||
def name_get(self, cr, uid, ids, context=None):
|
||||
return [(product.id, '%s%s' % (product.default_code and '[%s] ' % product.default_code or '', product.name))
|
||||
for product in self.browse(cr, uid, ids, context=context)]
|
||||
|
||||
def name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=100):
|
||||
# Only use the product.product heuristics if there is a search term and the domain
|
||||
# does not specify a match on `product.template` IDs.
|
||||
if not name or any(term[0] == 'id' for term in (args or [])):
|
||||
return super(product_template, self).name_search(
|
||||
cr, uid, name=name, args=args, operator=operator, context=context, limit=limit)
|
||||
template_ids = set()
|
||||
product_product = self.pool['product.product']
|
||||
results = product_product.name_search(cr, uid, name, args, operator=operator, context=context, limit=limit)
|
||||
product_ids = [p[0] for p in results]
|
||||
for p in product_product.browse(cr, uid, product_ids, context=context):
|
||||
template_ids.add(p.product_tmpl_id.id)
|
||||
while (results and len(template_ids) < limit):
|
||||
domain = [('product_tmpl_id', 'not in', list(template_ids))]
|
||||
args = args if args is not None else []
|
||||
results = product_product.name_search(
|
||||
cr, uid, name, args+domain, operator=operator, context=context, limit=limit)
|
||||
product_ids = [p[0] for p in results]
|
||||
for p in product_product.browse(cr, uid, product_ids, context=context):
|
||||
template_ids.add(p.product_tmpl_id.id)
|
||||
|
||||
|
||||
# re-apply product.template order + name_get
|
||||
return super(product_template, self).name_search(
|
||||
cr, uid, '', args=[('id', 'in', list(template_ids))],
|
||||
operator='ilike', context=context, limit=limit)
|
||||
|
||||
@@ -1,133 +1,108 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import re
|
||||
import time
|
||||
|
||||
import openerp
|
||||
from openerp import api, tools, SUPERUSER_ID
|
||||
from openerp.osv import osv, fields, expression
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FORMAT
|
||||
import psycopg2
|
||||
|
||||
import openerp.addons.decimal_precision as dp
|
||||
from openerp.tools.float_utils import float_round, float_compare
|
||||
from openerp.exceptions import UserError
|
||||
from openerp.exceptions import except_orm
|
||||
from odoo import api, fields, tools, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class product_uom_categ(osv.osv):
|
||||
class ProductUoMCategory(models.Model):
|
||||
_name = 'product.uom.categ'
|
||||
_description = 'Product uom categ'
|
||||
_columns = {
|
||||
'name': fields.char('Name', required=True, translate=True),
|
||||
}
|
||||
_description = 'Product UoM Categories'
|
||||
|
||||
class product_uom(osv.osv):
|
||||
name = fields.Char('Name', required=True, translate=True)
|
||||
|
||||
|
||||
class ProductUoM(models.Model):
|
||||
_name = 'product.uom'
|
||||
_description = 'Product Unit of Measure'
|
||||
|
||||
def _compute_factor_inv(self, factor):
|
||||
return factor and (1.0 / factor) or 0.0
|
||||
|
||||
def _factor_inv(self, cr, uid, ids, name, arg, context=None):
|
||||
res = {}
|
||||
for uom in self.browse(cr, uid, ids, context=context):
|
||||
res[uom.id] = self._compute_factor_inv(uom.factor)
|
||||
return res
|
||||
|
||||
def _factor_inv_write(self, cr, uid, id, name, value, arg, context=None):
|
||||
return self.write(cr, uid, id, {'factor': self._compute_factor_inv(value)}, context=context)
|
||||
|
||||
def name_create(self, cr, uid, name, context=None):
|
||||
""" The UoM category and factor are required, so we'll have to add temporary values
|
||||
for imported UoMs """
|
||||
if not context:
|
||||
context = {}
|
||||
uom_categ = self.pool.get('product.uom.categ')
|
||||
values = {self._rec_name: name, 'factor': 1}
|
||||
# look for the category based on the english name, i.e. no context on purpose!
|
||||
# TODO: should find a way to have it translated but not created until actually used
|
||||
if not context.get('default_category_id'):
|
||||
categ_misc = 'Unsorted/Imported Units'
|
||||
categ_id = uom_categ.search(cr, uid, [('name', '=', categ_misc)])
|
||||
if categ_id:
|
||||
values['category_id'] = categ_id[0]
|
||||
else:
|
||||
values['category_id'] = uom_categ.name_create(
|
||||
cr, uid, categ_misc, context=context)[0]
|
||||
uom_id = self.create(cr, uid, values, context=context)
|
||||
return self.name_get(cr, uid, [uom_id], context=context)[0]
|
||||
|
||||
def create(self, cr, uid, data, context=None):
|
||||
if 'factor_inv' in data:
|
||||
if data['factor_inv'] != 1:
|
||||
data['factor'] = self._compute_factor_inv(data['factor_inv'])
|
||||
del(data['factor_inv'])
|
||||
return super(product_uom, self).create(cr, uid, data, context)
|
||||
|
||||
_order = "name"
|
||||
_columns = {
|
||||
'name': fields.char('Unit of Measure', required=True, translate=True),
|
||||
'category_id': fields.many2one('product.uom.categ', 'Unit of Measure Category', required=True, ondelete='cascade',
|
||||
help="Conversion between Units of Measure can only occur if they belong to the same category. The conversion will be made based on the ratios."),
|
||||
'factor': fields.float('Ratio', required=True, digits=0, # force NUMERIC with unlimited precision
|
||||
help='How much bigger or smaller this unit is compared to the reference Unit of Measure for this category:\n'\
|
||||
'1 * (reference unit) = ratio * (this unit)'),
|
||||
'factor_inv': fields.function(_factor_inv, digits=0, # force NUMERIC with unlimited precision
|
||||
fnct_inv=_factor_inv_write,
|
||||
string='Bigger Ratio',
|
||||
help='How many times this Unit of Measure is bigger than the reference Unit of Measure in this category:\n'\
|
||||
'1 * (this unit) = ratio * (reference unit)', required=True),
|
||||
'rounding': fields.float('Rounding Precision', digits=0, required=True,
|
||||
help="The computed quantity will be a multiple of this value. "\
|
||||
"Use 1.0 for a Unit of Measure that cannot be further split, such as a piece."),
|
||||
'active': fields.boolean('Active', help="Uncheck the active field to disable a unit of measure without deleting it."),
|
||||
'uom_type': fields.selection([('bigger','Bigger than the reference Unit of Measure'),
|
||||
('reference','Reference Unit of Measure for this category'),
|
||||
('smaller','Smaller than the reference Unit of Measure')],'Type', required=1),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'active': 1,
|
||||
'rounding': 0.01,
|
||||
'factor': 1,
|
||||
'uom_type': 'reference',
|
||||
'factor': 1.0,
|
||||
}
|
||||
name = fields.Char('Unit of Measure', required=True, translate=True)
|
||||
category_id = fields.Many2one(
|
||||
'product.uom.categ', 'Category', required=True, ondelete='cascade',
|
||||
help="Conversion between Units of Measure can only occur if they belong to the same category. The conversion will be made based on the ratios.")
|
||||
factor = fields.Float(
|
||||
'Ratio', default=1.0, digits=0, required=True, # force NUMERIC with unlimited precision
|
||||
help='How much bigger or smaller this unit is compared to the reference Unit of Measure for this category: 1 * (reference unit) = ratio * (this unit)')
|
||||
factor_inv = fields.Float(
|
||||
'Bigger Ratio', compute='_compute_factor_inv', digits=0, # force NUMERIC with unlimited precision
|
||||
readonly=True, required=True,
|
||||
help='How many times this Unit of Measure is bigger than the reference Unit of Measure in this category: 1 * (this unit) = ratio * (reference unit)')
|
||||
rounding = fields.Float(
|
||||
'Rounding Precision', default=0.01, digits=0, required=True,
|
||||
help="The computed quantity will be a multiple of this value. "
|
||||
"Use 1.0 for a Unit of Measure that cannot be further split, such as a piece.")
|
||||
active = fields.Boolean('Active', default=True, help="Uncheck the active field to disable a unit of measure without deleting it.")
|
||||
uom_type = fields.Selection([
|
||||
('bigger', 'Bigger than the reference Unit of Measure'),
|
||||
('reference', 'Reference Unit of Measure for this category'),
|
||||
('smaller', 'Smaller than the reference Unit of Measure')], 'Type',
|
||||
default='reference', required=1)
|
||||
|
||||
_sql_constraints = [
|
||||
('factor_gt_zero', 'CHECK (factor!=0)', 'The conversion ratio for a unit of measure cannot be 0!')
|
||||
]
|
||||
|
||||
def _compute_quantity(self, cr, uid, ids, qty, to_unit, round=True, rounding_method='UP', context=None):
|
||||
context = context if context is not None else {}
|
||||
from_unit = self.browse(cr, uid, ids[0], context=context)
|
||||
if from_unit.category_id.id != to_unit.category_id.id:
|
||||
if context.get('raise-exception', True):
|
||||
raise UserError(_('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))
|
||||
@api.one
|
||||
@api.depends('factor')
|
||||
def _compute_factor_inv(self):
|
||||
self.factor_inv = self.factor and (1.0 / self.factor) or 0.0
|
||||
|
||||
@api.onchange('uom_type')
|
||||
def _onchange_uom_type(self):
|
||||
if self.uom_type == 'reference':
|
||||
self.factor = 1
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
if 'factor_inv' in values:
|
||||
factor_inv = values.pop('factor_inv')
|
||||
values['factor'] = factor_inv and (1.0 / factor_inv) or 0.0
|
||||
return super(ProductUoM, self).create(values)
|
||||
|
||||
@api.model
|
||||
def name_create(self, name):
|
||||
""" The UoM category and factor are required, so we'll have to add temporary values
|
||||
for imported UoMs """
|
||||
values = {
|
||||
self._rec_name: name,
|
||||
'factor': 1
|
||||
}
|
||||
# look for the category based on the english name, i.e. no context on purpose!
|
||||
# TODO: should find a way to have it translated but not created until actually used
|
||||
if not self._context.get('default_category_id'):
|
||||
EnglishUoMCateg = self.env['product.uom.categ'].with_context({})
|
||||
misc_category = EnglishUoMCateg.search([('name', '=', 'Unsorted/Imported Units')])
|
||||
if misc_category:
|
||||
values['category_id'] = misc_category.id
|
||||
else:
|
||||
values['category_id'] = EnglishUoMCateg.name_create('Unsorted/Imported Units').id
|
||||
new_uom = self.create(values)
|
||||
return new_uom.name_get()[0]
|
||||
|
||||
@api.multi
|
||||
def _compute_quantity(self, qty, to_unit, round=True, rounding_method='UP'):
|
||||
self.ensure_one()
|
||||
if self.category_id.id != to_unit.category_id.id:
|
||||
if self._context.get('raise-exception', True):
|
||||
raise UserError(_('Conversion from Product UoM %s to Default UoM %s is not possible as they both belong to different Category!.') % (self.name, to_unit.name))
|
||||
else:
|
||||
return qty
|
||||
amount = qty / from_unit.factor
|
||||
amount = qty / self.factor
|
||||
if to_unit:
|
||||
amount = amount * to_unit.factor
|
||||
if round:
|
||||
amount = float_round(amount, precision_rounding=to_unit.rounding, rounding_method=rounding_method)
|
||||
amount = tools.float_round(amount, precision_rounding=to_unit.rounding, rounding_method=rounding_method)
|
||||
return amount
|
||||
|
||||
def _compute_price(self, cr, uid, ids, price, to_unit, context=None):
|
||||
from_unit = self.browse(cr, uid, ids[0], context=context)
|
||||
if not from_unit or not price or not to_unit or from_unit == to_unit:
|
||||
@api.multi
|
||||
def _compute_price(self, price, to_unit):
|
||||
self.ensure_one()
|
||||
if not self or not price or not to_unit or self == to_unit:
|
||||
return price
|
||||
if from_unit.category_id.id != to_unit.category_id.id:
|
||||
if self.category_id.id != to_unit.category_id.id:
|
||||
return price
|
||||
amount = price * from_unit.factor
|
||||
amount = price * self.factor
|
||||
if to_unit:
|
||||
amount = amount / to_unit.factor
|
||||
return amount
|
||||
|
||||
def onchange_type(self, cr, uid, ids, value):
|
||||
if value == 'reference':
|
||||
return {'value': {'factor': 1, 'factor_inv': 1}}
|
||||
return {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, models, _
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
@@ -17,11 +17,11 @@ class ResCompany(models.Model):
|
||||
'name': new_company.name,
|
||||
'currency_id': new_company.currency_id.id,
|
||||
})
|
||||
field_id = self.env['ir.model.fields'].search([('model', '=', 'res.partner'), ('name', '=', 'property_product_pricelist')])
|
||||
field = self.env['ir.model.fields'].search([('model', '=', 'res.partner'), ('name', '=', 'property_product_pricelist')])
|
||||
self.env['ir.property'].create({
|
||||
'name': 'property_product_pricelist',
|
||||
'company_id': new_company.id,
|
||||
'value_reference': 'product.pricelist,%s' % pricelist.id,
|
||||
'fields_id': field_id.id
|
||||
'fields_id': field.id
|
||||
})
|
||||
return new_company
|
||||
|
||||
@@ -1,37 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import re
|
||||
import time
|
||||
|
||||
import openerp
|
||||
from openerp import api, tools, SUPERUSER_ID
|
||||
from openerp.osv import osv, fields, expression
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FORMAT
|
||||
import psycopg2
|
||||
|
||||
import openerp.addons.decimal_precision as dp
|
||||
from openerp.tools.float_utils import float_round, float_compare
|
||||
from openerp.exceptions import UserError
|
||||
from openerp.exceptions import except_orm
|
||||
from odoo import api, models, tools, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class res_currency(osv.osv):
|
||||
class ResCurrency(models.Model):
|
||||
_inherit = 'res.currency'
|
||||
|
||||
def _check_main_currency_rounding(self, cr, uid, ids, context=None):
|
||||
cr.execute('SELECT digits FROM decimal_precision WHERE name like %s',('Account',))
|
||||
digits = cr.fetchone()
|
||||
if digits and len(digits):
|
||||
digits = digits[0]
|
||||
main_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id
|
||||
for currency_id in ids:
|
||||
if currency_id == main_currency.id:
|
||||
if float_compare(main_currency.rounding, 10 ** -digits, precision_digits=6) == -1:
|
||||
return False
|
||||
@api.constrains('rounding')
|
||||
def _check_main_currency_rounding(self):
|
||||
account_precision = self.env['decimal.precision'].search([('name', '=', 'Account')])
|
||||
if any(currency == self.env.user.company_id.currency_id and
|
||||
tools.float_compare(self.env.user.company_id.currency_id.rounding, 10 ** - account_precision.digits, precision_digits=6) == -1
|
||||
for currency in self):
|
||||
raise ValidationError(_("You cannot define a rounding factor for the company's main currency that is smaller than the decimal precision of 'Account'."))
|
||||
return True
|
||||
|
||||
_constraints = [
|
||||
(_check_main_currency_rounding, 'Error! You cannot define a rounding factor for the company\'s main currency that is smaller than the decimal precision of \'Account\'.', ['rounding']),
|
||||
]
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class res_partner(osv.osv):
|
||||
class Partner(models.Model):
|
||||
_name = 'res.partner'
|
||||
_inherit = 'res.partner'
|
||||
_columns = {
|
||||
'property_product_pricelist': fields.property(
|
||||
type='many2one',
|
||||
relation='product.pricelist',
|
||||
string="Sale Pricelist",
|
||||
help="This pricelist will be used, instead of the default one, for sales to the current partner"),
|
||||
}
|
||||
|
||||
def _commercial_fields(self, cr, uid, context=None):
|
||||
return super(res_partner, self)._commercial_fields(cr, uid, context=context) + ['property_product_pricelist']
|
||||
property_product_pricelist = fields.Many2one(
|
||||
'product.pricelist', 'Sale Pricelist', company_dependent=True,
|
||||
help="This pricelist will be used, instead of the default one, for sales to the current partner")
|
||||
|
||||
def _commercial_fields(self):
|
||||
return super(Partner, self)._commercial_fields() + ['property_product_pricelist']
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
<record id="inherit_view_general_configuration" model="ir.ui.view">
|
||||
<field name="name">General Settings</field>
|
||||
<field name="model">base.config.settings</field>
|
||||
@@ -11,6 +10,4 @@
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="product_pricelist_item_tree_view" model="ir.ui.view">
|
||||
@@ -170,4 +170,4 @@
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="category_id"/>
|
||||
<field name="uom_type" on_change="onchange_type(uom_type)"/>
|
||||
<field name="uom_type"/>
|
||||
<field name="factor"
|
||||
digits="[42,5]"
|
||||
attrs="{'invisible':[('uom_type','!=','smaller')],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- base structure of product.template, common with product.product -->
|
||||
<record id="product_template_form_view" model="ir.ui.view">
|
||||
@@ -39,7 +39,7 @@
|
||||
<page string="General Information">
|
||||
<group>
|
||||
<group name="group_general">
|
||||
<field name="type" on_change="onchange_type(type, context)"/>
|
||||
<field name="type"/>
|
||||
</group>
|
||||
<group name="group_standard_price">
|
||||
<field name="list_price" widget='monetary' options="{'currency_field': 'currency_id'}"/>
|
||||
@@ -47,7 +47,7 @@
|
||||
<div name="standard_price_uom" groups="base.group_user">
|
||||
<field name="standard_price" widget='monetary' options="{'currency_field': 'currency_id'}" class="oe_inline"/>
|
||||
</div>
|
||||
<field name="uom_id" on_change="onchange_uom(uom_id,uom_po_id)" groups="product.group_uom" options="{'no_create': True}"/>
|
||||
<field name="uom_id" groups="product.group_uom" options="{'no_create': True}"/>
|
||||
<field name="uom_po_id" groups="product.group_uom" options="{'no_create': True}"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</group>
|
||||
@@ -561,4 +561,4 @@
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<odoo>
|
||||
<record id="view_partner_property_form" model="ir.ui.view">
|
||||
<field name="name">res.partner.product.property.form.inherit</field>
|
||||
<field name="model">res.partner</field>
|
||||
@@ -16,6 +14,4 @@
|
||||
</group>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
@@ -1,28 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
from odoo import api
|
||||
from odoo import api, fields, models
|
||||
|
||||
class product_price_list(osv.osv_memory):
|
||||
|
||||
class product_price_list(models.TransientModel):
|
||||
_name = 'product.price_list'
|
||||
_description = 'Price List'
|
||||
|
||||
_columns = {
|
||||
'price_list': fields.many2one('product.pricelist', 'PriceList', required=True),
|
||||
'qty1': fields.integer('Quantity-1'),
|
||||
'qty2': fields.integer('Quantity-2'),
|
||||
'qty3': fields.integer('Quantity-3'),
|
||||
'qty4': fields.integer('Quantity-4'),
|
||||
'qty5': fields.integer('Quantity-5'),
|
||||
}
|
||||
_defaults = {
|
||||
'qty1': 1,
|
||||
'qty2': 5,
|
||||
'qty3': 10,
|
||||
'qty4': 0,
|
||||
'qty5': 0,
|
||||
}
|
||||
price_list = fields.Many2one('product.pricelist', 'PriceList', required=True)
|
||||
qty1 = fields.Integer('Quantity-1', default=1)
|
||||
qty2 = fields.Integer('Quantity-2', default=5)
|
||||
qty3 = fields.Integer('Quantity-3', default=10)
|
||||
qty4 = fields.Integer('Quantity-4', default=0)
|
||||
qty5 = fields.Integer('Quantity-5', default=0)
|
||||
|
||||
@api.multi
|
||||
def print_report(self):
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
|
||||
<!-- Product Price List -->
|
||||
|
||||
<record id="view_product_price_list" model="ir.ui.view">
|
||||
<field name="name">Price per unit</field>
|
||||
<field name="model">product.price_list</field>
|
||||
@@ -32,6 +30,4 @@
|
||||
src_model="product.product"
|
||||
groups="product.group_sale_pricelist"
|
||||
view_mode="form" target="new" view_type="form" />
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -343,6 +343,7 @@ class Product(models.Model):
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = 'product.template'
|
||||
|
||||
type = fields.Selection(selection_add=[('product', 'Stockable Product')])
|
||||
property_stock_procurement = fields.Many2one(
|
||||
'stock.location', "Procurement Location",
|
||||
company_dependent=True, domain=[('usage', 'like', 'procurement')],
|
||||
@@ -443,13 +444,6 @@ class ProductTemplate(models.Model):
|
||||
template.reordering_min_qty = res[template.id]['reordering_min_qty']
|
||||
template.reordering_max_qty = res[template.id]['reordering_max_qty']
|
||||
|
||||
@api.model
|
||||
def _get_product_template_type(self):
|
||||
res = super(ProductTemplate, self)._get_product_template_type()
|
||||
if 'product' not in [item[0] for item in res]:
|
||||
res.append(('product', _('Stockable Product')))
|
||||
return res
|
||||
|
||||
@api.onchange('tracking')
|
||||
def onchange_tracking(self):
|
||||
return self.mapped('product_variant_ids').onchange_tracking()
|
||||
|
||||
Reference in New Issue
Block a user