[IMP][REF] product,sale,p_o_s: pricelist usability refactoring

Manage pricelists through a common configuration between sale and point_of_sale
Remove lots of technical fields

Improved Pricelist Flow
-----------------------

if pricelists enabled (product.group_product_pricelist), two modes are proposed:
* basic(default): pricelists enabled, but only with basic rules
* advanced: pricelists enabled, but with advanced rules (product.group_sale_pricelist)

When pricelists are enabled, we can always access them through the menu OR access their product-targeted rules from the products/templates views.
This commit is contained in:
Victor Feyens
2019-08-14 14:03:23 +00:00
parent 852f02ba14
commit 7d8df4bec1
19 changed files with 441 additions and 381 deletions
@@ -8,36 +8,3 @@ class ResConfigSettings(models.TransientModel):
sale_tax_id = fields.Many2one('account.tax', string="Default Sale Tax", related='company_id.account_sale_tax_id', readonly=False)
module_pos_mercury = fields.Boolean(string="Integrated Card Payments", help="The transactions are processed by Vantiv. Set your Vantiv credentials on the related payment journal.")
pos_sales_price = fields.Boolean("Multiple Product Prices", config_parameter='point_of_sale.pos_sales_price')
pos_pricelist_setting = fields.Selection([
('percentage', 'Multiple prices per product (e.g. customer segments, currencies)'),
('formula', 'Price computed from formulas (discounts, margins, roundings)')
], string="POS Pricelists", config_parameter='point_of_sale.pos_pricelist_setting')
@api.onchange('pos_sales_price')
def _onchange_pos_sales_price(self):
if not self.pos_sales_price:
self.pos_pricelist_setting = False
if self.pos_sales_price and not self.pos_pricelist_setting:
self.pos_pricelist_setting = 'percentage'
@api.onchange('pos_pricelist_setting')
def _onchange_pos_pricelist_setting(self):
if self.pos_pricelist_setting == 'percentage':
self.update({
'group_product_pricelist': True,
'group_sale_pricelist': True,
'group_pricelist_item': False,
})
elif self.pos_pricelist_setting == 'formula':
self.update({
'group_product_pricelist': False,
'group_sale_pricelist': True,
'group_pricelist_item': True,
})
else:
self.update({
'group_product_pricelist': False,
'group_sale_pricelist': False,
'group_pricelist_item': False,
})
@@ -29,24 +29,21 @@
</div>
<h2>Pricing</h2>
<div class="row mt16 o_settings_container">
<div class="col-12 col-lg-6 o_setting_box" title="There are two ways to manage pricelists: 1) Multiple prices per product (e.g. quantity, shop-specific) : must be set in the Sales tab of the product detail form. 2) Price computed from formulas (discounts, margins, rounding) : must be set in the pricelist form.">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="pos_sales_price"/>
<field name="group_product_pricelist"/>
</div>
<div class="o_setting_right_pane">
<label for="pos_sales_price"/>
<label for="group_product_pricelist"/>
<div class="text-muted">
Set multiple prices per product, automated discounts, etc.
</div>
<div class="content-group" attrs="{'invisible': [('pos_sales_price' ,'=', False)]}">
<div class="content-group" attrs="{'invisible': [('group_product_pricelist' ,'=', False)]}">
<div class="mt16">
<field name="group_product_pricelist" invisible="1"/>
<field name="group_sale_pricelist" invisible="1"/>
<field name="group_pricelist_item" invisible="1"/>
<field name="pos_pricelist_setting" widget="radio" class="o_light_label"/>
<field name="product_pricelist_setting" widget="radio" class="o_light_label"/>
</div>
<button name="%(product.product_pricelist_action2)d" icon="fa-arrow-right" type="action" string="Pricelists"
attrs="{'invisible': [('pos_pricelist_setting', '=', False)]}" class="btn-link"/>
<button name="%(product.product_pricelist_action2)d" icon="fa-arrow-right" type="action" string="Pricelists" groups="product.group_sale_pricelist" class="btn-link"/>
</div>
</div>
</div>
+25 -7
View File
@@ -113,8 +113,7 @@ class ProductProduct(models.Model):
volume = fields.Float('Volume')
weight = fields.Float('Weight', digits='Stock Weight')
pricelist_item_ids = fields.Many2many(
'product.pricelist.item', 'Pricelist Items', compute='_get_pricelist_items')
pricelist_item_count = fields.Integer("Number of price rules", compute="_compute_variant_item_count")
packaging_ids = fields.One2many(
'product.packaging', 'product_id', 'Product Packages',
@@ -317,12 +316,12 @@ class ProductProduct(models.Model):
else:
product.product_template_attribute_value_ids += values_per_template[product.product_tmpl_id.id][pav.id]
def _get_pricelist_items(self):
def _compute_variant_item_count(self):
for product in self:
product.pricelist_item_ids = self.env['product.pricelist.item'].search([
'|',
('product_id', '=', product.id),
('product_tmpl_id', '=', product.product_tmpl_id.id)]).ids
domain = ['|',
'&', ('product_tmpl_id', '=', product.product_tmpl_id.id), ('applied_on', '=', '1_product'),
'&', ('product_id', '=', product.id), ('applied_on', '=', '0_product_variant')]
product.pricelist_item_count = self.env['product.pricelist.item'].search_count(domain)
@api.constrains('attribute_value_ids')
def _check_attribute_value_ids(self):
@@ -581,6 +580,25 @@ class ProductProduct(models.Model):
return _('Products: ') + self.env['product.category'].browse(self._context['categ_id']).name
return res
def open_pricelist_rules(self):
self.ensure_one()
domain = ['|',
'&', ('product_tmpl_id', '=', self.product_tmpl_id.id), ('applied_on', '=', '1_product'),
'&', ('product_id', '=', self.id), ('applied_on', '=', '0_product_variant')]
return {
'name': _('Price Rules'),
'view_mode': 'tree,form',
'views': [(self.env.ref('product.product_pricelist_item_tree_view_from_product').id, 'tree'), (False, 'form')],
'res_model': 'product.pricelist.item',
'type': 'ir.actions.act_window',
'target': 'current',
'domain': domain,
'context': {
'default_product_id': self.id,
'default_applied_on': '0_product_variant',
}
}
def open_product_template(self):
""" Utility method used to add an "Open Template" button in product views """
self.ensure_one()
+94 -36
View File
@@ -7,7 +7,6 @@ from odoo import api, fields, models, tools, _
from odoo.exceptions import UserError, ValidationError
class Pricelist(models.Model):
_name = "product.pricelist"
_description = "Pricelist"
@@ -46,7 +45,7 @@ class Pricelist(models.Model):
FROM ((
SELECT pr.id, pr.name
FROM product_pricelist pr JOIN
res_currency cur ON
res_currency cur ON
(pr.currency_id = cur.id)
WHERE pr.name || ' (' || cur.name || ')' = %(name)s
)
@@ -59,7 +58,7 @@ class Pricelist(models.Model):
tr.name = 'product.pricelist,name' AND
tr.lang = %(lang)s
) JOIN
res_currency cur ON
res_currency cur ON
(pr.currency_id = cur.id)
WHERE tr.value || ' (' || cur.name || ')' = %(name)s
)
@@ -374,20 +373,22 @@ class ResCountryGroup(models.Model):
class PricelistItem(models.Model):
_name = "product.pricelist.item"
_description = "Pricelist Item"
_description = "Pricelist Rule"
_order = "applied_on, min_quantity desc, categ_id desc, id desc"
# NOTE: if you change _order on this model, make sure it matches the SQL
# query built in _compute_price_rule() above in this file to avoid
# inconstencies and undeterministic issues.
def _get_default_applied_on(self):
return self.env.user.has_group('product.group_product_pricelist') and '1_product' or '3_global'
def _default_pricelist_id(self):
return self.env['product.pricelist'].search([
'|', ('company_id', '=', False),
('company_id', '=', self.env.company.id)], limit=1)
product_tmpl_id = fields.Many2one(
'product.template', 'Product Template', ondelete='cascade',
'product.template', 'Product', 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',
'product.product', 'Product Variant', 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',
@@ -398,11 +399,11 @@ class PricelistItem(models.Model):
"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'),
('3_global', 'All Products'),
('2_product_category', ' Product Category'),
('1_product', 'Product'),
('0_product_variant', 'Product Variant')], "Apply On",
default=_get_default_applied_on, required=True,
default='3_global', required=True,
help='Pricelist Item applicable on selected option')
base = fields.Selection([
('list_price', 'Sales Price'),
@@ -414,7 +415,7 @@ class PricelistItem(models.Model):
'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', required=True)
pricelist_id = fields.Many2one('product.pricelist', 'Pricelist', index=True, ondelete='cascade', required=True, default=_default_pricelist_id)
price_surcharge = fields.Float(
'Price Surcharge', digits='Product Price',
help='Specify the fixed amount to add or substract(if negative) to the amount calculated with the discount.')
@@ -436,13 +437,15 @@ class PricelistItem(models.Model):
currency_id = fields.Many2one(
'res.currency', 'Currency',
readonly=True, related='pricelist_id.currency_id', store=True)
active = fields.Boolean(
readonly=True, related="pricelist_id.active", 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'),
('fixed', 'Fixed Price'),
('percentage', 'Percentage (discount)'),
('formula', 'Formula')], index=True, default='fixed')
fixed_price = fields.Float('Fixed Price', digits='Product Price')
('formula', 'Formula')], index=True, default='fixed', required=True)
fixed_price = fields.Monetary('Fixed Price')
percent_price = fields.Float('Percentage Price')
# functional fields used for usability purposes
name = fields.Char(
@@ -464,35 +467,39 @@ class PricelistItem(models.Model):
raise ValidationError(_('The minimum margin should be lower than the maximum margin.'))
return True
@api.depends('categ_id', 'product_tmpl_id', 'product_id', 'compute_price', 'fixed_price', \
@api.constrains('product_id', 'product_tmpl_id', 'categ_id')
def _check_product_consistency(self):
for item in self:
if item.applied_on == "2_product_category" and not item.categ_id:
raise ValidationError(_("Please specify the category for which this rule should be applied"))
elif item.applied_on == "1_product" and not item.product_tmpl_id:
raise ValidationError(_("Please specify the product for which this rule should be applied"))
elif item.applied_on == "0_product_variant" and not item.product_id:
raise ValidationError(_("Please specify the product variant for which this rule should be applied"))
@api.depends('applied_on', 'categ_id', 'product_tmpl_id', 'product_id', 'compute_price', 'fixed_price', \
'pricelist_id', 'percent_price', 'price_discount', 'price_surcharge')
def _get_pricelist_item_name_price(self):
for item in self:
if item.categ_id:
item.name = _("Category: %s") % (item.categ_id.name)
elif item.product_tmpl_id:
item.name = item.product_tmpl_id.name
elif item.product_id:
item.name = item.product_id.display_name.replace('[%s]' % item.product_id.code, '')
if item.categ_id and item.applied_on == '2_product_category':
item.name = _("Category: %s") % (item.categ_id.display_name)
elif item.product_tmpl_id and item.applied_on == '1_product':
item.name = _("Product: %s") % (item.product_tmpl_id.display_name)
elif item.product_id and item.applied_on == '0_product_variant':
item.name = _("Variant: %s") % (item.product_id.with_context(display_default_code=False).display_name)
else:
item.name = _("All Products")
if item.compute_price == 'fixed':
item.price = ("%s %s") % (item.fixed_price, item.pricelist_id.currency_id.name)
if item.currency_id.position == 'after':
item.price = "%s %s" % (item.fixed_price, item.currency_id.symbol)
else:
item.price = "%s %s" % (item.currency_id.symbol, item.fixed_price)
elif item.compute_price == 'percentage':
item.price = _("%s %% discount") % (item.percent_price)
else:
item.price = _("%s %% discount and %s surcharge") % (item.price_discount, item.price_surcharge)
@api.onchange('applied_on')
def _onchange_applied_on(self):
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):
if self.compute_price != 'fixed':
@@ -508,14 +515,65 @@ class PricelistItem(models.Model):
'price_max_margin': 0.0,
})
@api.onchange('product_id')
def _onchange_product_id(self):
has_product_id = self.filtered('product_id')
for item in has_product_id:
item.product_tmpl_id = item.product_id.product_tmpl_id
if self.env.context.get('default_applied_on', False) == '1_product':
# If a product variant is specified, apply on variants instead
# Reset if product variant is removed
has_product_id.update({'applied_on': '0_product_variant'})
(self - has_product_id).update({'applied_on': '1_product'})
@api.onchange('product_tmpl_id')
def _onchange_product_tmpl_id(self):
has_tmpl_id = self.filtered('product_tmpl_id')
for item in has_tmpl_id:
if item.product_id and item.product_id.product_tmpl_id != item.product_tmpl_id:
item.product_id = None
@api.onchange('product_id', 'product_tmpl_id', 'categ_id')
def _onchane_rule_content(self):
if not self.user_has_groups('product.group_sale_pricelist') and not self.env.context.get('default_applied_on', False):
# If advanced pricelists are disabled (applied_on field is not visible)
# AND we aren't coming from a specific product template/variant.
variants_rules = self.filtered('product_id')
template_rules = (self-variants_rules).filtered('product_tmpl_id')
variants_rules.update({'applied_on': '0_product_variant'})
template_rules.update({'applied_on': '1_product'})
(self-variants_rules-template_rules).update({'applied_on': '3_global'})
@api.model_create_multi
def create(self, vals_list):
for values in vals_list:
if values.get('applied_on', False):
# Ensure item consistency for later searches.
applied_on = values['applied_on']
if applied_on == '3_global':
values.update(dict(product_id=None, product_tmpl_id=None, categ_id=None))
elif applied_on == '2_product_category':
values.update(dict(product_id=None, product_tmpl_id=None))
elif applied_on == '1_product':
values.update(dict(product_id=None, categ_id=None))
elif applied_on == '0_product_variant':
values.update(dict(categ_id=None))
return super(PricelistItem, self).create(vals_list)
def write(self, values):
if values.get('applied_on', False):
# Ensure item consistency for later searches.
applied_on = values['applied_on']
if applied_on == '3_global':
values.update(dict(product_id=None, product_tmpl_id=None, categ_id=None))
elif applied_on == '2_product_category':
values.update(dict(product_id=None, product_tmpl_id=None))
elif applied_on == '1_product':
values.update(dict(product_id=None, categ_id=None))
elif applied_on == '0_product_variant':
values.update(dict(categ_id=None))
res = super(PricelistItem, self).write(values)
# When the pricelist changes we need the product.template price
# to be invalided and recomputed.
self.invalidate_cache()
return res
@api.onchange('product_tmpl_id')
def _onchange_product_tmpl_id(self):
if self.user_has_groups('product.group_product_variant'):
self.product_id = False
+27 -1
View File
@@ -148,11 +148,17 @@ class ProductTemplate(models.Model):
'Internal Reference', compute='_compute_default_code',
inverse='_set_default_code', store=True)
item_ids = fields.One2many('product.pricelist.item', 'product_tmpl_id', 'Pricelist Items')
pricelist_item_count = fields.Integer("Number of price rules", compute="_compute_item_count")
can_image_1024_be_zoomed = fields.Boolean("Can Image 1024 be zoomed", compute='_compute_can_image_1024_be_zoomed', store=True)
has_configurable_attributes = fields.Boolean("Is a configurable product", compute='_compute_has_configurable_attributes', store=True)
def _compute_item_count(self):
for template in self:
# Pricelist item count counts the rules applicable on current template or on its variants.
template.pricelist_item_count = template.env['product.pricelist.item'].search_count([
'|', ('product_tmpl_id', '=', template.id), ('product_id', 'in', template.product_variant_ids.ids)])
@api.depends('image_1920', 'image_1024')
def _compute_can_image_1024_be_zoomed(self):
for template in self:
@@ -454,6 +460,26 @@ class ProductTemplate(models.Model):
'', args=[('id', 'in', list(searched_ids))],
operator='ilike', limit=limit, name_get_uid=name_get_uid)
def open_pricelist_rules(self):
self.ensure_one()
domain = ['|',
('product_tmpl_id', '=', self.id),
('product_id', 'in', self.product_variant_ids.ids)]
return {
'name': _('Price Rules'),
'view_mode': 'tree,form',
'views': [(self.env.ref('product.product_pricelist_item_tree_view_from_product').id, 'tree'), (False, 'form')],
'res_model': 'product.pricelist.item',
'type': 'ir.actions.act_window',
'target': 'current',
'domain': domain,
'context': {
'default_product_tmpl_id': self.id,
'default_applied_on': '1_product',
'product_without_variants': self.product_variant_count == 1,
},
}
def price_compute(self, price_type, uom=False, currency=False, company=False):
# TDE FIXME: delegate to template or not ? fields are reencoded here ...
# compatibility about context keys used a bit everywhere in the code
+25 -5
View File
@@ -14,14 +14,17 @@ class ResConfigSettings(models.TransientModel):
module_sale_product_matrix = fields.Boolean("Product Grid Configurator")
group_stock_packaging = fields.Boolean('Product Packagings',
implied_group='product.group_stock_packaging')
group_sale_pricelist = fields.Boolean("Use pricelists to adapt your price per customers",
group_product_pricelist = fields.Boolean("Pricelists",
implied_group='product.group_product_pricelist')
group_sale_pricelist = fields.Boolean("Advanced Pricelists",
implied_group='product.group_sale_pricelist',
help="""Allows to manage different prices based on rules per category of customers.
Example: 10% for retailers, promotion of 5 EUR on this product, etc.""")
group_product_pricelist = fields.Boolean("Show pricelists On Products",
implied_group='product.group_product_pricelist')
group_pricelist_item = fields.Boolean("Show pricelists to customers",
implied_group='product.group_pricelist_item')
product_pricelist_setting = fields.Selection([
('basic', 'Multiple prices per product'),
('advanced', 'Advanced price rules (discounts, formulas)')
], default='basic', string="Pricelists Method", config_parameter='product.product_pricelist_setting',
help="Multiple prices: Pricelists with fixed price rules by product,\nAdvanced rules: enables advanced price rules for pricelists.")
product_weight_in_lbs = fields.Selection([
('0', 'Kilogram'),
('1', 'Pound'),
@@ -47,6 +50,23 @@ class ResConfigSettings(models.TransientModel):
if self.module_sale_product_configurator and not self.group_product_variant:
self.group_product_variant = True
@api.onchange('group_multi_currency')
def _onchange_group_multi_currency(self):
if self.group_product_pricelist and not self.group_sale_pricelist and self.group_multi_currency:
self.group_sale_pricelist = True
@api.onchange('group_product_pricelist')
def _onchange_group_sale_pricelist(self):
if not self.group_product_pricelist and self.group_sale_pricelist:
self.group_sale_pricelist = False
@api.onchange('product_pricelist_setting')
def _onchange_product_pricelist_setting(self):
if self.product_pricelist_setting == 'basic':
self.group_sale_pricelist = False
else:
self.group_sale_pricelist = True
def set_values(self):
super(ResConfigSettings, self).set_values()
if not self.group_discount_per_so_line:
@@ -7,11 +7,6 @@
<field name="category_id" ref="base.module_category_hidden"/>
</record>
<record id="group_pricelist_item" model="res.groups">
<field name="name">Manage Pricelist Items</field>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
<record id="group_product_pricelist" model="res.groups">
<field name="name">Pricelists On Product</field>
<field name="category_id" ref="base.module_category_hidden"/>
@@ -33,14 +33,14 @@ class TestProductPricelist(TransactionCase):
}), (0, 0, {
'name': '10% Discount on Assemble Computer',
'applied_on': '1_product',
'product_id': self.ipad_retina_display.id,
'product_tmpl_id': self.ipad_retina_display.product_tmpl_id.id,
'compute_price': 'formula',
'base': 'list_price',
'price_discount': 10
}), (0, 0, {
'name': '1 surchange on Laptop',
'applied_on': '1_product',
'product_id': self.laptop_E5023.id,
'product_tmpl_id': self.laptop_E5023.product_tmpl_id.id,
'compute_price': 'formula',
'base': 'list_price',
'price_surcharge': 1
@@ -54,7 +54,7 @@ class TestProductPricelist(TransactionCase):
'price_discount': 5
}), (0, 0, {
'name': '30% Discount on all products',
'applied_on': '0_product_variant',
'applied_on': '3_global',
'date_start': '2011-12-27',
'date_end': '2011-12-31',
'compute_price': 'formula',
+211 -98
View File
@@ -2,18 +2,87 @@
<odoo>
<data>
<record id="product_pricelist_item_tree_view" model="ir.ui.view">
<field name="name">product.pricelist.item.tree</field>
<record model="ir.ui.view" id="product_pricelist_item_view_search">
<field name="name">product.pricelist.item.search</field>
<field name="model">product.pricelist.item</field>
<field name="arch" type="xml">
<tree string="Pricelist Items">
<field colspan="4" name="name"/>
<field name="product_id" groups="product.group_product_variant"/>
<field name="product_tmpl_id"/>
<field name="categ_id"/>
<field name="min_quantity"/>
<field name="date_start"/>
<field name="date_end"/>
<search string="Products Price Rules Search">
<filter name="Product Rule" domain="[('applied_on', '=', '1_product')]"/>
<filter name="Variant Rule" domain="[('applied_on', '=', '0_product_variant')]" groups="product.group_product_variant"/>
<separator/>
<field name="pricelist_id"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="currency_id" groups="base.group_multi_currency"/>
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
<group expand="0" string="Group By">
<filter string="Product" name="groupby_product" domain="[]" context="{'group_by': 'product_tmpl_id'}"/>
<filter string="Variant"
name="groupby_product_variant"
domain="[('applied_on', '=', '0_product_variant')]"
context="{'group_by': 'product_tmpl_id'}"
groups="product.group_product_variant"/>
<filter string="Pricelist"
name="groupby_vendor"
domain="[]"
context="{'group_by': 'pricelist_id'}"
groups="product.group_sale_pricelist"/>
</group>
</search>
</field>
</record>
<record id="product_pricelist_item_tree_view" model="ir.ui.view">
<field name="name">product.pricelist.item.tree</field>
<field name="model">product.pricelist.item</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<tree string="Price Rules">
<field name="pricelist_id"/>
<field name="name" string="Applied On"/>
<field name="price"/>
<field name="min_quantity" colspan="4"/>
<field name="date_start" optional="hide"/>
<field name="date_end" optional="hide"/>
<field name="company_id" groups="base.group_multi_company" optional="show"/>
</tree>
</field>
</record>
<record id="product_pricelist_item_tree_view_from_product" model="ir.ui.view">
<!-- Access and edit price rules from a given product/product variant -->
<field name="name">product.pricelist.item.tree</field>
<field name="model">product.pricelist.item</field>
<field name="priority">100</field>
<field name="arch" type="xml">
<tree string="Pricelist Items" editable="bottom">
<!-- Scope = coming from a product/product template -->
<field name="pricelist_id" string="Pricelist" options="{'no_create_edit':1, 'no_open': 1}"/>
<field name="name" string="Applied On"/>
<field name="categ_id" invisible="1"/>
<field name="product_tmpl_id"
invisible="context.get('active_model')!='product.category'"
attrs="{'required': [('applied_on', '=', '1_product')]}"
domain="[('categ_id', '=', context.get('default_categ_id', True))]"
options="{'no_create_edit':1, 'no_open': 1}"/>
<field name="product_id"
groups="product.group_product_variant"
invisible="context.get('product_without_variants', False)"
readonly="context.get('active_model')=='product.product'"
attrs="{'required': [('applied_on', '=', '0_product_variant')]}"
domain="['|', '|',
('id', '=', context.get('default_product_id', 0)),
('product_tmpl_id', '=', context.get('default_product_tmpl_id', 0)),
('categ_id', '=', context.get('default_categ_id', 0)),
]"
options="{'no_create_edit':1, 'no_open': 1}"
/>
<field name="min_quantity" colspan="4"/>
<field name="currency_id" invisible="1"/>
<field name="fixed_price" string="Price" required='1'/>
<field name="date_start" optional="show"/>
<field name="date_end" optional="show"/>
<field name="applied_on" invisible="1"/>
<field name="company_id" groups="base.group_multi_company" optional="show" options="{'no_create_edit':1, 'no_open': 1}"/>
</tree>
</field>
</record>
@@ -23,61 +92,79 @@
<field name="model">product.pricelist.item</field>
<field name="arch" type="xml">
<form string="Pricelist Items">
<h1><field name="name"/></h1>
<group>
<group>
<field name="applied_on" widget="radio"/>
<field name="categ_id" attrs="{'invisible':[('applied_on', '!=', '2_product_category')], 'required':[('applied_on', '=', '2_product_category')]}"/>
<field name="product_tmpl_id" attrs="{'invisible':[('applied_on', '!=', '1_product')],'required':[('applied_on', '=', '1_product')]}" string="Product"/>
<field name="product_id" attrs="{'invisible':[('applied_on', '!=', '0_product_variant')],'required':[('applied_on', '=', '0_product_variant')]}" string="Product Variant"/>
</group>
<group>
<field name="min_quantity"/>
<field name="date_start"/>
<field name="date_end"/>
</group>
</group>
<separator string="Price Computation"/>
<group>
<group>
<label for="compute_price" string="Compute Price"/>
<div>
<field name="compute_price" widget="radio"/>
<field name="currency_id" invisible="1"/>
<div attrs="{'invisible':[('compute_price', '!=', 'fixed')]}">
<field name="fixed_price" nolabel= "1" widget='monetary' options="{'currency_field': 'currency_id'}"/></div>
<div attrs="{'invisible':[('compute_price', '!=', 'percentage')]}">
<field name="percent_price" nolabel="1" class="oe_inline"/>%%
</div>
</div>
</group>
</group>
<div class="oe_grey" groups="uom.group_uom">
<p>The computed price is expressed in the default Unit of Measure of the product.</p>
</div>
<group col="6" attrs="{'invisible':[('compute_price', '!=', 'formula')]}">
<field name="base" colspan="6"/>
<sheet>
<h1><field name="name"/></h1>
<group>
<group name="pricelist_rule_target">
<field name="applied_on" widget="radio"/>
<field name="categ_id" attrs="{
'invisible':[('applied_on', '!=', '2_product_category')],
'required':[('applied_on', '=', '2_product_category')]}"
options="{'no_create':1}"/>
<field name="product_tmpl_id" attrs="{
'invisible':[('applied_on', '!=', '1_product')],
'required':[('applied_on', '=', '1_product')]}"
options="{'no_create':1}"/>
<field name="product_id" attrs="{
'invisible':[('applied_on', '!=', '0_product_variant')],
'required':[('applied_on', '=', '0_product_variant')]}"
options="{'no_create':1}"/>
</group>
<group name="pricelist_rule_limits">
<field name="min_quantity"/>
<field name="date_start"/>
<field name="date_end"/>
</group>
<group name="pricelist_rule_related" groups="base.group_no_one">
<!-- Infos from the pricelist for UI rendering (monetary fields, ...) -->
<field name="pricelist_id" invisible="1"/>
<field name="currency_id" groups="base.group_multi_currency"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
<group string="Price Computation" name="pricelist_rule_computation" groups="product.group_sale_pricelist">
<group name="pricelist_rule_method">
<field name="compute_price" string="Compute Price" widget="radio"/>
</group>
<group name="pricelist_rule_base">
<field name="fixed_price" attrs="{'invisible':[('compute_price', '!=', 'fixed')]}"/>
<label for="percent_price" attrs="{'invisible':[('compute_price', '!=', 'percentage')]}"/>
<div attrs="{'invisible':[('compute_price', '!=', 'percentage')]}">
<field name="percent_price"
class="oe_inline"
attrs="{'invisible':[('compute_price', '!=', 'percentage')]}"/>
%%
</div>
<field name="base" attrs="{'invisible':[('compute_price', '!=', 'formula')]}"/>
<field name="base_pricelist_id" attrs="{
'invisible': ['|', ('compute_price', '!=', 'formula'), ('base', '!=', 'pricelist')],
'required': [('compute_price', '=', 'formula'), ('base', '=', 'pricelist')],
'readonly': [('base', '!=', 'pricelist')]}"/>
</group>
</group>
<div class="oe_grey" groups="uom.group_uom">
<p>The computed price is expressed in the default Unit of Measure of the product.</p>
</div>
<group name="pricelist_rule_advanced" col="6" attrs="{'invisible':[('compute_price', '!=', 'formula')]}" groups="product.group_sale_pricelist">
<label for="" string="New Price ="/>
<div>
<span attrs="{'invisible':[('base', '!=', 'list_price')]}">Sales Price - </span>
<span attrs="{'invisible':[('base', '!=', 'standard_price')]}">Cost - </span>
<span attrs="{'invisible':[('base', '!=', 'pricelist')]}">Other Pricelist - </span>
</div>
<label for="price_discount"/>
<div class="o_row">
<field name="price_discount"/>
<span>%%</span>
</div>
<label string=" + " for="price_surcharge"/>
<field name="price_surcharge" nolabel="1"/>
<label for="" string="New Price ="/>
<div>
<span attrs="{'invisible':[('base', '!=', 'list_price')]}">Sales Price - </span>
<span attrs="{'invisible':[('base', '!=', 'standard_price')]}">Cost - </span>
<span attrs="{'invisible':[('base', '!=', 'pricelist')]}">Other Pricelist - </span>
</div>
<label for="price_discount"/>
<div class="o_row">
<field name="price_discount"/>
<span>%%</span>
</div>
<label string=" + " for="price_surcharge"/>
<field name="price_surcharge" nolabel="1"/>
<field name="price_round" string="Rounding Method"/>
<field name="price_min_margin" string="Min. Margin"/>
<field name="price_max_margin" string="Max. Margin"/>
<field name="base_pricelist_id" attrs="{'invisible':[('base', '!=', 'pricelist')],'required': [('base','=', 'pricelist')], 'readonly': [('base','!=', 'pricelist')]}"/>
</group>
<field name="price_round" string="Rounding Method"/>
<field name="price_min_margin" string="Min. Margin"/>
<field name="price_max_margin" string="Max. Margin"/>
</group>
</sheet>
</form>
</field>
</record>
@@ -142,28 +229,44 @@
<h1><field name="name" placeholder="e.g. USD Retailers"/></h1>
</div>
<group>
<field name="discount_policy" groups="product.group_discount_per_so_line" widget="radio"/>
<field name="currency_id" groups="base.group_multi_currency"/>
<field name="active" invisible="1"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
<field name="country_group_ids"/>
<group name="pricelist_settings">
<field name="currency_id" groups="base.group_multi_currency"/>
<field name="active" invisible="1"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
</group>
</group>
<div>
<separator string="Pricelist Items"/>
<field name="item_ids" nolabel="1" context="{'default_base':'list_price'}" groups="product.group_pricelist_item">
<tree string="Pricelist Items">
<field name="name" string="Applicable On"/>
<field name="min_quantity"/>
<field name="date_start"/>
<field name="date_end"/>
<field name="price" string="Price"/>
<field name="base" invisible="1"/>
<field name="price_discount" invisible="1"/>
<field name="applied_on" invisible="1"/>
<field name="compute_price" invisible="1"/>
</tree>
</field>
</div>
<notebook>
<page name="pricelist_rules" string="Price Rules">
<field name="item_ids" nolabel="1" context="{'default_base':'list_price'}">
<tree string="Pricelist Items" editable="bottom">
<field name="product_tmpl_id" string="Products" required="1"/>
<field name="product_id" string="Variants"
groups="product.group_product_variant"
domain="[('product_tmpl_id', '=', product_tmpl_id)]"
options="{'no_create':1}"/>
<field name="min_quantity"/>
<field name="fixed_price" string="Price"/>
<field name="currency_id" invisible="1"/>
<field name="pricelist_id" invisible="1"/>
<!-- Pricelist ID is here only for related fields to be correctly computed -->
<field name="date_start"/>
<field name="date_end"/>
<field name="base" invisible="1"/>
<field name="applied_on" invisible="1"/>
</tree>
</field>
</page>
<page name="pricelist_config" string="Configuration">
<group>
<group name="pricelist_availability" string="Availability">
<field name="country_group_ids" widget="many2many_tags"/>
</group>
<group name="pricelist_discounts" groups="product.group_discount_per_so_line" string="Discounts">
<field name="discount_policy" widget="radio"/>
</group>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
@@ -173,21 +276,25 @@
<field name="name">product.pricelist.form.inherit</field>
<field name="model">product.pricelist</field>
<field name="inherit_id" ref="product.product_pricelist_view"/>
<field name="groups_id" eval="[(4, ref('product.group_product_pricelist'))]"/>
<field name="groups_id" eval="[(4, ref('product.group_sale_pricelist'))]"/>
<field name="arch" type="xml">
<!-- When in advanced pricelist mode : pricelist rules
Should open in a form view and not be editable inline anymore.
-->
<field name="item_ids" position="replace">
<field name="item_ids" nolabel="1" context="{'default_base':'list_price'}">
<tree string="Pricelist Items" editable="bottom">
<field name="product_tmpl_id" string="Products" required="1"/>
<field name="product_id" string="Variants" groups="product.group_product_variant" domain="[('product_tmpl_id', '=', product_tmpl_id)]"/>
<field name="fixed_price" string="Price" widget="monetary"/>
<field name="min_quantity"/>
<field name="date_start"/>
<field name="date_end"/>
<field name="base" invisible="1"/>
<field name="applied_on" invisible="1"/>
</tree>
</field>
<field name="item_ids" nolabel="1" context="{'default_base':'list_price'}" groups="product.group_product_pricelist">
<tree string="Pricelist Items">
<field name="name" string="Applicable On"/>
<field name="min_quantity"/>
<field name="price" string="Price"/>
<field name="date_start"/>
<field name="date_end"/>
<field name="base" invisible="1"/>
<field name="price_discount" invisible="1"/>
<field name="applied_on" invisible="1"/>
<field name="compute_price" invisible="1"/>
</tree>
</field>
</field>
</field>
</record>
@@ -221,5 +328,11 @@
</field>
</record>
<record id="product_pricelist_item_action" model="ir.actions.act_window">
<field name="name">Price Rules</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">product.pricelist.item</field>
<field name="view_mode">tree,form</field>
</record>
</data>
</odoo>
+17 -83
View File
@@ -45,7 +45,20 @@
<field name="categ_id" string="Product Category"/>
</group>
<group name="group_standard_price">
<field name="list_price" string="Sales Price" widget='monetary' options="{'currency_field': 'currency_id', 'field_digits': True}"/>
<label for="list_price"/>
<div name="pricing">
<field name="list_price" string="Sales Price" widget='monetary' options="{'currency_field': 'currency_id', 'field_digits': True}"/>
<button name="open_pricelist_rules" icon="fa-arrow-right" type="object"
groups="product.group_product_pricelist" class="oe_inline">
<field name="pricelist_item_count" attrs="{'invisible': [('pricelist_item_count', '=', 0)]}"/>
<span attrs="{'invisible': [('pricelist_item_count', '=', 1)]}">
Extra Prices
</span>
<span attrs="{'invisible': [('pricelist_item_count', '!=', 1)]}">
Extra Price
</span>
</button>
</div>
<label for="standard_price" groups="base.group_user" attrs="{'invisible': [('product_variant_count', '&gt;', 1), ('is_product_variant', '=', False)]}"/>
<div name="standard_price_uom" groups="base.group_user" attrs="{'invisible': [('product_variant_count', '&gt;', 1), ('is_product_variant', '=', False)]}" class="o_row">
<field name="standard_price" widget='monetary' options="{'currency_field': 'cost_currency_id'}"/>
@@ -68,71 +81,6 @@
</page>
<page string="Sales" attrs="{'invisible':[('sale_ok','=',False)]}" name="sales" invisible="1">
<group name="sale">
<group groups="product.group_product_pricelist">
<div name="pricelist_item" groups="product.group_product_pricelist">
<separator string="Pricing"/>
<field name="item_ids" nolabel="1" mode="tree,kanban" context="{'default_base':'list_price', 'default_applied_on' :'1_product'}">
<tree string="Pricelist Items" editable="bottom">
<field name="pricelist_id" string="Pricelist" required='1'/>
<field name="currency_id" invisible="1"/>
<field name="fixed_price" string="Price" required='1' widget='monetary' options="{'currency_field': 'currency_id'}"/>
<field name="min_quantity"/>
<field name="date_start"/>
<field name="date_end"/>
<field name="applied_on" invisible="1"/>
</tree>
<kanban string="Pricelist Items" class="o_kanban_mobile">
<field name="pricelist_id"/>
<field name="currency_id"/>
<field name="fixed_price"/>
<field name="min_quantity"/>
<field name="date_start"/>
<field name="date_end"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_global_click">
<div class="row">
<div class="col-12">
<strong>
<field name='pricelist_id'/>: <field name="fixed_price" widget='monetary' options="{'currency_field': 'currency_id'}"/>
</strong>
</div>
</div>
<div class="row">
<div class="col-12">
<span><t t-esc='record.min_quantity.string'/>: <t t-esc='record.min_quantity.value'/></span>
</div>
</div>
<span t-if='record.date_start.value || record.date_end.value'>
<div class="row">
<div class="col-12">
Valid <span t-if='record.date_start.value'> from <t t-esc='record.date_start.value'/></span><span t-if='record.date_end.value'> until <t t-esc='record.date_end.value'/></span>
</div>
</div>
</span>
</div>
</t>
</templates>
</kanban>
<form string="Pricelist Items">
<h1><field name="name"/></h1>
<group>
<group>
<field name="applied_on" invisible="1"/>
<field name="pricelist_id" string="Pricelist" required='1'/>
<field name="currency_id" invisible="1"/>
<field name="fixed_price" string="Price" required='1' widget='monetary' options="{'currency_field': 'currency_id'}"/>
</group>
<group>
<field name="min_quantity"/>
<field name="date_start"/>
<field name="date_end"/>
</group>
</group>
</form>
</field>
</div>
</group>
<group name="email_template_and_project" invisible="1"/>
</group>
<group string="Sales Description" name="description">
@@ -383,29 +331,15 @@
<attribute name="name">lst_price</attribute>
<attribute name="attrs">{'readonly': [('product_variant_count', '&gt;', 1)]}</attribute>
</field>
<xpath expr="//label[@for='list_price']" position="replace">
<label for="lst_price"/>
</xpath>
<group name="packaging" position="attributes">
<attribute name="attrs">{'invisible': 0}</attribute>
</group>
<field name="name" position="after">
<field name="product_tmpl_id" class="oe_inline" readonly="1" invisible="1" attrs="{'required': [('id', '!=', False)]}"/>
</field>
<field name="item_ids" position="inside">
<field name="pricelist_item_ids" widget="many2many">
<tree string="Pricelist Items">
<field name="pricelist_id"/>
<field name="name" string="Applicable On"/>
<field name="min_quantity"/>
<field name="date_start"/>
<field name="date_end"/>
<field name="price" string="Price"/>
<field name="base" invisible="1"/>
<field name="sequence" invisible="1"/>
<field name="price_discount" invisible="1"/>
<field name="applied_on" invisible="1"/>
<field name="compute_price" invisible="1"/>
</tree>
</field>
</field>
<xpath expr="//div[hasclass('oe_title')]" position="inside">
<field name="attribute_value_ids" widget="many2many_tags" readonly="1"
groups="product.group_product_variant"/>
+1 -2
View File
@@ -109,8 +109,7 @@ class ProductTemplate(models.Model):
def get_import_templates(self):
res = super(ProductTemplate, self).get_import_templates()
if self.env.context.get('sale_multi_pricelist_product_template'):
sale_pricelist_setting = self.env['ir.config_parameter'].sudo().get_param('sale.sale_pricelist_setting')
if sale_pricelist_setting == 'percentage':
if self.user_has_groups('product.group_sale_pricelist'):
return [{
'label': _('Import Template for Products'),
'template': '/product/static/xls/product_template.xls'
-51
View File
@@ -15,16 +15,6 @@ class ResConfigSettings(models.TransientModel):
portal_confirmation_sign = fields.Boolean(related='company_id.portal_confirmation_sign', string='Online Signature', readonly=False)
portal_confirmation_pay = fields.Boolean(related='company_id.portal_confirmation_pay', string='Online Payment', readonly=False)
group_sale_delivery_address = fields.Boolean("Customer Addresses", implied_group='sale.group_delivery_invoice_address')
multi_sales_price = fields.Boolean("Multiple Sales Prices per Product")
multi_sales_price_method = fields.Selection([
('percentage', 'Multiple prices per product (e.g. customer segments, currencies)'),
('formula', 'Prices computed from formulas (discounts, margins, roundings)')
], default='percentage', string="Pricelists Method")
sale_pricelist_setting = fields.Selection([
('fixed', 'A single sales price per product'),
('percentage', 'Multiple prices per product (e.g. customer segments, currencies)'),
('formula', 'Price computed from formulas (discounts, margins, roundings)')
], string="Pricelists", config_parameter='sale.sale_pricelist_setting')
group_proforma_sales = fields.Boolean(string="Pro-Forma Invoice", implied_group='sale.group_proforma_sales',
help="Allows you to send pro-forma invoice.")
group_sale_order_dates = fields.Boolean("Delivery Date", implied_group='sale.group_sale_order_dates')
@@ -80,35 +70,6 @@ class ResConfigSettings(models.TransientModel):
if self.default_invoice_policy != 'order':
self.env['ir.config_parameter'].set_param('sale.automatic_invoice', False)
@api.onchange('multi_sales_price', 'multi_sales_price_method')
def _onchange_sale_price(self):
if self.multi_sales_price and not self.multi_sales_price_method:
self.update({
'multi_sales_price_method': 'percentage',
})
self.sale_pricelist_setting = self.multi_sales_price and self.multi_sales_price_method or 'fixed'
@api.onchange('sale_pricelist_setting')
def _onchange_sale_pricelist_setting(self):
if self.sale_pricelist_setting == 'percentage':
self.update({
'group_product_pricelist': True,
'group_sale_pricelist': True,
'group_pricelist_item': False,
})
elif self.sale_pricelist_setting == 'formula':
self.update({
'group_product_pricelist': False,
'group_sale_pricelist': True,
'group_pricelist_item': True,
})
else:
self.update({
'group_product_pricelist': False,
'group_sale_pricelist': False,
'group_pricelist_item': False,
})
@api.onchange('use_quotation_validity_days')
def _onchange_use_quotation_validity_days(self):
if self.quotation_validity_days <= 0:
@@ -121,15 +82,3 @@ class ResConfigSettings(models.TransientModel):
return {
'warning': {'title': "Warning", 'message': "Quotation Validity is required and must be greater than 0."},
}
@api.model
def get_values(self):
res = super(ResConfigSettings, self).get_values()
ICPSudo = self.env['ir.config_parameter'].sudo()
sale_pricelist_setting = ICPSudo.get_param('sale.sale_pricelist_setting')
res.update(
multi_sales_price=sale_pricelist_setting in ['percentage', 'formula'],
multi_sales_price_method=sale_pricelist_setting in ['percentage', 'formula'] and sale_pricelist_setting or False,
sale_pricelist_setting=sale_pricelist_setting,
)
return res
+6 -9
View File
@@ -137,15 +137,12 @@ class TestSaleOrder(TestCommonSaleNoChart):
line._onchange_discount()
# Check that pricelist of the SO has been applied on the sale order lines or not
for line in self.sale_order.order_line:
if self.sale_order.pricelist_id in line.product_id.item_ids.mapped('pricelist_id'): # concerned by pricelist (all except product_deliver)
if line.product_id == self.product_order:
self.assertEquals(line.price_unit, self.pricelist_discount_incl_item3.fixed_price, 'Price of product_order should be %s applied on the order line' % (self.pricelist_discount_incl_item3.fixed_price,))
else: # only services (service_order and service_deliver)
for item in self.sale_order.pricelist_id.item_ids.filtered(lambda l: l.product_tmpl_id == line.product_id.product_tmpl_id):
price = item.percent_price
self.assertEquals(price, (line.product_id.list_price - line.price_unit) / line.product_id.list_price * 100, 'Pricelist of the SO should be applied on an order line %s' % (line.product_id.name,))
else:
self.assertEquals(line.price_unit, line.product_id.list_price, 'Pricelist of the SO should not be applied on an order line')
if line.product_id == self.product_order:
self.assertEquals(line.price_unit, self.pricelist_discount_incl_item3.fixed_price, 'Price of product_order should be %s applied on the order line' % (self.pricelist_discount_incl_item3.fixed_price,))
else: # only services (service_order and service_deliver)
for item in self.sale_order.pricelist_id.item_ids.filtered(lambda l: l.product_tmpl_id == line.product_id.product_tmpl_id):
price = item.percent_price
self.assertEquals(price, (line.product_id.list_price - line.price_unit) / line.product_id.list_price * 100, 'Pricelist of the SO should be applied on an order line %s' % (line.product_id.name,))
def test_sale_with_pricelist_discount_excluded(self):
""" Test SO with the pricelist 'discount displayed' and check discount and unit price appeared on its lines """
@@ -112,25 +112,21 @@
</div>
</div>
</div>
<div class="col-12 col-lg-6 o_setting_box" title="There are two ways to manage pricelists: 1) Multiple prices per product: must be set in the Sales tab of the product detail form. 2) Price computed from formulas: must be set in the pricelist form.">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="multi_sales_price"/>
<field name="group_product_pricelist"/>
</div>
<div class="o_setting_right_pane">
<label for="multi_sales_price"/>
<label for="group_product_pricelist"/>
<div class="text-muted">
Set multiple prices per product, automated discounts, etc.
</div>
<div class="content-group" attrs="{'invisible': [('multi_sales_price' ,'=', False)]}">
<div class="content-group" attrs="{'invisible': [('group_product_pricelist' ,'=', False)]}">
<div class="mt16">
<field name="group_product_pricelist" invisible="1"/>
<field name="group_sale_pricelist" invisible="1"/>
<field name="group_pricelist_item" invisible="1"/>
<field name="multi_sales_price_method" widget="radio" class="o_light_label"/>
<field name="sale_pricelist_setting" invisible="1" widget="radio"/>
<field name="product_pricelist_setting" widget="radio" class="o_light_label"/>
</div>
<button name="%(product.product_pricelist_action2)d" icon="fa-arrow-right" type="action" string="Pricelists"
attrs="{'invisible': [('sale_pricelist_setting', '=', False)]}" class="btn-link"/>
<button name="%(product.product_pricelist_action2)d" icon="fa-arrow-right" type="action" string="Pricelists" groups="product.group_product_pricelist" class="btn-link"/>
</div>
</div>
</div>
+2 -2
View File
@@ -91,7 +91,7 @@
<menuitem id="next_id_16" name="Units of Measure" parent="sale.prod_config_main" sequence="6" groups="uom.group_uom" active="False"/>
<menuitem action="uom.product_uom_form_action" id="menu_product_uom_form_action" parent="sale.prod_config_main" sequence="7" groups="uom.group_uom" active="False"/>
<menuitem action="uom.product_uom_categ_form_action" id="menu_product_uom_categ_form_action" parent="sale.prod_config_main" sequence="8" groups="uom.group_uom" active="False"/>
<menuitem id="menu_product_pricelist_main" name="Pricelists" parent="product_menu_catalog" action="product.product_pricelist_action2" groups="product.group_sale_pricelist" sequence="3" active="False"/>
<menuitem id="menu_product_pricelist_main" name="Pricelists" parent="product_menu_catalog" action="product.product_pricelist_action2" groups="product.group_product_pricelist" sequence="3" active="False"/>
<record id="sale_order_view_activity" model="ir.ui.view">
<field name="name">sale.order.activity</field>
@@ -308,7 +308,7 @@
<label for="date_order" string="Order Date"/>
</div>
<field name="date_order" attrs="{'required': [('state', 'in', ['sale', 'done'])], 'invisible': [('state', 'in', ['draft', 'sent'])]}" nolabel="1"/>
<field name="pricelist_id" groups="product.group_sale_pricelist"/>
<field name="pricelist_id" groups="product.group_product_pricelist"/>
<field name="currency_id" invisible="1"/>
<field name="payment_term_id" options="{'no_open':True,'no_create': True}"/>
</group>
@@ -19,7 +19,7 @@ class TestUi(odoo.tests.HttpCase):
self.env.ref('base.user_admin').write({
'groups_id': [
(4, self.env.ref('product.group_product_variant').id),
(4, self.env.ref('product.group_sale_pricelist').id),
(4, self.env.ref('product.group_product_pricelist').id),
],
})
@@ -88,7 +88,7 @@ class TestUi(odoo.tests.HttpCase):
self.env.ref('base.user_admin').write({
'groups_id': [
(4, self.env.ref('product.group_product_variant').id),
(4, self.env.ref('product.group_sale_pricelist').id),
(4, self.env.ref('product.group_product_pricelist').id),
],
})
@@ -131,7 +131,7 @@ class TestUi(odoo.tests.HttpCase):
self.env.ref('account.group_show_line_subtotals_tax_included').users |= admin
# Active pricelist on SO
self.env.ref('product.group_sale_pricelist').users |= admin
self.env.ref('product.group_product_pricelist').users |= admin
# Add a 15% tax on desk
tax = self.env['account.tax'].create({'name': "Test tax", 'amount': 15})
@@ -145,7 +145,7 @@ class TestUi(odoo.tests.HttpCase):
product_template = self.env.ref('product.product_product_4_product_template')
pricelist = self.env.ref('product.list0')
if not pricelist.item_ids.filtered(lambda i: i.product_tmpl_id == product_template and i.price_discount == 20):
if not pricelist.item_ids.filtered(lambda i: i.product_tmpl_id == product_template and i.applied_on == '1_product' and i.price_discount == 20):
self.env['product.pricelist.item'].create({
'base': 'list_price',
'applied_on': '1_product',
@@ -76,5 +76,5 @@ class ResConfigSettings(models.TransientModel):
def _onchange_group_discount_per_so_line(self):
if self.group_discount_per_so_line:
self.update({
'multi_sales_price': True,
'group_product_pricelist': True,
})
+8 -16
View File
@@ -12,17 +12,6 @@
</field>
</record>
<record model="ir.ui.view" id="product_pricelist_view">
<field name="name">product.pricelist.website.form</field>
<field name="model">product.pricelist</field>
<field name="inherit_id" ref="product.product_pricelist_view"/>
<field name="arch" type="xml">
<field name="currency_id" position="before">
<field name="code"/>
</field>
</field>
</record>
<record model="ir.ui.view" id="product_product_website_tree_view">
<field name="name">product.product.website.tree</field>
<field name="model">product.product</field>
@@ -188,11 +177,14 @@
<field name="inherit_id" ref="product.product_pricelist_view" />
<field name="model">product.pricelist</field>
<field name="arch" type="xml">
<field name='currency_id' position='after'>
<field name="selectable"/>
<field name="website_id" options="{'no_create': True}" />
</field>
</field>
<xpath expr="//group[@name='pricelist_availability']" position="after">
<group name="pricelist_website" string="Website">
<field name="website_id" options="{'no_create': True}"/>
<field name="selectable"/>
<field name="code"/>
</group>
</xpath>
</field>
</record>
<record id="website_sale_pricelist_tree_view" model="ir.ui.view">
@@ -102,20 +102,19 @@
</div>
<div class="col-12 col-lg-6 o_setting_box" id="pricelists_setting" title="With the first mode you can set several prices in the product config form (from Sales tab). With the second one, you set prices and computation rules from Pricelists.">
<div class="o_setting_left_pane">
<field name="multi_sales_price"/>
<field name="group_product_pricelist"/>
</div>
<div class="o_setting_right_pane">
<label for="multi_sales_price"/>
<label for="group_product_pricelist"/>
<div class="text-muted">
Apply specific prices per country, discounts, etc.
</div>
<div class="content-group mt16" attrs="{'invisible': [('multi_sales_price', '=', False)]}">
<div class="content-group mt16" attrs="{'invisible': [('group_product_pricelist', '=', False)]}">
<field name="group_sale_pricelist" invisible="1"/>
<field name="group_pricelist_item" invisible="1"/>
<field name="group_product_pricelist" invisible="1"/>
<field name="multi_sales_price_method" class="o_light_label" widget="radio"/>
<field name="product_pricelist_setting" class="o_light_label" widget="radio"/>
</div>
<div attrs="{'invisible': [('multi_sales_price', '=', False)]}">
<div attrs="{'invisible': [('group_product_pricelist', '=', False)]}">
<button type="action" name="%(product.product_pricelist_action2)d" string="Pricelists" class="btn-link" icon="fa-arrow-right"/>
</div>
</div>