[REF] product,repair,(*_)sale(_*): unrequired pricelist

Removes the constraint of using a pricelist and makes all sales flows
rely on the currency of the sale order (or repair order)

task-2735672

closes odoo/odoo#84920

Related: odoo/enterprise#24716
Related: odoo/upgrade#3642
Signed-off-by: Morgane Demesmaeker <edm@odoo.com>
This commit is contained in:
Demesmaeker
2023-01-18 21:08:52 +01:00
parent 50f99e3617
commit 83c52575d0
87 changed files with 520 additions and 573 deletions
+1 -1
View File
@@ -468,7 +468,7 @@ class AccountChartTemplate(models.Model):
# When we install the CoA of first company, set the currency to price types and pricelists
if company.id == 1:
for reference in ['product.list_price', 'product.standard_price', 'product.list0']:
for reference in ['product.list_price', 'product.standard_price']:
try:
tmp2 = self.env.ref(reference).write({'currency_id': self.currency_id.id})
except ValueError:
@@ -16,7 +16,6 @@ class TestDeliveryCost(common.TransactionCase):
self.Product = self.env['product.product']
self.partner_18 = self.env['res.partner'].create({'name': 'My Test Customer'})
self.pricelist = self.env.ref('product.list0')
self.product_4 = self.env['product.product'].create({'name': 'A product to deliver'})
self.product_uom_unit = self.env.ref('uom.product_uom_unit')
self.product_delivery_normal = self.env['product.product'].create({
@@ -48,7 +47,6 @@ class TestDeliveryCost(common.TransactionCase):
self.env.cr.execute(
"UPDATE res_company SET currency_id = %s WHERE id = %s",
[self.env.ref('base.USD').id, self.env.company.id])
self.pricelist.currency_id = self.env.ref('base.USD').id
self.env.user.groups_id |= self.env.ref('uom.group_uom')
def test_00_delivery_cost(self):
@@ -59,7 +57,6 @@ class TestDeliveryCost(common.TransactionCase):
'partner_id': self.partner_18.id,
'partner_invoice_id': self.partner_18.id,
'partner_shipping_id': self.partner_18.id,
'pricelist_id': self.pricelist.id,
'order_line': [(0, 0, {
'name': 'PC Assamble + 2GB RAM',
'product_id': self.product_4.id,
@@ -118,7 +115,6 @@ class TestDeliveryCost(common.TransactionCase):
'partner_id': self.partner_4.id,
'partner_invoice_id': self.partner_address_13.id,
'partner_shipping_id': self.partner_address_13.id,
'pricelist_id': self.pricelist.id,
'order_line': [(0, 0, {
'name': 'Service on demand',
'product_id': self.product_consultant.id,
@@ -276,7 +272,6 @@ class TestDeliveryCost(common.TransactionCase):
self.env.user.groups_id += self.env.ref('product.group_product_pricelist')
order_form = Form(self.env['sale.order'].with_context(tracking_disable=True))
order_form.partner_id = self.partner_18
order_form.pricelist_id = self.pricelist
order_form.fiscal_position_id = fiscal_position
# Try adding delivery product as a normal product
@@ -16,7 +16,6 @@ class StockMoveInvoice(AccountTestInvoicingCommon):
cls.AccountJournal = cls.env['account.journal']
cls.partner_18 = cls.env['res.partner'].create({'name': 'My Test Customer'})
cls.pricelist_id = cls.env.ref('product.list0')
cls.product_11 = cls.env['product.product'].create({'name': 'A product to deliver'})
cls.product_cable_management_box = cls.env['product.product'].create({
'name': 'Another product to deliver',
@@ -44,7 +43,6 @@ class StockMoveInvoice(AccountTestInvoicingCommon):
'partner_id': self.partner_18.id,
'partner_invoice_id': self.partner_18.id,
'partner_shipping_id': self.partner_18.id,
'pricelist_id': self.pricelist_id.id,
'order_line': [(0, 0, {
'name': 'Cable Management Box',
'product_id': self.product_cable_management_box.id,
@@ -113,7 +111,6 @@ class StockMoveInvoice(AccountTestInvoicingCommon):
'partner_id': self.partner_18.id,
'partner_invoice_id': self.partner_18.id,
'partner_shipping_id': self.partner_18.id,
'pricelist_id': self.pricelist_id.id,
'order_line': [(0, 0, {
'name': 'Cable Management Box',
'product_id': self.product_cable_management_box.id,
@@ -30,7 +30,6 @@
<record id="event_0_sale_order_0" model="sale.order">
<field name="user_id" ref="base.user_admin"/>
<field name="partner_id" ref="base.res_partner_address_1"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="date_order" eval="DateTime.now() - relativedelta(days=2)"/>
<field name="state">sale</field>
</record>
@@ -57,7 +56,6 @@
<record id="event_2_sale_order_0" model="sale.order">
<field name="user_id" ref="base.user_admin"/>
<field name="partner_id" ref="base.res_partner_address_2"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="date_order" eval="DateTime.now() - relativedelta(days=0.5)"/>
<field name="state">sale</field>
</record>
@@ -83,7 +81,6 @@
<record id="event_2_sale_order_1" model="sale.order">
<field name="user_id" ref="base.user_admin"/>
<field name="partner_id" ref="base.res_partner_address_3"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="date_order" eval="DateTime.now() - relativedelta(days=1)"/>
<field name="state">sale</field>
</record>
@@ -101,7 +98,6 @@
<record id="event_4_sale_order_0" model="sale.order">
<field name="user_id" ref="base.user_admin"/>
<field name="partner_id" ref="base.res_partner_address_7"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="date_order" eval="DateTime.now() - relativedelta(days=8)"/>
<field name="state">sale</field>
</record>
@@ -118,7 +114,6 @@
<record id="event_4_sale_order_1" model="sale.order">
<field name="user_id" ref="base.user_admin"/>
<field name="partner_id" ref="base.res_partner_address_13"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="date_order" eval="DateTime.now() - relativedelta(days=7)"/>
<field name="state">sale</field>
</record>
@@ -135,7 +130,6 @@
<record id="event_4_sale_order_2" model="sale.order">
<field name="user_id" ref="base.user_admin"/>
<field name="partner_id" ref="base.res_partner_address_14"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="date_order" eval="DateTime.now() - relativedelta(days=7)"/>
<field name="state">sale</field>
</record>
@@ -153,7 +147,6 @@
<record id="event_7_sale_order_0" model="sale.order">
<field name="user_id" ref="base.user_admin"/>
<field name="partner_id" ref="base.res_partner_address_5"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="state">sale</field>
</record>
<record id="event_7_sale_order_0_line_0" model="sale.order.line">
@@ -178,7 +171,6 @@
<record id="event_7_sale_order_1" model="sale.order">
<field name="user_id" ref="base.user_admin"/>
<field name="partner_id" ref="base.res_partner_address_25"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="state">sale</field>
</record>
<record id="event_7_sale_order_1_line_0" model="sale.order.line">
+1 -1
View File
@@ -330,7 +330,7 @@ class TestEventSale(TestEventSaleCommon):
def test_ticket_price_with_pricelist_and_tax(self):
self.env.user.partner_id.country_id = False
pricelist = self.env['product.pricelist'].search([], limit=1)
pricelist = self.env['product.pricelist'].create({'name': 'Base Pricelist'})
tax = self.env['account.tax'].create({
'name': "Tax 10",
+4 -7
View File
@@ -36,9 +36,6 @@ class PosConfig(models.Model):
('journal_id.currency_id', 'in', (False, self.env.company.currency_id.id)),
])
def _default_pricelist(self):
return self.env['product.pricelist'].search([('company_id', 'in', (False, self.env.company.id)), ('currency_id', '=', self.env.company.currency_id.id)], limit=1)
def _get_group_pos_manager(self):
return self.env.ref('point_of_sale.group_pos_manager')
@@ -112,9 +109,9 @@ class PosConfig(models.Model):
pos_session_username = fields.Char(compute='_compute_current_session_user')
pos_session_state = fields.Char(compute='_compute_current_session_user')
pos_session_duration = fields.Char(compute='_compute_current_session_user')
pricelist_id = fields.Many2one('product.pricelist', string='Default Pricelist', required=True, default=_default_pricelist,
help="The pricelist used if no customer is selected or if the customer has no Sale Pricelist configured.")
available_pricelist_ids = fields.Many2many('product.pricelist', string='Available Pricelists', default=_default_pricelist,
pricelist_id = fields.Many2one('product.pricelist', string='Default Pricelist',
help="The pricelist used if no customer is selected or if the customer has no Sale Pricelist configured if any.")
available_pricelist_ids = fields.Many2many('product.pricelist', string='Available Pricelists',
help="Make several pricelists available in the Point of Sale. You can also apply a pricelist to specific customers from their contact form (in Sales tab). To be valid, this pricelist must be listed here as an available pricelist. Otherwise the default pricelist will apply.")
company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.company)
group_pos_manager_id = fields.Many2one('res.groups', string='Point of Sale Manager Group', default=_get_group_pos_manager,
@@ -302,7 +299,7 @@ class PosConfig(models.Model):
@api.constrains('pricelist_id', 'use_pricelist', 'available_pricelist_ids', 'journal_id', 'invoice_journal_id', 'payment_method_ids')
def _check_currencies(self):
for config in self:
if config.use_pricelist and config.pricelist_id not in config.available_pricelist_ids:
if config.use_pricelist and config.pricelist_id and config.pricelist_id not in config.available_pricelist_ids:
raise ValidationError(_("The default pricelist must be included in the available pricelists."))
# Check if the config's payment methods are compatible with its currency
+15 -22
View File
@@ -31,7 +31,7 @@ class PosOrder(models.Model):
taxes = line.tax_ids.filtered(lambda t: t.company_id.id == line.order_id.company_id.id)
taxes = fiscal_position_id.map_tax(taxes)
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
taxes = taxes.compute_all(price, line.order_id.pricelist_id.currency_id, line.qty, product=line.product_id, partner=line.order_id.partner_id or False)['taxes']
taxes = taxes.compute_all(price, line.order_id.currency_id, line.qty, product=line.product_id, partner=line.order_id.partner_id or False)['taxes']
return sum(tax.get('amount', 0.0) for tax in taxes)
@api.model
@@ -46,7 +46,7 @@ class PosOrder(models.Model):
'partner_id': ui_order['partner_id'] or False,
'date_order': ui_order['creation_date'].replace('T', ' ')[:19],
'fiscal_position_id': ui_order['fiscal_position_id'],
'pricelist_id': ui_order['pricelist_id'],
'pricelist_id': ui_order.get('pricelist_id'),
'amount_paid': ui_order['amount_paid'],
'amount_total': ui_order['amount_total'],
'amount_tax': ui_order['amount_tax'],
@@ -167,7 +167,7 @@ class PosOrder(models.Model):
:param draft: Indicate that the pos_order is not validated yet.
:type draft: bool.
"""
prec_acc = order.pricelist_id.currency_id.decimal_places
prec_acc = order.currency_id.decimal_places
order_bank_statement_lines= self.env['pos.payment'].search([('pos_order_id', '=', order.id)])
order_bank_statement_lines.unlink()
@@ -248,7 +248,7 @@ class PosOrder(models.Model):
help="Allows to know if all the total cost of the order lines have already been computed")
lines = fields.One2many('pos.order.line', 'order_id', string='Order Lines', states={'draft': [('readonly', False)]}, readonly=True, copy=True)
company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True)
pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', required=True, states={
pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', states={
'draft': [('readonly', False)]}, readonly=True)
partner_id = fields.Many2one('res.partner', string='Customer', change_default=True, index='btree_not_null', states={'draft': [('readonly', False)], 'paid': [('readonly', False)]})
sequence_number = fields.Integer(string='Sequence Number', help='A session-unique sequence number for the order', default=1)
@@ -367,9 +367,9 @@ class PosOrder(models.Model):
@api.onchange('payment_ids', 'lines')
def _onchange_amount_all(self):
for order in self:
if not order.pricelist_id.currency_id:
if not order.currency_id:
raise UserError(_("You can't: create a pos order from the backend interface, or unset the pricelist, or create a pos.order in a python test with Form tool, or edit the form view in studio if no PoS order exist"))
currency = order.pricelist_id.currency_id
currency = order.currency_id
order.amount_paid = sum(payment.amount for payment in order.payment_ids)
order.amount_return = sum(payment.amount < 0 and payment.amount or 0 for payment in order.payment_ids)
order.amount_tax = currency.round(sum(self._amount_line_tax(line, order.fiscal_position_id) for line in order.lines))
@@ -392,12 +392,11 @@ class PosOrder(models.Model):
amounts[order['order_id'][0]]['taxes'] = order['price_subtotal_incl'] - order['price_subtotal']
for order in self:
currency = order.pricelist_id.currency_id
order.write({
'amount_paid': amounts[order.id]['paid'],
'amount_return': amounts[order.id]['return'],
'amount_tax': currency.round(amounts[order.id]['taxes']),
'amount_total': currency.round(amounts[order.id]['taxed'])
'amount_tax': order.currency_id.round(amounts[order.id]['taxes']),
'amount_total': order.currency_id.round(amounts[order.id]['taxed'])
})
@api.onchange('partner_id')
@@ -605,8 +604,7 @@ class PosOrder(models.Model):
'ref': self.name,
'partner_id': self.partner_id.id,
'partner_bank_id': self._get_partner_bank_id(),
# considering partner's sale pricelist's currency
'currency_id': self.pricelist_id.currency_id.id,
'currency_id': self.currency_id.id,
'invoice_user_id': self.user_id.id,
'invoice_date': invoice_date.astimezone(timezone).date(),
'fiscal_position_id': self.fiscal_position_id.id,
@@ -1169,7 +1167,7 @@ class PosOrderLine(models.Model):
fpos = self.order_id.fiscal_position_id
tax_ids_after_fiscal_position = fpos.map_tax(self.tax_ids)
price = self.price_unit * (1 - (self.discount or 0.0) / 100.0)
taxes = tax_ids_after_fiscal_position.compute_all(price, self.order_id.pricelist_id.currency_id, self.qty, product=self.product_id, partner=self.order_id.partner_id)
taxes = tax_ids_after_fiscal_position.compute_all(price, self.order_id.currency_id, self.qty, product=self.product_id, partner=self.order_id.partner_id)
return {
'price_subtotal_incl': taxes['total_included'],
'price_subtotal': taxes['total_excluded'],
@@ -1179,12 +1177,9 @@ class PosOrderLine(models.Model):
@api.onchange('product_id')
def _onchange_product_id(self):
if self.product_id:
if not self.order_id.pricelist_id:
raise UserError(
_('You have to select a pricelist in the sale form !\n'
'Please set one before choosing a product.'))
price = self.order_id.pricelist_id._get_product_price(
self.product_id, self.qty or 1.0)
self.product_id, self.qty or 1.0, currency=self.currency_id
)
self.tax_ids = self.product_id.taxes_id.filtered(lambda r: not self.company_id or r.company_id == self.company_id)
tax_ids_after_fiscal_position = self.order_id.fiscal_position_id.map_tax(self.tax_ids)
self.price_unit = self.env['account.tax']._fix_tax_included_price_company(price, self.tax_ids, tax_ids_after_fiscal_position, self.company_id)
@@ -1193,12 +1188,10 @@ class PosOrderLine(models.Model):
@api.onchange('qty', 'discount', 'price_unit', 'tax_ids')
def _onchange_qty(self):
if self.product_id:
if not self.order_id.pricelist_id:
raise UserError(_('You have to select a pricelist in the sale form.'))
price = self.price_unit * (1 - (self.discount or 0.0) / 100.0)
self.price_subtotal = self.price_subtotal_incl = price * self.qty
if (self.tax_ids):
taxes = self.tax_ids.compute_all(price, self.order_id.pricelist_id.currency_id, self.qty, product=self.product_id, partner=False)
taxes = self.tax_ids.compute_all(price, self.order_id.currency_id, self.qty, product=self.product_id, partner=False)
self.price_subtotal = taxes['total_excluded']
self.price_subtotal_incl = taxes['total_included']
@@ -1410,8 +1403,8 @@ class ReportSaleDetails(models.AbstractModel):
products_sold = {}
taxes = {}
for order in orders:
if user_currency != order.pricelist_id.currency_id:
total += order.pricelist_id.currency_id._convert(
if user_currency != order.currency_id:
total += order.currency_id._convert(
order.amount_total, user_currency, order.company_id, order.date_order or fields.Date.today())
else:
total += order.amount_total
@@ -24,10 +24,20 @@ class SetPricelistButton extends PosComponent {
const selectionList = this.env.pos.pricelists.map((pricelist) => ({
id: pricelist.id,
label: pricelist.name,
isSelected: pricelist.id === this.currentOrder.pricelist.id,
isSelected: this.currentOrder.pricelist
&& pricelist.id === this.currentOrder.pricelist.id,
item: pricelist,
}));
if (!this.env.pos.default_pricelist) {
selectionList.push({
id: null,
label: this.env._t('Default Price'),
isSelected: !this.currentOrder.pricelist,
item: null,
})
}
const { confirmed, payload: selectedPricelist } = await this.showPopup("SelectionPopup", {
title: this.env._t("Select the pricelist"),
list: selectionList,
@@ -43,7 +53,7 @@ SetPricelistButton.template = "SetPricelistButton";
ProductScreen.addControlButton({
component: SetPricelistButton,
condition: function () {
return this.env.pos.config.use_pricelist && this.env.pos.pricelists.length > 1;
return this.env.pos.config.use_pricelist && this.env.pos.pricelists.length > 0;
},
});
+15 -10
View File
@@ -1470,13 +1470,16 @@ export class Product extends PosModel {
// product.pricelist.item records are loaded with a search_read
// and were automatically sorted based on their _order by the
// ORM. After that they are added in this order to the pricelists.
get_price(pricelist, quantity, price_extra) {
get_price(pricelist, quantity, price_extra, recurring=false) {
var self = this;
var date = moment();
// In case of nested pricelists, it is necessary that all pricelists are made available in
// the POS. Display a basic alert to the user in this case.
if (!pricelist) {
// the POS. Display a basic alert to the user in the case where there is a pricelist item
// but we can't load the base pricelist to get the price when calling this method again.
// As this method is also call without pricelist available in the POS, we can't just check
// the absence of pricelist.
if (recurring && !pricelist) {
alert(
_t(
"An error occurred when loading product prices. " +
@@ -1492,16 +1495,18 @@ export class Product extends PosModel {
category = category.parent;
}
var pricelist_items = _.filter(
self.applicablePricelistItems[pricelist.id],
function (item) {
var pricelist_items = [];
if (pricelist) {
var pricelist_items = _.filter(
self.applicablePricelistItems[pricelist.id],
function (item) {
return (
(!item.categ_id || _.contains(category_ids, item.categ_id[0])) &&
(!item.date_start || moment.utc(item.date_start).isSameOrBefore(date)) &&
(!item.date_end || moment.utc(item.date_end).isSameOrAfter(date))
);
}
);
});
}
var price = self.lst_price;
if (price_extra) {
@@ -1517,7 +1522,7 @@ export class Product extends PosModel {
return pricelist.id === rule.base_pricelist_id[0];
});
if (base_pricelist) {
price = self.get_price(base_pricelist, quantity);
price = self.get_price(base_pricelist, quantity, undefined, true);
}
} else if (rule.base === "standard_price") {
price = self.standard_price;
@@ -2227,7 +2232,7 @@ export class Orderline extends PosModel {
};
}
display_discount_policy() {
return this.order.pricelist.discount_policy;
return (this.order.pricelist ? this.order.pricelist.discount_policy : "with_discount" );
}
compute_fixed_price(price) {
var order = this.order;
+4 -1
View File
@@ -439,7 +439,10 @@ class TestPointOfSaleHttpCommon(AccountTestInvoicingHttpCommon):
'type': 'sale',
'company_id': main_company.id})
all_pricelists = env['product.pricelist'].search([('id', '!=', excluded_pricelist.id)])
all_pricelists = env['product.pricelist'].search([
('id', '!=', excluded_pricelist.id),
'|', ('company_id', '=', main_company.id), ('company_id', '=', False)
])
all_pricelists.write(dict(currency_id=main_company.currency_id.id))
src_tax = env['account.tax'].create({'name': "SRC", 'amount': 10})
@@ -29,8 +29,8 @@ class TestPointOfSale(TransactionCase):
self.env.user.company_id = self.company1
def test_default_pricelist_with_company(self):
""" Verify that the default pricelist belongs to the same company as the config """
def test_no_default_pricelist(self):
""" Verify that the default pricelist isn't automatically set in the config """
company1_pricelist = self.env["product.pricelist"].create({
"name": "company 1 pricelist",
"currency_id": self.currency.id,
@@ -38,26 +38,10 @@ class TestPointOfSale(TransactionCase):
"sequence": 2,
})
# make sure this doesn't pick the company2 pricelist
# make sure this doesn't pick a pricelist as default
new_config = self.env["pos.config"].create({
"name": "usd config"
"name": "usd config", "available_pricelist_ids": [(6, 0, [company1_pricelist.id])]
})
self.assertEqual(new_config.pricelist_id, company1_pricelist,
"POS config incorrectly has pricelist %s" % new_config.pricelist_id.display_name)
def test_default_pricelist_without_company(self):
""" Verify that a default pricelist without a company works """
universal_pricelist = self.env["product.pricelist"].create({
"name": "universal pricelist",
"currency_id": self.currency.id,
"sequence": 2,
})
# make sure this doesn't pick the company2 pricelist
new_config = self.env["pos.config"].create({
"name": "usd config"
})
self.assertEqual(new_config.pricelist_id, universal_pricelist,
self.assertEqual(new_config.pricelist_id, self.env['product.pricelist'],
"POS config incorrectly has pricelist %s" % new_config.pricelist_id.display_name)
@@ -17,7 +17,7 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
def compute_tax(self, product, price, qty=1, taxes=None):
if not taxes:
taxes = product.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id)
currency = self.pos_config.pricelist_id.currency_id
currency = self.pos_config.currency_id
res = taxes.compute_all(price, currency, qty, product=product)
untax = res['total_excluded']
return untax, sum(tax.get('amount', 0.0) for tax in res['taxes'])
@@ -147,7 +147,6 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
[0, 0, {'lot_name': '1002'}],
]
})],
'pricelist_id': 1,
'amount_paid': 12.0,
'amount_total': 12.0,
'amount_tax': 0.0,
@@ -570,7 +569,6 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'amount_total': untax + atax,
'creation_date': fields.Datetime.to_string(fields.Datetime.now()),
'fiscal_position_id': False,
'pricelist_id': self.pos_config.available_pricelist_ids[0].id,
'lines': [[0,
0,
{'discount': 0,
@@ -604,7 +602,6 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'amount_total': untax + atax,
'creation_date': fields.Datetime.to_string(fields.Datetime.now()),
'fiscal_position_id': False,
'pricelist_id': self.pos_config.available_pricelist_ids[0].id,
'lines': [[0,
0,
{'discount': 0,
@@ -638,7 +635,6 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'amount_total': untax + atax,
'creation_date': fields.Datetime.to_string(fields.Datetime.now()),
'fiscal_position_id': False,
'pricelist_id': self.pos_config.available_pricelist_ids[0].id,
'lines': [[0,
0,
{'discount': 0,
@@ -715,7 +711,7 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
})
# make a config that has currency different from the company
eur_pricelist = self.partner1.property_product_pricelist.copy(default={'currency_id': self.env.ref('base.EUR').id})
eur_pricelist = self.env['product.pricelist'].create({'name': 'Test EUR Pricelist', 'currency_id': self.env.ref('base.EUR').id})
sale_journal = self.env['account.journal'].create({
'name': 'PoS Sale EUR',
'type': 'sale',
@@ -923,7 +919,6 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'amount_total': untax + atax,
'creation_date': fields.Datetime.to_string(fields.Datetime.now()),
'fiscal_position_id': False,
'pricelist_id': self.pos_config.available_pricelist_ids[0].id,
'lines': [[0,
0,
{'discount': 0,
@@ -1135,7 +1130,6 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'amount_total': 750,
'creation_date': fields.Datetime.to_string(fields.Datetime.now()),
'fiscal_position_id': False,
'pricelist_id': self.pos_config.available_pricelist_ids[0].id,
'lines': [[0, 0, {
'discount': 0,
'id': 42,
@@ -1195,7 +1189,6 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'amount_total': untax + atax,
'creation_date': fields.Datetime.to_string(fields.Datetime.now()),
'fiscal_position_id': False,
'pricelist_id': self.pos_config.available_pricelist_ids[0].id,
'lines': [(0, 0, {
'discount': 0,
'id': 42,
@@ -1277,7 +1270,6 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'amount_total': 750,
'creation_date': fields.Datetime.to_string(fields.Datetime.now()),
'fiscal_position_id': False,
'pricelist_id': self.pos_config.available_pricelist_ids[0].id,
'lines': [[0, 0, {
'discount': 0,
'id': 42,
@@ -26,7 +26,6 @@ class TestFrontend(odoo.tests.HttpCase):
'printer_ids': [(4, printer.id)],
'iface_start_categ_id': drinks_category.id,
'start_category': True,
'pricelist_id': self.env.ref('product.list0').id,
})
main_floor = self.env['restaurant.floor'].create({
+2 -2
View File
@@ -22,11 +22,11 @@ class PosOrder(models.Model):
values.setdefault('crm_team_id', session.config_id.crm_team_id.id)
return values
@api.depends('pricelist_id.currency_id', 'date_order', 'company_id')
@api.depends('date_order', 'company_id')
def _compute_currency_rate(self):
for order in self:
date_order = order.date_order or fields.Datetime.now()
order.currency_rate = self.env['res.currency']._get_conversion_rate(order.company_id.currency_id, order.pricelist_id.currency_id, order.company_id, date_order)
order.currency_rate = self.env['res.currency']._get_conversion_rate(order.company_id.currency_id, order.currency_id, order.company_id, date_order)
def _prepare_invoice_vals(self):
invoice_vals = super(PosOrder, self)._prepare_invoice_vals()
-10
View File
@@ -43,15 +43,5 @@ there yet otherwise people who have installed the server (even with the without-
parameter) will see those record just disappear.
-->
<!-- Price list -->
<record id="list0" model="product.pricelist">
<field name="name">Public Pricelist</field>
<field name="sequence">1</field>
</record>
<!--
Property
-->
</data>
</odoo>
-8
View File
@@ -810,13 +810,5 @@
<field name="currency_id" ref="base.USD"/>
</record>
<record forcecreate="True" id="property_product_pricelist_demo" model="ir.property">
<field name="name">property_product_pricelist</field>
<field name="fields_id" search="[('model','=','res.partner'),('name','=','property_product_pricelist')]"/>
<field name="value" eval="'product.pricelist,'+str(ref('list0'))"/>
<field name="res_id" eval="'res.partner,'+str(ref('base.partner_demo'))"/>
<field name="company_id" ref="base.main_company"/>
</record>
</data>
</odoo>
+78 -43
View File
@@ -58,69 +58,98 @@ class Pricelist(models.Model):
def name_get(self):
return [(pricelist.id, '%s (%s)' % (pricelist.name, pricelist.currency_id.name)) for pricelist in self]
def _get_products_price(self, products, quantity, uom=None, date=False, **kwargs):
def _get_products_price(self, products, *args, **kwargs):
"""Compute the pricelist prices for the specified products, qty & uom.
Note: self.ensure_one()
Note: self and self.ensure_one()
:returns: dict{product_id: product price}, considering the current pricelist
:rtype: dict
:param products: recordset of products (product.product/product.template)
:param float quantity: quantity of products requested (in given uom)
:param currency: record of currency (res.currency) (optional)
:param uom: unit of measure (uom.uom record) (optional)
If not specified, prices returned are expressed in product uoms
:param date: date to use for price computation and currency conversions (optional)
:type date: date or datetime
:returns: {product_id: product price}, considering the current pricelist if any
:rtype: dict(int, float)
"""
self.ensure_one()
self and self.ensure_one() # self is at most one record
return {
product_id: res_tuple[0]
for product_id, res_tuple in self._compute_price_rule(
products,
quantity,
uom=uom,
date=date,
**kwargs
).items()
for product_id, res_tuple in self._compute_price_rule(products, *args, **kwargs).items()
}
def _get_product_price(self, product, quantity, uom=None, date=False, **kwargs):
def _get_product_price(self, product, *args, **kwargs):
"""Compute the pricelist price for the specified product, qty & uom.
Note: self.ensure_one()
Note: self and self.ensure_one()
:returns: unit price of the product, considering pricelist rules
:param products: recordset of products (product.product/product.template)
:param float quantity: quantity of products requested (in given uom)
:param currency: record of currency (res.currency) (optional)
:param uom: unit of measure (uom.uom record) (optional)
If not specified, prices returned are expressed in product uoms
:param date: date to use for price computation and currency conversions (optional)
:type date: date or datetime
:returns: unit price of the product, considering pricelist rules if any
:rtype: float
"""
self.ensure_one()
return self._compute_price_rule(
product, quantity, uom=uom, date=date, **kwargs
)[product.id][0]
self and self.ensure_one() # self is at most one record
return self._compute_price_rule(product, *args, **kwargs)[product.id][0]
def _get_product_price_rule(self, product, quantity, uom=None, date=False, **kwargs):
def _get_product_price_rule(self, product, *args, **kwargs):
"""Compute the pricelist price & rule for the specified product, qty & uom.
Note: self.ensure_one()
Note: self and self.ensure_one()
:param products: recordset of products (product.product/product.template)
:param float quantity: quantity of products requested (in given uom)
:param currency: record of currency (res.currency) (optional)
:param uom: unit of measure (uom.uom record) (optional)
If not specified, prices returned are expressed in product uoms
:param date: date to use for price computation and currency conversions (optional)
:type date: date or datetime
:returns: (product unit price, applied pricelist rule id)
:rtype: tuple(float, int)
"""
self.ensure_one()
return self._compute_price_rule(product, quantity, uom=uom, date=date, **kwargs)[product.id]
self and self.ensure_one() # self is at most one record
return self._compute_price_rule(product, *args, **kwargs)[product.id]
def _get_product_rule(self, product, quantity, uom=None, date=False, **kwargs):
def _get_product_rule(self, product, *args, **kwargs):
"""Compute the pricelist price & rule for the specified product, qty & uom.
Note: self.ensure_one()
Note: self and self.ensure_one()
:param products: recordset of products (product.product/product.template)
:param float quantity: quantity of products requested (in given uom)
:param currency: record of currency (res.currency) (optional)
:param uom: unit of measure (uom.uom record) (optional)
If not specified, prices returned are expressed in product uoms
:param date: date to use for price computation and currency conversions (optional)
:type date: date or datetime
:returns: applied pricelist rule id
:rtype: int or False
"""
self.ensure_one()
return self._compute_price_rule(
product, quantity, uom=uom, date=date, compute_price=False, **kwargs
)[product.id][1]
self and self.ensure_one() # self is at most one record
return self._compute_price_rule(product, *args, **kwargs)[product.id][1]
def _compute_price_rule(self, products, qty, uom=None, date=False, compute_price=True, **kwargs):
def _compute_price_rule(
self, products, quantity, currency=None, uom=None, date=False, compute_price=True,
**kwargs
):
""" Low-level method - Mono pricelist, multi products
Returns: dict{product_id: (price, suitable_rule) for the given pricelist}
Note: self and self.ensure_one()
:param products: recordset of products (product.product/product.template)
:param float qty: quantity of products requested (in given uom)
:param float quantity: quantity of products requested (in given uom)
:param currency: record of currency (res.currency)
note: currency.ensure_one()
:param uom: unit of measure (uom.uom record)
If not specified, prices returned are expressed in product uoms
:param date: date to use for price computation and currency conversions
@@ -130,7 +159,10 @@ class Pricelist(models.Model):
:returns: product_id: (price, pricelist_rule)
:rtype: dict
"""
self.ensure_one()
self and self.ensure_one() # self is at most one record
currency = currency or self.currency_id or self.env.company.currency_id
currency.ensure_one()
if not products:
return {}
@@ -152,9 +184,11 @@ class Pricelist(models.Model):
# Compute quantity in product uom because pricelist rules are specified
# w.r.t product default UoM (min_quantity, price_surchage, ...)
if target_uom != product_uom:
qty_in_product_uom = target_uom._compute_quantity(qty, product_uom, raise_if_failure=False)
qty_in_product_uom = target_uom._compute_quantity(
quantity, product_uom, raise_if_failure=False
)
else:
qty_in_product_uom = qty
qty_in_product_uom = quantity
for rule in rules:
if rule._is_applicable_for(product, qty_in_product_uom):
@@ -162,7 +196,8 @@ class Pricelist(models.Model):
break
if compute_price:
price = suitable_rule._compute_price(product, qty, target_uom, date=date, currency=self.currency_id)
price = suitable_rule._compute_price(
product, quantity, target_uom, date=date, currency=currency)
else:
# Skip price computation when only the rule is requested.
price = 0.0
@@ -172,7 +207,7 @@ class Pricelist(models.Model):
# Split methods to ease (community) overrides
def _get_applicable_rules(self, products, date, **kwargs):
self.ensure_one()
self and self.ensure_one() # self is at most one record
# Do not filter out archived pricelist items, since it means current pricelist is also archived
# We do not want the computation of prices for archived pricelist to always fallback on the Sales price
# because no rule was found (thanks to the automatic orm filtering on active field)
@@ -181,6 +216,7 @@ class Pricelist(models.Model):
)
def _get_applicable_rules_domain(self, products, date, **kwargs):
self and self.ensure_one() # self is at most one record
if products._name == 'product.template':
templates_domain = ('product_tmpl_id', 'in', products.ids)
products_domain = ('product_id.product_tmpl_id', 'in', products.ids)
@@ -198,13 +234,13 @@ class Pricelist(models.Model):
]
# Multi pricelists price|rule computation
def _price_get(self, product, qty, **kwargs):
def _price_get(self, product, quantity, **kwargs):
""" Multi pricelist, mono product - returns price per pricelist """
return {
key: price[0]
for key, price in self._compute_price_rule_multi(product, qty, **kwargs)[product.id].items()}
for key, price in self._compute_price_rule_multi(product, quantity, **kwargs)[product.id].items()}
def _compute_price_rule_multi(self, products, qty, uom=None, date=False, **kwargs):
def _compute_price_rule_multi(self, products, quantity, uom=None, date=False, **kwargs):
""" Low-level method - Multi pricelist, multi products
Returns: dict{product_id: dict{pricelist_id: (price, suitable_rule)} }"""
if not self.ids:
@@ -213,7 +249,7 @@ class Pricelist(models.Model):
pricelists = self
results = {}
for pricelist in pricelists:
subres = pricelist._compute_price_rule(products, qty, uom=uom, date=date, **kwargs)
subres = pricelist._compute_price_rule(products, quantity, uom=uom, date=date, **kwargs)
for product_id, price in subres.items():
results.setdefault(product_id, {})
results[product_id][pricelist.id] = price
@@ -228,9 +264,8 @@ class Pricelist(models.Model):
First, the pricelist of the specific property (res_id set), this one
is created when saving a pricelist on the partner form view.
Else, it will return the pricelist of the partner country group
Else, it will return the generic property (res_id not set), this one
is created on the company creation.
Else, it will return the first available pricelist
Else, it will return the generic property (res_id not set)
Else, it will return the first available pricelist if any
:param int company_id: if passed, used for looking up properties,
instead of current user's company
@@ -345,19 +345,23 @@ class PricelistItem(models.Model):
def _compute_price(self, product, quantity, uom, date, currency=None):
"""Compute the unit price of a product in the context of a pricelist application.
Note: self and self.ensure_one()
:param product: recordset of product (product.product/product.template)
:param float qty: quantity of products requested (in given uom)
:param uom: unit of measure (uom.uom record)
:param datetime date: date to use for price computation and currency conversions
:param currency: pricelist currency (for the specific case where self is empty)
:param currency: currency (for the case where self is empty)
:returns: price according to pricelist rule, expressed in pricelist currency
:returns: price according to pricelist rule or the product price, expressed in the param
currency, the pricelist currency or the company currency
:rtype: float
"""
self and self.ensure_one() # self is at most one record
product.ensure_one()
uom.ensure_one()
currency = currency or self.currency_id
currency = currency or self.currency_id or self.env.company.currency_id
currency.ensure_one()
# Pricelist specific values are specified according to product UoM
@@ -410,7 +414,9 @@ class PricelistItem(models.Model):
rule_base = self.base or 'list_price'
if rule_base == 'pricelist' and self.base_pricelist_id:
price = self.base_pricelist_id._get_product_price(product, quantity, uom, date)
price = self.base_pricelist_id._get_product_price(
product, quantity, currency=self.currency_id, uom=uom, date=date
)
src_currency = self.base_pricelist_id.currency_id
elif rule_base == "standard_price":
src_currency = product.cost_currency_id
@@ -1306,9 +1306,6 @@ class ProductTemplate(models.Model):
self.ensure_one()
# YTI TODO: During website_sale cleaning, we should get rid of those crappy context thing
pricelist = self._get_contextual_pricelist()
if not pricelist:
return 0.0
quantity = self.env.context.get('quantity', 1.0)
uom = self.env['uom.uom'].browse(self.env.context.get('uom'))
date = self.env.context.get('date')
+36 -56
View File
@@ -1,7 +1,6 @@
# -*- 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):
@@ -9,59 +8,40 @@ class ResCompany(models.Model):
@api.model_create_multi
def create(self, vals_list):
companies = super(ResCompany, self).create(vals_list)
ProductPricelist = self.env['product.pricelist']
for new_company in companies:
pricelist = ProductPricelist.search([
('currency_id', '=', new_company.currency_id.id),
('company_id', '=', False)
], limit=1)
if not pricelist:
params = {'currency': new_company.currency_id.name}
pricelist = ProductPricelist.create({
'name': _("Default %(currency)s pricelist") % params,
'currency_id': new_company.currency_id.id,
})
self.env['ir.property']._set_default(
'property_product_pricelist',
'res.partner',
pricelist,
new_company,
)
companies = super().create(vals_list)
companies._activate_or_create_pricelists()
return companies
def write(self, values):
# When we modify the currency of the company, we reflect the change on the list0 pricelist, if
# that pricelist is not used by another company. Otherwise, we create a new pricelist for the
# given currency.
ProductPricelist = self.env['product.pricelist']
currency_id = values.get('currency_id')
main_pricelist = self.env.ref('product.list0', False)
if currency_id and main_pricelist:
nb_companies = self.search_count([])
for company in self:
existing_pricelist = ProductPricelist.search(
[('company_id', 'in', (False, company.id)),
('currency_id', 'in', (currency_id, company.currency_id.id))])
if existing_pricelist and any(currency_id == x.currency_id.id for x in existing_pricelist):
continue
if currency_id == company.currency_id.id:
continue
currency_match = main_pricelist.currency_id == company.currency_id
company_match = (main_pricelist.company_id == company or
(main_pricelist.company_id.id is False and nb_companies == 1))
if currency_match and company_match:
main_pricelist.write({'currency_id': currency_id})
else:
params = {'currency': self.env['res.currency'].browse(currency_id).name}
pricelist = ProductPricelist.create({
'name': _("Default %(currency)s pricelist") % params,
'currency_id': currency_id,
})
self.env['ir.property']._set_default(
'property_product_pricelist',
'res.partner',
pricelist,
company,
)
return super(ResCompany, self).write(values)
def _activate_or_create_pricelists(self):
""" Manage the default pricelists for needed companies. """
if self.user_has_groups('product.group_product_pricelist'):
companies = self or self.env['res.company'].search([])
ProductPricelist = self.env['product.pricelist'].sudo()
# Activate existing default pricelists
default_pricelists_sudo = ProductPricelist.with_context(active_test=False).search(
[('item_ids', '=', False), ('company_id', 'in', companies.ids)]
).filtered(lambda pl: pl.currency_id == pl.company_id.currency_id)
default_pricelists_sudo.action_unarchive()
companies_without_pricelist = companies.filtered(
lambda c: c.id not in default_pricelists_sudo.company_id.ids
)
# Create missing default pricelists
ProductPricelist.create([
company._get_default_pricelist_vals() for company in companies_without_pricelist
])
def _get_default_pricelist_vals(self):
"""Add values to the default pricelist at company creation or activation of the pricelist
Note: self.ensure_one()
:rtype: dict
"""
self.ensure_one()
values = {}
values.update({
'name': _("Default %s pricelist", self.currency_id.name),
'currency_id': self.currency_id.id,
'company_id': self.id,
})
return values
+16 -3
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo import _, api, fields, models
class ResConfigSettings(models.TransientModel):
@@ -43,8 +43,16 @@ class ResConfigSettings(models.TransientModel):
@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
if not self.group_product_pricelist:
if self.group_sale_pricelist:
self.group_sale_pricelist = False
active_pricelist = self.env['product.pricelist'].sudo().search([('active', '=', True)])
if active_pricelist:
return {
'warning': {
'message': _("You are deactivating the pricelist feature. "
"Every active pricelist will be archived.")
}}
@api.onchange('product_pricelist_setting')
def _onchange_product_pricelist_setting(self):
@@ -58,3 +66,8 @@ class ResConfigSettings(models.TransientModel):
if not self.group_discount_per_so_line:
pl = self.env['product.pricelist'].search([('discount_policy', '=', 'without_discount')])
pl.write({'discount_policy': 'with_discount'})
if self.group_product_pricelist:
self.env['res.company']._activate_or_create_pricelists()
else:
self.env['product.pricelist'].sudo().search([]).action_archive()
+1
View File
@@ -12,3 +12,4 @@ class ResCurrency(models.Model):
super()._activate_group_multi_currency()
group_user = self.env.ref('base.group_user').sudo()
group_user._apply_group(self.env.ref('product.group_product_pricelist'))
self.env['res.company']._activate_or_create_pricelists()
+3 -11
View File
@@ -47,17 +47,9 @@ class ProductCommon(
@classmethod
def _archive_other_pricelists(cls):
"""Do not raise if there is no pricelist(s) for a given website"""
website_sale = cls.env['ir.module.module']._get('website_sale')
if website_sale.state == 'installed':
archive_context = patch('odoo.addons.website_sale.models.product_pricelist.ProductPricelist._check_website_pricelist')
else:
archive_context = nullcontext()
with archive_context:
cls.env['product.pricelist'].search([
('id', '!=', cls.pricelist.id),
]).action_archive()
cls.env['product.pricelist'].search([
('id', '!=', cls.pricelist.id),
]).action_archive()
class ProductAttributesCommon(ProductCommon):
+36 -2
View File
@@ -200,11 +200,11 @@ class TestProductPricelist(ProductCommon):
product = self.product_multi_price
price = self.customer_pricelist._get_product_price(product, quantity=1.0)
msg = "Wrong price: Multi Product Price. should be 99 instead of %s" % price
self.assertEqual(float_compare(price, 99, precision_digits=2), 0)
self.assertEqual(float_compare(price, 99, precision_digits=2), 0, msg)
price = self.business_pricelist._get_product_price(product, quantity=1.0)
msg = "Wrong price: Multi Product Price. should be 50 instead of %s" % price
self.assertEqual(float_compare(price, 50, precision_digits=2), 0)
self.assertEqual(float_compare(price, 50, precision_digits=2), 0, msg)
def test_20_price_different_currency_pricelist(self):
pricelist = self.env['product.pricelist'].create({
@@ -249,3 +249,37 @@ class TestProductPricelist(ProductCommon):
price = pricelist._get_product_price(self.monitor, quantity=1.0)
# product price use the currency of the pricelist
self.assertEqual(price, 10090)
def test_price_without_pricelist_fallback_product_price(self):
ProductPricelist = self.env['product.pricelist']
spam = self.env['product.product'].create({
'name': '1 tonne of spam',
'uom_id': self.uom_ton.id,
'uom_po_id': self.uom_ton.id,
'list_price': 100,
'type': 'consu'
})
self.assertEqual(
ProductPricelist._get_product_price(self.monitor, quantity=1.0),
self.monitor.list_price,
msg="without pricelist, the price should be the same as the list price",
)
self.assertEqual(
ProductPricelist._get_product_price(self.monitor, quantity=1.0, currency=self.new_currency),
self.monitor.list_price*10,
msg="without pricelist but with a currency different than the product one, the price "
"should be the same as the list price converted with the currency rate",
)
self.assertEqual(
ProductPricelist._get_product_price(spam, quantity=1.0, uom=self.uom_kgm),
spam.list_price / 1000,
msg="the product price should be converted using the specified uom",
)
self.assertEqual(
ProductPricelist._get_product_price(
spam, quantity=1.0, currency=self.new_currency, uom=self.uom_kgm
),
spam.list_price / 100,
msg="the product price should be converted using the specified uom and converted to the"
" correct currency",
)
+1 -1
View File
@@ -14,7 +14,7 @@
Hello <t t-out="object.partner_id.name or ''">Brandon Freeman</t>,<br/>
Here is your repair order <strong t-out="object.name or ''">RO/00004</strong>
<t t-if="object.invoice_method != 'none'">
amounting in <strong><t t-out="format_amount(object.amount_total, object.pricelist_id.currency_id) or ''">$ 100.00</t>.</strong><br/>
amounting in <strong><t t-out="format_amount(object.amount_total, object.currency_id) or ''">$ 100.00</t>.</strong><br/>
</t>
<t t-else="">
.<br/>
+29 -54
View File
@@ -83,9 +83,13 @@ class Repair(models.Model):
copy=True)
pricelist_id = fields.Many2one(
'product.pricelist', 'Pricelist',
default=lambda self: self.env['product.pricelist'].search([('company_id', 'in', [self.env.company.id, False])], limit=1).id,
help='Pricelist of the selected partner.', check_company=True)
currency_id = fields.Many2one(related='pricelist_id.currency_id')
currency_id = fields.Many2one(
comodel_name='res.currency',
compute='_compute_currency_id',
store=True,
precompute=True,
ondelete='restrict')
partner_invoice_id = fields.Many2one('res.partner', 'Invoicing Address', check_company=True)
invoice_method = fields.Selection([
("none", "No Invoice"),
@@ -147,27 +151,31 @@ class Repair(models.Model):
if order.partner_id:
order.default_address_id = order.partner_id.address_get(['contact'])['contact']
@api.depends('pricelist_id', 'company_id')
def _compute_currency_id(self):
for order in self:
order.currency_id = order.pricelist_id.currency_id.id or order.company_id.currency_id.id
@api.depends('picking_id', 'picking_id.state')
def _compute_is_returned(self):
self.is_returned = False
returned = self.filtered(lambda r: r.picking_id and r.picking_id.state == 'done')
returned.is_returned = True
@api.depends('operations.price_subtotal', 'invoice_method', 'fees_lines.price_subtotal', 'pricelist_id.currency_id')
@api.depends('operations.price_subtotal', 'invoice_method', 'fees_lines.price_subtotal', 'currency_id')
def _amount_untaxed(self):
for order in self:
total = sum(operation.price_subtotal for operation in order.operations)
total += sum(fee.price_subtotal for fee in order.fees_lines)
currency = order.pricelist_id.currency_id or self.env.company.currency_id
order.amount_untaxed = currency.round(total)
order.amount_untaxed = order.currency_id.round(total)
@api.depends('operations.price_unit', 'operations.product_uom_qty', 'operations.product_id',
'fees_lines.price_unit', 'fees_lines.product_uom_qty', 'fees_lines.product_id',
'pricelist_id.currency_id', 'partner_id')
'currency_id', 'partner_id')
def _amount_tax(self):
for order in self:
val = 0.0
currency = order.pricelist_id.currency_id or self.env.company.currency_id
currency = order.currency_id
for operation in order.operations:
if operation.tax_id:
tax_calculate = operation.tax_id.compute_all(operation.price_unit, currency, operation.product_uom_qty, operation.product_id, order.partner_id)
@@ -183,8 +191,7 @@ class Repair(models.Model):
@api.depends('amount_untaxed', 'amount_tax')
def _amount_total(self):
for order in self:
currency = order.pricelist_id.currency_id or self.env.company.currency_id
order.amount_total = currency.round(order.amount_untaxed + order.amount_tax)
order.amount_total = order.currency_id.round(order.amount_untaxed + order.amount_tax)
_sql_constraints = [
('name', 'unique (name)', 'The name of the Repair Order must be unique!'),
@@ -373,7 +380,7 @@ class Repair(models.Model):
raise UserError(_('You have to select an invoice address in the repair form.'))
narration = repair.quotation_notes
currency = repair.pricelist_id.currency_id
currency = repair.currency_id
company = repair.env.company
if (partner_invoice.id, currency.id, company.id) not in grouped_invoices_vals:
@@ -711,7 +718,7 @@ class RepairLine(models.Model):
@api.depends('price_unit', 'repair_id', 'product_uom_qty', 'product_id', 'tax_id', 'repair_id.invoice_method')
def _compute_price_total_and_subtotal(self):
for line in self:
taxes = line.tax_id.compute_all(line.price_unit, line.repair_id.pricelist_id.currency_id, line.product_uom_qty, line.product_id, line.repair_id.partner_id)
taxes = line.tax_id.compute_all(line.price_unit, line.repair_id.currency_id, line.product_uom_qty, line.product_id, line.repair_id.partner_id)
line.price_subtotal = taxes['total_excluded']
line.price_total = taxes['total_included']
@@ -774,30 +781,14 @@ class RepairLine(models.Model):
fpos = self.env['account.fiscal.position']._get_fiscal_position(partner_invoice, delivery=self.repair_id.address_id)
taxes = self.product_id.taxes_id.filtered(lambda x: x.company_id == self.repair_id.company_id)
self.tax_id = fpos.map_tax(taxes)
warning = False
pricelist = self.repair_id.pricelist_id
if not pricelist:
warning = {
'title': _('No pricelist found.'),
'message':
_('You have to select a pricelist in the Repair form !\n Please set one before choosing a product.')}
return {'warning': warning}
else:
self._onchange_product_uom()
self._onchange_product_uom()
@api.onchange('product_uom')
def _onchange_product_uom(self):
pricelist = self.repair_id.pricelist_id
if pricelist and self.product_id and self.type != 'remove':
price = pricelist._get_product_price(self.product_id, self.product_uom_qty, uom=self.product_uom)
if price is False:
warning = {
'title': _('No valid pricelist line found.'),
'message':
_("Couldn't find a pricelist line matching this product and quantity.\nYou have to change either the product, the quantity or the pricelist.")}
return {'warning': warning}
else:
self.price_unit = price
if self.product_id and self.type != 'remove':
price = self.repair_id.pricelist_id._get_product_price(
self.product_id, self.product_uom_qty, uom=self.product_uom)
self.price_unit = price
class RepairFee(models.Model):
@@ -833,7 +824,7 @@ class RepairFee(models.Model):
@api.depends('price_unit', 'repair_id', 'product_uom_qty', 'product_id', 'tax_id')
def _compute_price_total_and_subtotal(self):
for fee in self:
taxes = fee.tax_id.compute_all(fee.price_unit, fee.repair_id.pricelist_id.currency_id, fee.product_uom_qty, fee.product_id, fee.repair_id.partner_id)
taxes = fee.tax_id.compute_all(fee.price_unit, fee.repair_id.currency_id, fee.product_uom_qty, fee.product_id, fee.repair_id.partner_id)
fee.price_subtotal = taxes['total_excluded']
fee.price_total = taxes['total_included']
@@ -853,7 +844,6 @@ class RepairFee(models.Model):
partner = self.repair_id.partner_id
partner_invoice = self.repair_id.partner_invoice_id or partner
pricelist = self.repair_id.pricelist_id
if partner and self.product_id:
fpos = self.env['account.fiscal.position']._get_fiscal_position(partner_invoice, delivery=self.repair_id.address_id)
@@ -870,29 +860,14 @@ class RepairFee(models.Model):
else:
self.name += '\n' + self.product_id.description_sale
warning = False
if not pricelist:
warning = {
'title': _('No pricelist found.'),
'message':
_('You have to select a pricelist in the Repair form !\n Please set one before choosing a product.')}
return {'warning': warning}
else:
self._onchange_product_uom()
self._onchange_product_uom()
@api.onchange('product_uom')
def _onchange_product_uom(self):
pricelist = self.repair_id.pricelist_id
if pricelist and self.product_id:
price = pricelist._get_product_price(self.product_id, self.product_uom_qty, uom=self.product_uom)
if price is False:
warning = {
'title': _('No valid pricelist line found.'),
'message':
_("Couldn't find a pricelist line matching this product and quantity.\nYou have to change either the product, the quantity or the pricelist.")}
return {'warning': warning}
else:
self.price_unit = price
if self.product_id:
price = self.repair_id.pricelist_id._get_product_price(
self.product_id, self.product_uom_qty, uom=self.product_uom)
self.price_unit = price
class RepairTags(models.Model):
@@ -84,7 +84,7 @@
<span t-esc="','.join(map( lambda x: x.name, line.tax_id))"/>
</td>
<td class="text-end o_price_total">
<span t-field="line.price_subtotal" t-options='{"widget": "monetary", "display_currency": o.pricelist_id.currency_id}'/>
<span t-field="line.price_subtotal" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
</td>
</t>
</tr>
@@ -108,7 +108,7 @@
</td>
<td class="text-end o_price_total">
<span t-field="fees.price_subtotal"
t-options='{"widget": "monetary", "display_currency": o.pricelist_id.currency_id}'/>
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
</td>
</t>
</tr>
@@ -124,21 +124,21 @@
<td><strong>Total Without Taxes</strong></td>
<td class="text-end">
<span t-field="o.amount_untaxed"
t-options='{"widget": "monetary", "display_currency": o.pricelist_id.currency_id}'/>
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
</td>
</tr>
<tr>
<td>Taxes</td>
<td class="text-end o_price_total">
<span t-field="o.amount_tax"
t-options='{"widget": "monetary", "display_currency": o.pricelist_id.currency_id}'/>
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
</td>
</tr>
<tr class="border-black o_total">
<td><strong>Total</strong></td>
<td class="text-end o_price_total">
<span t-field="o.amount_total"
t-options='{"widget": "monetary", "display_currency": o.pricelist_id.currency_id}'/>
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
</td>
</tr>
</t>
+1 -1
View File
@@ -210,7 +210,7 @@ class CustomerPortal(portal.CustomerPortal):
providers_sudo, logged_in=logged_in, sale_order_id=order_sudo.id
),
'amount': order_sudo.amount_total,
'currency': order_sudo.pricelist_id.currency_id,
'currency': order_sudo.currency_id,
'partner_id': order_sudo.partner_id.id,
'access_token': order_sudo.access_token,
'transaction_route': order_sudo.get_portal_url(suffix='/transaction'),
+2 -2
View File
@@ -20,14 +20,14 @@
<t t-if="object.origin">
(with reference: <t t-out="object.origin or ''"></t> )
</t>
amounting in <span style="font-weight: bold;" t-out="format_amount(object.amount_total, object.pricelist_id.currency_id) or ''">$ 10.00</span> is available.
amounting in <span style="font-weight: bold;" t-out="format_amount(object.amount_total, object.currency_id) or ''">$ 10.00</span> is available.
</t>
<t t-else="">
<t t-out="doc_name or ''">quotation</t> <span style="font-weight: bold;" t-out="object.name or ''"></span>
<t t-if="object.origin">
(with reference: <t t-out="object.origin or ''">S00052</t> )
</t>
amounting in <span style="font-weight: bold;" t-out="format_amount(object.amount_total, object.pricelist_id.currency_id) or ''">$ 10.00</span> is ready for review.
amounting in <span style="font-weight: bold;" t-out="format_amount(object.amount_total, object.currency_id) or ''">$ 10.00</span> is ready for review.
</t>
<br/><br/>
Do not hesitate to contact us if you have any questions.
-20
View File
@@ -29,7 +29,6 @@
<field name="partner_invoice_id" ref="base.res_partner_2"/>
<field name="partner_shipping_id" ref="base.res_partner_2"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -63,7 +62,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_13"/>
<field name="partner_shipping_id" ref="base.res_partner_address_13"/>
<field name="user_id" ref="base.user_admin"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -91,7 +89,6 @@
<field name="partner_invoice_id" ref="base.res_partner_4"/>
<field name="partner_shipping_id" ref="base.res_partner_4"/>
<field name="user_id" ref="base.user_admin"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -116,7 +113,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_admin"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -155,7 +151,6 @@
<field name="partner_invoice_id" ref="base.res_partner_2"/>
<field name="partner_shipping_id" ref="base.res_partner_2"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.crm_team_1"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -174,7 +169,6 @@
<field name="partner_invoice_id" ref="base.res_partner_18"/>
<field name="partner_shipping_id" ref="base.res_partner_18"/>
<field name="user_id" ref="base.user_admin"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.crm_team_1"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -193,7 +187,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_11"/>
<field name="partner_shipping_id" ref="base.res_partner_address_11"/>
<field name="user_id" ref="base.user_admin"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -231,7 +224,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.crm_team_1"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -259,7 +251,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.crm_team_1"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -286,7 +277,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.crm_team_1"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -315,7 +305,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.crm_team_1"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -342,7 +331,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.crm_team_1"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -369,7 +357,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.crm_team_1"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -394,7 +381,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -421,7 +407,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -448,7 +433,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -475,7 +459,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -502,7 +485,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -529,7 +511,6 @@
<field name="partner_invoice_id" ref="base.partner_demo_portal"/>
<field name="partner_shipping_id" ref="base.partner_demo_portal"/>
<field name="user_id" ref="base.user_admin"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="state">sent</field>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="date_order" eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
@@ -563,7 +544,6 @@
<field name="partner_invoice_id" ref="base.partner_demo_portal"/>
<field name="partner_shipping_id" ref="base.partner_demo_portal"/>
<field name="user_id" ref="base.user_admin"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="date_order" eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
<field name="message_partner_ids" eval="[(4, ref('base.partner_demo_portal'))]"/>
+13 -14
View File
@@ -157,7 +157,10 @@ class ProductTemplate(models.Model):
}]
return res
def _get_combination_info(self, combination=False, product_id=False, add_qty=1, pricelist=False, parent_combination=False, only_template=False):
def _get_combination_info(
self, combination=False, product_id=False, add_qty=1, pricelist=False,
parent_combination=False, only_template=False
):
""" Return info about a given combination.
Note: this method does not take into account whether the combination is
@@ -209,12 +212,13 @@ class ProductTemplate(models.Model):
# get the name before the change of context to benefit from prefetch
display_name = self.display_name
display_image = True
quantity = self.env.context.get('quantity', add_qty)
product_template = self
combination = combination or product_template.env['product.template.attribute.value']
pricelist = pricelist or self.env['product.pricelist']
if not product_id and not combination and not only_template:
combination = product_template._get_first_possible_combination(parent_combination)
@@ -242,10 +246,7 @@ class ProductTemplate(models.Model):
no_variant_attributes_price_extra=tuple(no_variant_attributes_price_extra)
)
list_price = product.price_compute('list_price')[product.id]
if pricelist:
price = pricelist._get_product_price(product, quantity)
else:
price = list_price
price = pricelist._get_product_price(product, quantity)
display_image = bool(product.image_128)
display_name = product.display_name
price_extra = (product.price_extra or 0.0) + (sum(no_variant_attributes_price_extra) or 0.0)
@@ -254,28 +255,26 @@ class ProductTemplate(models.Model):
product_template = product_template.with_context(current_attributes_price_extra=current_attributes_price_extra)
price_extra = sum(current_attributes_price_extra)
list_price = product_template.price_compute('list_price')[product_template.id]
if pricelist:
price = pricelist._get_product_price(product_template, quantity)
else:
price = list_price
price = pricelist._get_product_price(product_template, quantity)
display_image = bool(product_template.image_128)
combination_name = combination._get_combination_name()
if combination_name:
display_name = "%s (%s)" % (display_name, combination_name)
if pricelist and pricelist.currency_id != product_template.currency_id:
currency = pricelist.currency_id or self.env.company.currency_id
if currency != product_template.currency_id:
list_price = product_template.currency_id._convert(
list_price, pricelist.currency_id, product_template._get_current_company(pricelist=pricelist),
list_price, currency, product_template._get_current_company(pricelist=pricelist),
fields.Date.today()
)
price_extra = product_template.currency_id._convert(
price_extra, pricelist.currency_id, product_template._get_current_company(pricelist=pricelist),
price_extra, currency, product_template._get_current_company(pricelist=pricelist),
fields.Date.today()
)
price_without_discount = list_price if pricelist and pricelist.discount_policy == 'without_discount' else price
has_discounted_price = (pricelist or product_template).currency_id.compare_amounts(price_without_discount, price) == 1
has_discounted_price = currency.compare_amounts(price_without_discount, price) == 1
return {
'product_id': product.id,
+44 -10
View File
@@ -172,15 +172,18 @@ class SaleOrder(models.Model):
comodel_name='product.pricelist',
string="Pricelist",
compute='_compute_pricelist_id',
store=True, readonly=False, precompute=True, check_company=True, required=True, # Unrequired company
store=True, readonly=False, precompute=True, check_company=True, # Unrequired company
states=READONLY_FIELD_STATES,
tracking=1,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]",
help="If you change the pricelist, only newly added lines will be affected.")
currency_id = fields.Many2one(
related='pricelist_id.currency_id',
depends=["pricelist_id"],
store=True, precompute=True, ondelete="restrict")
comodel_name='res.currency',
compute='_compute_currency_id',
store=True,
precompute=True,
ondelete='restrict'
)
currency_rate = fields.Float(
string="Currency Rate",
compute='_compute_currency_rate',
@@ -284,10 +287,11 @@ class SaleOrder(models.Model):
show_update_fpos = fields.Boolean(
string="Has Fiscal Position Changed", store=False) # True if the fiscal position was changed
has_active_pricelist = fields.Boolean(
compute='_compute_has_active_pricelist')
show_update_pricelist = fields.Boolean(
string="Has Pricelist Changed", store=False) # True if the pricelist was changed
def init(self):
create_index(self._cr, 'sale_order_date_order_id_idx', 'sale_order', ["date_order desc", "id desc"])
@@ -368,15 +372,22 @@ class SaleOrder(models.Model):
order = order.with_company(order.company_id)
order.payment_term_id = order.partner_id.property_payment_term_id
@api.depends('partner_id')
@api.depends('partner_id', 'company_id')
def _compute_pricelist_id(self):
for order in self:
if order.state != 'draft':
continue
if not order.partner_id:
order.pricelist_id = False
continue
order = order.with_company(order.company_id)
order.pricelist_id = order.partner_id.property_product_pricelist
@api.depends('pricelist_id', 'company_id')
def _compute_currency_id(self):
for order in self:
order.currency_id = order.pricelist_id.currency_id or order.company_id.currency_id
@api.depends('currency_id', 'date_order', 'company_id')
def _compute_currency_rate(self):
cache = {}
@@ -398,6 +409,14 @@ class SaleOrder(models.Model):
)
order.currency_rate = cache[key]
@api.depends('company_id')
def _compute_has_active_pricelist(self):
for order in self:
order.has_active_pricelist = bool(self.env['product.pricelist'].search(
[('company_id', 'in', (False, order.company_id.id)), ('active', '=', True)],
limit=1,
))
@api.depends('partner_id')
def _compute_user_id(self):
for order in self:
@@ -619,6 +638,19 @@ class SaleOrder(models.Model):
}
}
@api.onchange('company_id')
def _onchange_company_id_warning(self):
self.show_update_pricelist = True
if self.order_line and self.state == 'draft':
return {
'warning': {
'title': _("Warning for the change of your quotation's company"),
'message': _("Changing the company of an existing quotation might need some "
"manual adjustments in the details of the lines. You might "
"consider updating the prices."),
}
}
@api.onchange('fiscal_position_id')
def _onchange_fpos_id_show_update_fpos(self):
if self.order_line and (
@@ -655,8 +687,7 @@ class SaleOrder(models.Model):
@api.onchange('pricelist_id')
def _onchange_pricelist_id_show_update_prices(self):
if self.order_line and self.pricelist_id and self._origin.pricelist_id != self.pricelist_id:
self.show_update_pricelist = True
self.show_update_pricelist = bool(self.order_line)
#=== CRUD METHODS ===#
@@ -936,10 +967,13 @@ class SaleOrder(models.Model):
self._recompute_prices()
if self.pricelist_id:
self.message_post(body=_(
message = _(
"Product prices have been recomputed according to pricelist %s.",
self.pricelist_id._get_html_link(),
))
)
else:
message = _("Product prices have been recomputed.")
self.message_post(body=message)
def _recompute_prices(self):
lines_to_recompute = self.order_line.filtered(lambda line: not line.display_type)
+7 -7
View File
@@ -396,7 +396,7 @@ class SaleOrderLine(models.Model):
else:
line.pricelist_item_id = line.order_id.pricelist_id._get_product_rule(
line.product_id,
line.product_uom_qty or 1.0,
quantity=line.product_uom_qty or 1.0,
uom=line.product_uom,
date=line.order_id.date_order,
)
@@ -408,7 +408,7 @@ class SaleOrderLine(models.Model):
# manually edited
if line.qty_invoiced > 0:
continue
if not line.product_uom or not line.product_id or not line.order_id.pricelist_id:
if not line.product_uom or not line.product_id:
line.price_unit = 0.0
else:
price = line.with_company(line.company_id)._get_display_price()
@@ -459,11 +459,11 @@ class SaleOrderLine(models.Model):
pricelist_rule = self.pricelist_item_id
order_date = self.order_id.date_order or fields.Date.today()
product = self.product_id.with_context(**self._get_product_price_context())
qty = self.product_uom_qty or 1.0
quantity = self.product_uom_qty or 1.0
uom = self.product_uom or self.product_id.uom_id
price = pricelist_rule._compute_price(
product, qty, uom, order_date, currency=self.currency_id)
product, quantity, uom, order_date, currency=self.currency_id)
return price
@@ -502,7 +502,7 @@ class SaleOrderLine(models.Model):
pricelist_rule = self.pricelist_item_id
order_date = self.order_id.date_order or fields.Date.today()
product = self.product_id.with_context(**self._get_product_price_context())
qty = self.product_uom_qty or 1.0
quantity = self.product_uom_qty or 1.0
uom = self.product_uom
if pricelist_rule:
@@ -512,14 +512,14 @@ class SaleOrderLine(models.Model):
# to show the discount to the customer.
while pricelist_item.base == 'pricelist' and pricelist_item.base_pricelist_id.discount_policy == 'without_discount':
rule_id = pricelist_item.base_pricelist_id._get_product_rule(
product, qty, uom=uom, date=order_date)
product, quantity, currency=self.currency_id, uom=uom, date=order_date)
pricelist_item = self.env['product.pricelist.item'].browse(rule_id)
pricelist_rule = pricelist_item
price = pricelist_rule._compute_base_price(
product,
qty,
quantity,
uom,
order_date,
target_currency=self.currency_id,
@@ -133,7 +133,7 @@
<strong class="mr16">Subtotal</strong>
<span
t-out="current_subtotal"
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
t-options='{"widget": "monetary", "display_currency": doc.currency_id}'
/>
</td>
</tr>
+12
View File
@@ -486,6 +486,7 @@ class TestSalePrices(SaleCommon):
"""
sale_order = self.sale_order
so_amount = sale_order.amount_total
start_so_amount = so_amount
sale_order._recompute_prices()
self.assertEqual(
sale_order.amount_total, so_amount,
@@ -512,6 +513,17 @@ class TestSalePrices(SaleCommon):
self.assertEqual(sale_order.amount_undiscounted, so_amount)
self.assertEqual(sale_order.amount_total, 0.95*so_amount)
# Test taking off the pricelist
sale_order.pricelist_id = False
sale_order._recompute_prices()
self.assertTrue(all(line.discount == 0 for line in sale_order.order_line))
self.assertEqual(sale_order.amount_undiscounted, so_amount)
self.assertEqual(
sale_order.amount_total, start_so_amount,
"The SO amount without pricelist should be the same than with an empty pricelist"
)
# Taxes tests:
# We do not rely on accounting common on purpose to avoid
# all the useless setup not needed here.
@@ -8,6 +8,13 @@ from odoo.tests import tagged
class TestSaleProductAttributeValueCommon(TestProductAttributeValueCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env['product.pricelist'].sudo().search([]).action_archive()
cls.env['product.pricelist'].create({'name': 'Base Pricelist'})
@classmethod
def _setup_currency(cls, currency_ratio=2):
"""Get or create a currency. This makes the test non-reliant on demo.
+7 -2
View File
@@ -264,9 +264,14 @@
<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="has_active_pricelist" invisible="1"/>
<field name="show_update_pricelist" invisible="1"/>
<label for="pricelist_id" groups="product.group_product_pricelist"/>
<div groups="product.group_product_pricelist" class="o_row">
<label for="pricelist_id"
groups="product.group_product_pricelist"
attrs="{'invisible': [('has_active_pricelist', '=', False)]}"/>
<div groups="product.group_product_pricelist"
class="o_row"
attrs="{'invisible': [('has_active_pricelist', '=', False)]}">
<field name="pricelist_id" options="{'no_open':True,'no_create': True}"/>
<button name="action_update_prices" type="object"
string=" Update Prices"
+2 -2
View File
@@ -158,7 +158,7 @@
<small><b class="text-muted">Your advantage</b></small>
<small>
<b t-field="sale_order.amount_undiscounted"
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'
t-options='{"widget": "monetary", "display_currency": sale_order.currency_id}'
style="text-decoration: line-through"
class="d-block mt-1"
data-id="amount_undiscounted" />
@@ -546,7 +546,7 @@
<td colspan="99">
<strong class="mr16">Subtotal</strong>
<span t-out="current_subtotal"
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'
t-options='{"widget": "monetary", "display_currency": sale_order.currency_id}'
/>
</td>
</tr>
+1 -1
View File
@@ -136,7 +136,7 @@
<span t-out="abs(combination_info_variant['price_extra'])" class="variant_price_extra text-muted fst-italic" style="white-space: nowrap;"
t-options='{
"widget": "monetary",
"display_currency": (pricelist or product).currency_id
"display_currency": (pricelist or product.env.company).currency_id
}'/>
</span>
</template>
@@ -24,7 +24,6 @@ class TestSaleExpense(TestExpenseCommon, TestSaleCommon):
'product_uom': self.company_data['product_delivery_no'].uom_id.id,
'price_unit': self.company_data['product_delivery_no'].list_price,
})],
'pricelist_id': self.env.ref('product.list0').id,
})
so.action_confirm()
so._create_analytic_account() # normally created at so confirmation when you use the right products
-2
View File
@@ -14,8 +14,6 @@ class TestSaleCouponCommon(TestSaleProductAttributeValueCommon):
# set currency to not rely on demo data and avoid possible race condition
cls.currency_ratio = 1.0
pricelist = cls.env.ref('product.list0')
pricelist.currency_id = cls._setup_currency(cls.currency_ratio)
# Set all the existing programs to active=False to avoid interference
cls.env['loyalty.program'].search([]).write({'active': False})
@@ -82,7 +82,7 @@ class SaleOrderOption(models.Model):
@api.depends('product_id', 'uom_id', 'quantity')
def _compute_price_unit(self):
for option in self:
if not option.product_id or not option.order_id.pricelist_id:
if not option.product_id:
continue
# To compute the price_unit a so line is created in cache
values = option._get_values_to_add_to_order()
@@ -28,7 +28,7 @@
<td name="td_option_price_unit">
<strong class="text-end">
<div t-field="option.price_unit"
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
t-options='{"widget": "monetary", "display_currency": doc.currency_id}'
t-att-style="option.discount and 'text-decoration: line-through' or None"
t-att-class="option.discount and 'text-danger' or None"/>
<div t-if="option.discount">
@@ -50,11 +50,11 @@
<td>
<strong class="text-end">
<div t-field="option.price_unit"
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'
t-options='{"widget": "monetary", "display_currency": sale_order.currency_id}'
t-att-style="option.discount and 'text-decoration: line-through' or None"
t-att-class="option.discount and 'text-danger' or None"/>
<div t-if="option.discount">
<t t-out="(1-option.discount / 100.0) * option.price_unit" t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/>
<t t-out="(1-option.discount / 100.0) * option.price_unit" t-options='{"widget": "monetary", "display_currency": sale_order.currency_id}'/>
</div>
</strong>
</td>
@@ -182,7 +182,6 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
'product_uom': product.uom_id.id,
'price_unit': product.list_price
})],
'pricelist_id': self.env.ref('product.list0').id,
'company_id': self.company_data['company'].id,
}
so = self.env['sale.order'].create(so_vals)
@@ -661,7 +661,6 @@ class TestSaleMrpFlow(ValuationReconciliationTestCommon):
'product_uom': self.finished_product.uom_id.id,
'price_unit': self.finished_product.list_price
})],
'pricelist_id': self.env.ref('product.list0').id,
'company_id': self.company.id,
}
self.so = self.env['sale.order'].create(so_vals)
@@ -1896,7 +1895,6 @@ class TestSaleMrpFlow(ValuationReconciliationTestCommon):
'product_uom': self.uom_unit.id,
'price_unit': 50
})],
'pricelist_id': self.env.ref('product.list0').id,
'company_id': self.env.company.id
}
so = self.env['sale.order'].create(so_vals)
@@ -46,14 +46,14 @@
t-out="combination_info['list_price']"
t-options='{
"widget": "monetary",
"display_currency": (pricelist or product).currency_id
"display_currency": (pricelist or product.env.company).currency_id
}'/>
<span class="oe_price product_id mt-3" style="white-space: nowrap;"
t-att-data-product-id="product.id"
t-out="combination_info['price']"
t-options='{
"widget": "monetary",
"display_currency": (pricelist or product).currency_id
"display_currency": (pricelist or product.env.company).currency_id
}'/>
</h2>
<t t-if="product.visible_qty_configurator">
@@ -136,7 +136,7 @@
t-out="combination_info['list_price']"
t-options='{
"widget": "monetary",
"display_currency": (pricelist or product).currency_id
"display_currency": (pricelist or product.env.company).currency_id
}'
/>
<span class="oe_price product_id" style="white-space: nowrap;"
@@ -144,7 +144,7 @@
t-out="combination_info['price']"
t-options='{
"widget": "monetary",
"display_currency": (pricelist or product).currency_id
"display_currency": (pricelist or product.env.company).currency_id
}'/>
<span class="js_raw_price d-none" t-out="product._get_contextual_price()"/>
<p class="css_not_available_msg alert alert-warning">Option not available</p>
@@ -158,7 +158,7 @@
t-out="combination_info['price'] * (add_qty or 1)"
t-options='{
"widget": "monetary",
"display_currency": (pricelist or product).currency_id
"display_currency": (pricelist or product.env.company).currency_id
}'/>
</td>
</tr>
@@ -205,13 +205,13 @@
t-out="combination_info['list_price']"
t-options='{
"widget": "monetary",
"display_currency": (pricelist or product).currency_id
"display_currency": (pricelist or product.env.company).currency_id
}'/>
<div class="oe_price" style="white-space: nowrap;"
t-out="combination_info['price']"
t-options='{
"widget": "monetary",
"display_currency": (pricelist or product).currency_id
"display_currency": (pricelist or product.env.company).currency_id
}'/>
<span class="js_raw_price d-none" t-out="combination_info['price']" />
<p class="css_not_available_msg alert alert-warning">Option not available</p>
@@ -32,7 +32,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -64,7 +63,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -87,7 +85,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -119,7 +116,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
<field name="medium_id" ref="utm.utm_medium_email"/>
@@ -1089,7 +1089,6 @@ class TestSaleStock(TestSaleCommon, ValuationReconciliationTestCommon):
'product_uom': product.uom_id.id,
'price_unit': product.list_price
})],
'pricelist_id': self.env.ref('product.list0').id,
})
so.action_confirm()
self.assertEqual(so.state, 'done')
@@ -34,7 +34,6 @@ class TestDropship(common.TransactionCase):
'product_uom': prod.uom_id.id,
'price_unit': 12,
})],
'pricelist_id': self.env.ref('product.list0').id,
'picking_policy': 'direct',
})
so.action_confirm()
@@ -49,7 +49,6 @@ class TestStockValuation(ValuationReconciliationTestCommon):
'price_unit': 12,
'tax_id': [(6, 0, [])],
})],
'pricelist_id': self.env.ref('product.list0').id,
'picking_policy': 'direct',
})
self.sale_order1.action_confirm()
@@ -437,7 +437,7 @@ class TestOnlineEventPerformance(EventPerformanceCase, UtilPerf):
# website customer data
with freeze_time(self.reference_now):
self.authenticate('user_eventmanager', 'user_eventmanager')
with self.assertQueryCount(default=34): # tef 33
with self.assertQueryCount(default=35): # tef 34
self._test_url_open('/event/%i' % self.test_event.id)
@warmup
@@ -453,7 +453,7 @@ class TestOnlineEventPerformance(EventPerformanceCase, UtilPerf):
# website customer data
with freeze_time(self.reference_now):
self.authenticate('user_eventmanager', 'user_eventmanager')
with self.assertQueryCount(default=42): # tef 41
with self.assertQueryCount(default=43): # tef 42
self._test_url_open('/event')
@warmup
@@ -461,7 +461,7 @@ class TestOnlineEventPerformance(EventPerformanceCase, UtilPerf):
# website customer data
with freeze_time(self.reference_now):
self.authenticate(None, None)
with self.assertQueryCount(default=38):
with self.assertQueryCount(default=39):
self._test_url_open('/event')
# @warmup
@@ -60,7 +60,7 @@ tour.register('sale_product_configurator_tour', {
trigger: 'td.o_data_cell:contains("Chair floor protection")',
run: function (){}
}, {
trigger: 'span[name=amount_total]:contains("0.00")',
trigger: 'span[name=amount_total]:contains("960.60")',
run: function (){}
}, ...tour.stepUtils.discardForm()
]);
@@ -62,6 +62,7 @@ class TestWebsiteEventBoothSale(HttpCase, TestWebsiteEventSaleCommon):
})
def test_tour(self):
self.env['product.pricelist'].sudo().search([]).action_archive()
self.start_tour('/event', 'website_event_booth_tour', login='portal')
def test_booth_pricelists_different_currencies(self):
@@ -29,6 +29,7 @@ class TestWebsiteBoothPriceList(TestEventBoothSaleCommon, TestWebsiteEventSaleCo
})
def test_pricelist_different_currency(self):
self.env['product.pricelist'].search([('id', '!=', self.pricelist.id)]).action_archive()
so_line = self.env['sale.order.line'].create({
'event_booth_category_id': self.event_booth_category_1.id,
'event_booth_pending_ids': (self.booth_1 + self.booth_2).ids,
@@ -10,10 +10,10 @@
t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.get_current_pricelist().currency_id}"/>
</t>
<span t-field="booth_category.price_reduce" class="fw-normal text-muted"
t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.pricelist_id.currency_id}"
t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.currency_id}"
groups="account.group_show_line_subtotals_tax_excluded"/>
<span t-field="booth_category.price_reduce_taxinc" class="fw-normal text-muted"
t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.pricelist_id.currency_id}"
t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.currency_id}"
groups="account.group_show_line_subtotals_tax_included"/>
</t>
<span t-else="" class="fw-normal text-muted">Free</span>
@@ -35,7 +35,7 @@
</div>
<div class="col-sm-6">
<span class="fw-bold" t-out="float(0)"
t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.pricelist_id.currency_id}"/>
t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.currency_id}"/>
</div>
</div>
</xpath>
@@ -12,7 +12,6 @@ class WebsiteEventSaleController(WebsiteEventController):
@route()
def event_register(self, event, **post):
event = event.with_context(pricelist=request.website.id)
if not request.context.get('pricelist'):
pricelist = request.website.pricelist_id
if pricelist:
+1 -1
View File
@@ -52,7 +52,7 @@ class TestWebsiteEventSaleCommon(TransactionCase):
}])
cls.current_website = cls.env['website'].get_current_website()
cls.pricelist = cls.current_website.get_current_pricelist()
cls.pricelist = cls.env['product.pricelist'].create({'name': 'Base Pricelist'})
cls.so = cls.env['sale.order'].create({
'company_id': cls.env.company.id,
@@ -89,6 +89,7 @@ class TestUi(HttpCaseWithUserDemo, TestWebsiteEventSaleCommon):
# - that this test awaits for hardcoded USDs amount
# we have to force company currency as USDs only for this test
self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", [self.env.ref('base.USD').id, self.env.ref('base.main_company').id])
self.env['product.pricelist'].create({'name': "Public Pricelist"})
transfer_provider = self.env.ref('payment.payment_provider_transfer')
transfer_provider.write({
@@ -110,6 +111,9 @@ class TestUi(HttpCaseWithUserDemo, TestWebsiteEventSaleCommon):
})
transfer_provider._transfer_ensure_pending_msg_is_set()
# Ensure the use of USD (company currency)
self.env['product.pricelist'].create({'name': "Public Pricelist"})
self.start_tour("/", 'event_buy_tickets', login="demo")
def test_buy_last_ticket(self):
@@ -17,7 +17,7 @@ class TestWebsiteEventPriceList(TestWebsiteEventSaleCommon):
cls.WebsiteSaleController = WebsiteSale()
def test_pricelist_different_currency(self):
self.env['product.pricelist'].search([('id', '!=', self.pricelist.id)]).action_archive()
so_line = self.env['sale.order.line'].create({
'event_id': self.event.id,
'event_ticket_id': self.ticket.id,
@@ -15,17 +15,17 @@
<!-- Add price information on tickets (multi tickets, aka in collapse) -->
<xpath expr="//div[hasclass('o_wevent_registration_multi_select')]" position="inside">
<t t-if="ticket.price">
<t t-if="(ticket.price - ticket.price_reduce) &gt; 1 and website.pricelist_id.discount_policy == 'without_discount'">
<del class="text-danger me-1" t-field="ticket.price" t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.pricelist_id.currency_id}"/>
<t t-if="(ticket.price - ticket.price_reduce) &gt; 1 and website.pricelist_id and website.pricelist_id.discount_policy == 'without_discount'">
<del class="text-danger me-1" t-field="ticket.price" t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.currency_id}"/>
</t>
<span t-field="ticket.price_reduce"
t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.pricelist_id.currency_id}"
t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.currency_id}"
groups="account.group_show_line_subtotals_tax_excluded"/>
<span t-field="ticket.price_reduce_taxinc"
t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.pricelist_id.currency_id}"
t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.currency_id}"
groups="account.group_show_line_subtotals_tax_included"/>
<span itemprop="price" class="d-none" t-out="ticket.price"/>
<span itemprop="priceCurrency" class="d-none" t-out="website.pricelist_id.currency_id.name"/>
<span itemprop="priceCurrency" class="d-none" t-out="website.currency_id.name"/>
</t>
<span t-else="" class="fw-bold text-uppercase">Free</span>
</xpath>
@@ -36,10 +36,10 @@
<t t-if="highest_price > 0">
<span class="text-dark">
From
<span t-out="lowest_price" t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.pricelist_id.currency_id}"/>
<span t-out="lowest_price" t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.currency_id}"/>
<t t-if="lowest_price != highest_price">
to
<span t-out="highest_price" t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.pricelist_id.currency_id}"/>
<span t-out="highest_price" t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.currency_id}"/>
</t>
</span>
</t>
@@ -48,17 +48,17 @@
<xpath expr="//div[hasclass('o_wevent_registration_single')]//h6" position="after">
<div class="px-2 text-dark d-flex align-items-center align-self-stretch">
<t t-if="tickets.price">
<t t-if="(tickets.price - tickets.price_reduce) &gt;1 and website.get_current_pricelist().discount_policy == 'without_discount'">
<del class="text-danger me-1" t-field="tickets.price" t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.pricelist_id.currency_id}"/>
<t t-if="(tickets.price - tickets.price_reduce) &gt;1 and website.pricelist_id and website.get_current_pricelist().discount_policy == 'without_discount'">
<del class="text-danger me-1" t-field="tickets.price" t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.currency_id}"/>
</t>
<span t-field="tickets.price_reduce"
t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.pricelist_id.currency_id}"
t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.currency_id}"
groups="account.group_show_line_subtotals_tax_excluded"/>
<span t-field="tickets.price_reduce_taxinc"
t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.pricelist_id.currency_id}"
t-options="{'widget': 'monetary', 'from_currency': event.company_id.sudo().currency_id, 'display_currency': website.currency_id}"
groups="account.group_show_line_subtotals_tax_included"/>
<span itemprop="price" class="d-none" t-out="tickets.price"/>
<span itemprop="priceCurrency" class="d-none" t-out="website.pricelist_id.currency_id.name"/>
<span itemprop="priceCurrency" class="d-none" t-out="website.currency_id.name"/>
</t>
<span t-else="" class="fw-bold text-uppercase">Free</span>
</div>
+7 -7
View File
@@ -215,7 +215,7 @@ class WebsiteSale(http.Controller):
yield {'loc': loc}
def _get_search_options(
self, category=None, attrib_values=None, pricelist=None, min_price=0.0, max_price=0.0, conversion_rate=1, **post
self, category=None, attrib_values=None, min_price=0.0, max_price=0.0, conversion_rate=1, **post
):
return {
'displayDescription': True,
@@ -228,7 +228,7 @@ class WebsiteSale(http.Controller):
'min_price': min_price / conversion_rate,
'max_price': max_price / conversion_rate,
'attrib_values': attrib_values,
'display_currency': pricelist.currency_id,
'display_currency': post.get('display_currency'),
}
def _shop_lookup_products(self, attrib_set, options, post, search, website):
@@ -345,9 +345,9 @@ class WebsiteSale(http.Controller):
keep = QueryURL('/shop', **self._shop_get_query_url_kwargs(category and int(category), search, min_price, max_price, **post))
now = datetime.timestamp(datetime.now())
pricelist = request.env['product.pricelist'].browse(request.session.get('website_sale_current_pl'))
if not pricelist or request.session.get('website_sale_pricelist_time', 0) < now - 60*60: # test: 1 hour in session
pricelist = website.get_current_pricelist()
pricelist = website.get_current_pricelist()
if request.session.get('website_sale_pricelist_time', 0) < now - 60*60: # test: 1 hour in session
pricelist = website.pricelist_id
request.session['website_sale_pricelist_time'] = now
request.session['website_sale_current_pl'] = pricelist.id
@@ -357,7 +357,7 @@ class WebsiteSale(http.Controller):
if filter_by_price_enabled:
company_currency = website.company_id.currency_id
conversion_rate = request.env['res.currency']._get_conversion_rate(
company_currency, pricelist.currency_id, request.website.company_id, fields.Date.today())
company_currency, website.currency_id, request.website.company_id, fields.Date.today())
else:
conversion_rate = 1
@@ -370,10 +370,10 @@ class WebsiteSale(http.Controller):
options = self._get_search_options(
category=category,
attrib_values=attrib_values,
pricelist=pricelist,
min_price=min_price,
max_price=max_price,
conversion_rate=conversion_rate,
display_currency=website.currency_id,
**post
)
fuzzy_search_term, product_count, search_product = self._shop_lookup_products(attrib_set, options, post, search, website)
-5
View File
@@ -51,11 +51,6 @@
<field name="salesteam_id" ref="sales_team.salesteam_website_sales"/>
</record>
<record model="product.pricelist" id="product.list0">
<field name="selectable" eval="True" />
<field name="website_id" eval="False"/>
</record>
</data>
<data>
<!-- Filters for Dynamic Filter -->
+5 -33
View File
@@ -116,16 +116,6 @@
<field name="accessory_product_ids" eval="[(6, 0, [ref('product.product_product_7')])]"/>
</record>
<record id="item1" model="product.pricelist.item">
<field name="base">list_price</field>
<field name="applied_on">1_product</field>
<field name="pricelist_id" ref="product.list0"/>
<field name="product_tmpl_id" ref="product.product_product_4_product_template"/>
<field name="price_discount">20</field>
<field name="min_quantity">2</field>
<field name="compute_price">formula</field>
</record>
<!-- product.public.category -->
<record id="public_category_desks" model="product.public.category">
@@ -268,18 +258,17 @@
<field name="public_categ_ids" eval="[(6,0,[ref('public_category_lamps')])]"/>
</record>
<record model="product.pricelist" id="product.list0">
<field name="selectable" eval="True" />
<field name="sequence">3</field>
<field name="currency_id" ref="base.USD"/>
</record>
<record id="benelux" model="res.country.group">
<field name="name">BeNeLux</field>
<field name="country_ids" eval="[(6,0,[
ref('base.be'),ref('base.lu'),ref('base.nl')])]"/>
</record>
<!-- Since we are adding pricelists, we activate the feature -->
<record id="base.group_user" model="res.groups">
<field name="implied_ids" eval="[(4, ref('product.group_product_pricelist'))]"/>
</record>
<record id="list_christmas" model="product.pricelist">
<field name="name">Christmas</field>
<field name="selectable" eval="False" />
@@ -325,7 +314,6 @@
</record>
<record id="item_us" model="product.pricelist.item">
<field name="pricelist_id" ref="product.list0"/>
<field name="compute_price">formula</field>
<field name="base">list_price</field>
</record>
@@ -337,7 +325,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.salesteam_website_sales"/>
<field name="date_order" eval="(datetime.now()-relativedelta(days=7)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="state">sale</field>
@@ -358,7 +345,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.salesteam_website_sales"/>
<field name="date_order" eval="(datetime.now()-relativedelta(days=6)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="state">sale</field>
@@ -379,7 +365,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.salesteam_website_sales"/>
<field name="date_order" eval="(datetime.now()-relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="tag_ids" eval="[(4, ref('sales_team.categ_oppor2'))]"/>
@@ -401,7 +386,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.salesteam_website_sales"/>
<field name="date_order" eval="(datetime.now()-relativedelta(days=4)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="state">sale</field>
@@ -422,7 +406,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.salesteam_website_sales"/>
<field name="date_order" eval="(datetime.now()-relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="state">sale</field>
@@ -443,7 +426,6 @@
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.salesteam_website_sales"/>
<field name="date_order" eval="(datetime.now()-relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="state">sale</field>
@@ -465,7 +447,6 @@
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="website_id" ref="website.default_website"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.salesteam_website_sales"/>
<field name="date_order" eval="(datetime.now()-relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="state">sale</field>
@@ -486,7 +467,6 @@
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="website_id" ref="website.default_website"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.salesteam_website_sales"/>
<field name="date_order" eval="datetime.now()"/>
<field name="tag_ids" eval="[(4, ref('sales_team.categ_oppor1'))]"/>
@@ -508,7 +488,6 @@
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="website_id" ref="website.default_website"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.salesteam_website_sales"/>
<field name="date_order" eval="(datetime.now()-relativedelta(hours=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
@@ -538,7 +517,6 @@
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="website_id" ref="website.default_website"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.salesteam_website_sales"/>
<field name="date_order" eval="datetime.now()"/>
<field name="tag_ids" eval="[(4, ref('sales_team.categ_oppor5'))]"/>
@@ -560,7 +538,6 @@
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="website_id" ref="website.default_website"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.salesteam_website_sales"/>
<field name="date_order" eval="(datetime.now()-timedelta(hours=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
@@ -581,7 +558,6 @@
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="website_id" ref="website.default_website"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.salesteam_website_sales"/>
<field name="payment_term_id" ref="account.account_payment_term_immediate"/>
<field name="date_order" eval="(datetime.now()-timedelta(hours=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
@@ -604,7 +580,6 @@
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="website_id" ref="website.default_website"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.salesteam_website_sales"/>
</record>
@@ -624,7 +599,6 @@
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="website_id" ref="website.default_website"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.salesteam_website_sales"/>
<field name="date_order" eval="datetime.now()-relativedelta(months=1)"/>
<field name="state">sale</field>
@@ -646,7 +620,6 @@
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="website_id" ref="website.default_website"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.salesteam_website_sales"/>
<field name="date_order" eval="datetime.now()-relativedelta(months=1, days=2)"/>
</record>
@@ -668,7 +641,6 @@
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
<field name="user_id" ref="base.user_demo"/>
<field name="website_id" ref="website.default_website"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="team_id" ref="sales_team.salesteam_website_sales"/>
<field name="date_order" eval="datetime.now()-relativedelta(months=2)"/>
</record>
@@ -39,14 +39,11 @@ class ProductPricelist(models.Model):
def write(self, data):
res = super(ProductPricelist, self).write(data)
if data.keys() & {'code', 'active', 'website_id', 'selectable', 'company_id'}:
self._check_website_pricelist()
self and self.clear_caches()
return res
def unlink(self):
res = super(ProductPricelist, self).unlink()
self._check_website_pricelist()
self and self.clear_caches()
return res
@@ -64,12 +61,6 @@ class ProductPricelist(models.Model):
res = res.filtered(lambda pl: pl._is_available_on_website(website))
return res
def _check_website_pricelist(self):
for website in self.env['website'].search([]):
# sudo() to be able to read pricelists/website from another company
if not website.sudo().pricelist_ids:
raise UserError(_("With this action, '%s' website would not have any pricelist available.") % (website.name))
def _is_available_on_website(self, website):
""" To be able to be used on a website, a pricelist should either:
- Have its `website_id` set to current website (specific pricelist).
@@ -85,7 +76,7 @@ class ProductPricelist(models.Model):
self.ensure_one()
if self.company_id and self.company_id != website.company_id:
return False
return self.website_id.id == website.id or (not self.website_id and (self.selectable or self.sudo().code))
return self.active and self.website_id.id == website.id or (not self.website_id and (self.selectable or self.sudo().code))
def _is_available_in_country(self, country_code):
self.ensure_one()
+19 -15
View File
@@ -165,17 +165,19 @@ class ProductTemplate(models.Model):
return self._get_possible_variants(parent_combination).sorted(_sort_key_variant)
def _get_sales_prices(self, pricelist):
pricelist.ensure_one()
pricelist and pricelist.ensure_one()
partner_sudo = self.env.user.partner_id
pricelist = pricelist or self.env['product.pricelist']
currency = pricelist.currency_id or self.env.company.currency_id
# Try to fetch geoip based fpos or fallback on partner one
fpos_id = self.env['website']._get_current_fiscal_position_id(partner_sudo)
fiscal_position = self.env['account.fiscal.position'].sudo().browse(fpos_id)
sales_prices = pricelist._get_products_price(self, 1.0)
show_discount = pricelist.discount_policy == 'without_discount'
show_discount = pricelist and pricelist.discount_policy == 'without_discount'
base_sales_prices = self.price_compute('list_price', currency=pricelist.currency_id)
base_sales_prices = self.price_compute('list_price', currency=currency)
res = {}
for template in self:
@@ -188,7 +190,7 @@ class ProductTemplate(models.Model):
price_reduce, product_taxes, taxes, self.env.company)
tax_display = self.user_has_groups('account.group_show_line_subtotals_tax_excluded') and 'total_excluded' or 'total_included'
price_reduce = taxes.compute_all(price_reduce, pricelist.currency_id, 1, template, partner_sudo)[tax_display]
price_reduce = taxes.compute_all(price_reduce, currency, 1, template, partner_sudo)[tax_display]
template_price_vals = {
'price_reduce': price_reduce
@@ -208,7 +210,7 @@ class ProductTemplate(models.Model):
if base_price and base_price != price_reduce:
base_price = self.env['account.tax']._fix_tax_included_price_company(
base_price, product_taxes, taxes, self.env.company)
base_price = taxes.compute_all(base_price, pricelist.currency_id, 1, template, partner_sudo)[
base_price = taxes.compute_all(base_price, currency, 1, template, partner_sudo)[
tax_display]
template_price_vals['base_price'] = base_price
if base_price > price_reduce:
@@ -234,7 +236,7 @@ class ProductTemplate(models.Model):
def _get_combination_info(self, combination=False, product_id=False, add_qty=1, pricelist=False, parent_combination=False, only_template=False):
"""Override for website, where we want to:
- take the website pricelist if no pricelist is set
- take the website pricelist if no pricelist is set and one is available
- apply the b2b/b2c setting to the result
This will work when adding website_id to the context, which is done
@@ -257,6 +259,7 @@ class ProductTemplate(models.Model):
product = self.env['product.product'].browse(combination_info['product_id']) or self
partner = self.env.user.partner_id
company_id = current_website.company_id
currency = current_website.currency_id
fpos_id = self.env['website'].sudo()._get_current_fiscal_position_id(partner)
fiscal_position = self.env['account.fiscal.position'].sudo().browse(fpos_id)
@@ -264,28 +267,29 @@ class ProductTemplate(models.Model):
taxes = fiscal_position.map_tax(product_taxes)
price = self._price_with_tax_computed(
combination_info['price'], product_taxes, taxes, company_id, pricelist, product,
combination_info['price'], product_taxes, taxes, company_id, currency, product,
partner
)
if pricelist.discount_policy == 'without_discount':
list_price = self._price_with_tax_computed(
combination_info['list_price'], product_taxes, taxes, company_id, pricelist,
combination_info['list_price'], product_taxes, taxes, company_id, currency,
product, partner
)
else:
list_price = price
price_extra = self._price_with_tax_computed(
combination_info['price_extra'], product_taxes, taxes, company_id, pricelist,
combination_info['price_extra'], product_taxes, taxes, company_id, currency,
product, partner
)
base_unit_price = product._get_base_unit_price(list_price)
if pricelist.currency_id != product.currency_id:
base_unit_price = pricelist.currency_id._convert(
if currency != product.currency_id:
base_unit_price = product.currency_id._convert(
base_unit_price,
pricelist.currency_id,
currency,
company_id,
fields.Date.today())
has_discounted_price = pricelist.currency_id.compare_amounts(list_price, price) == 1
has_discounted_price = currency.compare_amounts(list_price, price) == 1
prevent_zero_price_sale = not price and current_website.prevent_zero_price_sale
compare_list_price = self.compare_list_price
@@ -307,7 +311,7 @@ class ProductTemplate(models.Model):
return combination_info
def _price_with_tax_computed(
self, price, product_taxes, taxes, company_id, pricelist, product, partner
self, price, product_taxes, taxes, company_id, currency, product, partner
):
price = self.env['account.tax']._fix_tax_included_price_company(
price, product_taxes, taxes, company_id
@@ -315,7 +319,7 @@ class ProductTemplate(models.Model):
show_tax_excluded = self.user_has_groups('account.group_show_line_subtotals_tax_excluded')
tax_display = 'total_excluded' if show_tax_excluded else 'total_included'
# The list_price is always the price of one.
return taxes.compute_all(price, pricelist.currency_id, 1, product, partner)[tax_display]
return taxes.compute_all(price, currency, 1, product, partner)[tax_display]
def _get_image_holder(self):
"""Returns the holder of the image to use as default representation.
+13
View File
@@ -15,3 +15,16 @@ class ResCompany(models.Model):
self.env.company.payment_onboarding_payment_method = 'stripe'
menu_id = self.env.ref('website.menu_website_dashboard').id
return self._run_payment_onboarding_step(menu_id)
def _get_default_pricelist_vals(self):
""" Override of product. Called at company creation or activation of the pricelist setting.
We don't want the default website from the current company to be applied on every company
Note: self.ensure_one()
:rtype: dict
"""
values = super()._get_default_pricelist_vals()
values['website_id'] = False
return values
+9 -11
View File
@@ -28,12 +28,9 @@ class Website(models.Model):
default=_default_salesteam_id)
pricelist_id = fields.Many2one(
'product.pricelist',
compute='_compute_pricelist_id',
string='Default Pricelist')
'product.pricelist', compute='_compute_pricelist_id', string="Default Pricelist if any")
currency_id = fields.Many2one(
related='pricelist_id.currency_id', depends=(), related_sudo=False,
string='Default Currency', readonly=False)
'res.currency', compute='_compute_currency_id', string="Default Currency")
pricelist_ids = fields.One2many('product.pricelist', compute="_compute_pricelist_ids",
string='Price list available for this Ecommerce/Website')
# Technical: Used to recompute pricelist_ids
@@ -125,6 +122,11 @@ class Website(models.Model):
# Because most of it must still be computed for the pricelist choice template (`pricelist_list`)
# Therefore, avoiding all pricelist computation is impossible in fact...
@api.depends('all_pricelist_ids', 'pricelist_id', 'company_id')
def _compute_currency_id(self):
for website in self:
website.currency_id = website.pricelist_id.currency_id or website.company_id.currency_id
# This method is cached, must not return records! See also #8795
@tools.ormcache(
'country_code', 'show_visible',
@@ -276,11 +278,6 @@ class Website(models.Model):
# then this special pricelist is amongs these available pricelists, and therefore it won't fall in this case.
pricelist = available_pricelists[0]
if not pricelist:
_logger.error(
'Failed to find pricelist for partner "%s" (id %s)',
partner_sudo.name, partner_sudo.id,
)
return pricelist
def sale_product_domain(self):
@@ -310,7 +307,8 @@ class Website(models.Model):
sale_order_sudo = self.env.user.partner_id.last_website_so_id
if sale_order_sudo:
available_pricelists = self.get_pricelist_available()
if sale_order_sudo.pricelist_id not in available_pricelists:
so_pricelist_sudo = sale_order_sudo.pricelist_id
if so_pricelist_sudo and so_pricelist_sudo not in available_pricelists:
# Do not reload the cart of this user last visit
# if the cart uses a pricelist no longer available.
sale_order_sudo = SaleOrder
@@ -14,8 +14,8 @@ class WebsiteSnippetFilter(models.Model):
@api.model
def _get_website_currency(self):
pricelist = self.env['website'].get_current_website().get_current_pricelist()
return pricelist.currency_id
website = self.env['website'].get_current_website()
return website.currency_id
def _get_hardcoded_sample(self, model):
samples = super()._get_hardcoded_sample(model)
+17 -16
View File
@@ -3,6 +3,7 @@
import base64
from odoo.addons.base.tests.common import HttpCaseWithUserDemo, HttpCaseWithUserPortal
from odoo.fields import Command
from odoo.modules.module import get_module_resource
from odoo.tests import tagged
@@ -146,22 +147,18 @@ class TestUi(HttpCaseWithUserDemo, HttpCaseWithUserPortal):
'list_price': 12.0,
})
# fix runbot, sometimes one pricelist is chosen, sometimes the other...
pricelists = self.env['website'].get_current_website().get_current_pricelist() | self.env.ref('product.list0')
for pricelist in pricelists:
if not pricelist.item_ids.filtered(lambda i: i.product_tmpl_id == product_template and i.price_discount == 20):
self.env['product.pricelist.item'].create({
'base': 'list_price',
'applied_on': '1_product',
'pricelist_id': pricelist.id,
'product_tmpl_id': product_template.id,
'price_discount': 20,
'min_quantity': 2,
'compute_price': 'formula',
})
pricelist.discount_policy = 'without_discount'
self.env['product.pricelist'].create({
'name': 'Base Pricelist',
'discount_policy': 'without_discount',
'item_ids': [Command.create({
'base': 'list_price',
'applied_on': '1_product',
'product_tmpl_id': product_template.id,
'price_discount': 20,
'min_quantity': 2,
'compute_price': 'formula',
})],
})
self.start_tour("/", 'shop_custom_attribute_value', login="admin")
@@ -318,6 +315,10 @@ class TestUi(HttpCaseWithUserDemo, HttpCaseWithUserPortal):
self.start_tour(self.env['website'].get_client_action_url('/shop?search=Test Product'), 'shop_list_view_b2c', login="admin")
def test_07_editor_shop(self):
self.env['product.pricelist'].create([
{'name': 'Base Pricelist', 'selectable': True},
{'name': 'Other Pricelist', 'selectable': True}
])
self.start_tour("/", 'shop_editor', login="admin")
def test_08_portal_tour_archived_variant_multiple_attributes(self):
@@ -14,6 +14,7 @@ class TestUi(HttpCaseWithUserDemo):
def setUp(self):
super(TestUi, self).setUp()
self.env['product.pricelist'].sudo().search([]).action_archive()
product_product_7 = self.env['product.product'].create({
'name': 'Storage Box',
'standard_price': 70.0,
@@ -23,6 +23,8 @@ class TestWebsiteSaleImage(odoo.tests.HttpCase):
color_blue = '#4169E1'
name_blue = 'Royal Blue'
self.env['product.pricelist'].sudo().search([]).action_archive()
# create the color attribute
product_attribute = self.env['product.attribute'].create({
'name': 'Beautiful Color',
@@ -210,16 +212,9 @@ class TestWebsiteSaleImage(odoo.tests.HttpCase):
[('key', '=', 'website_sale.product_picture_magnify_click')]
).write({'active': True})
# Ensure that only one pricelist is available during the test, with the company currency.
# Ensure that no pricelist is available during the test.
# This ensures that tours with triggers on the amounts will run properly.
# To this purpose, we will ensure that only the public_pricelist is available for the default_website.
public_pricelist = self.env.ref('product.list0')
default_website = self.env.ref('website.default_website')
self.env['product.pricelist'].search([
('id', '!=', public_pricelist.id),
('website_id', 'in', [False, default_website.id])]
).website_id = self.env.ref('website.website2')
public_pricelist.currency_id = self.env.company.currency_id
self.env['product.pricelist'].search([]).action_archive()
self.start_tour("/", 'shop_zoom', login="admin")
@@ -36,7 +36,8 @@ class TestWebsitePriceList(TransactionCase):
self.website = self.env.ref('website.default_website')
self.website.user_id = self.env.user
(self.env['product.pricelist'].search([]) - self.env.ref('product.list0')).write({'website_id': False, 'active': False})
self.env['product.pricelist'].search([]).action_archive()
self.env['product.pricelist'].create({'name': 'Public Pricelist'})
self.benelux = self.env['res.country.group'].create({
'name': 'BeNeLux',
'country_ids': [(6, 0, (self.env.ref('base.be') + self.env.ref('base.lu') + self.env.ref('base.nl')).ids)]
@@ -84,8 +85,6 @@ class TestWebsitePriceList(TransactionCase):
'compute_price': 'formula',
'base': 'list_price',
})
self.env.ref('product.list0').website_id = self.website.id
self.website.pricelist_id = self.ref('product.list0')
ca_group = self.env['res.country.group'].create({
'name': 'Canada',
@@ -228,7 +227,8 @@ class TestWebsitePriceList(TransactionCase):
'product_uom': product.uom_id.id,
'price_unit': product.list_price,
'tax_id': False,
})]
})],
'website_id': current_website.id,
})
sol = so.order_line
self.assertEqual(sol.price_total, 100.0)
@@ -12,10 +12,13 @@ class WebsiteSaleProductTests(TestSaleProductAttributeValueCommon):
def test_website_sale_contextual_price(self):
contextual_price = self.computer._get_contextual_price()
self.assertEqual(0.0, contextual_price, "With no pricelist context, the contextual price should be 0.")
self.assertEqual(
self.computer.list_price,
contextual_price,
"With no pricelist context, the contextual price should be the computer list price."
)
current_website = self.env['website'].get_current_website()
pricelist = current_website.get_current_pricelist()
pricelist = self.env['product.pricelist'].create({'name': 'Base Pricelist'})
# make sure the pricelist has a 10% discount
self.env['product.pricelist.item'].create({
@@ -173,8 +173,7 @@ class TestWebsiteSaleProductPricelist(AccountTestInvoicingCommon, TestSaleProduc
# We will test that the mapping of an 10% included tax by a 6% by a fiscal position is taken into account when updating the cart
self.env.user.partner_id.country_id = False
current_website = self.env['website'].get_current_website()
pricelist = current_website.get_current_pricelist()
(self.env['product.pricelist'].search([]) - pricelist).write({'active': False})
pricelist = self.env['product.pricelist'].create({'name': 'Base Pricelist'})
# Add 10% tax on product
tax10 = self.env['account.tax'].create({'name': "Test tax 10", 'amount': 10, 'price_include': True, 'amount_type': 'percent'})
tax6 = self.env['account.tax'].create({'name': "Test tax 6", 'amount': 6, 'price_include': True, 'amount_type': 'percent'})
@@ -185,7 +184,7 @@ class TestWebsiteSaleProductPricelist(AccountTestInvoicingCommon, TestSaleProduc
'taxes_id': [(6, 0, [tax10.id])],
}).with_context(website_id=current_website.id)
# Add discout of 50% for pricelist
# Add discount of 50% for pricelist
pricelist.item_ids = self.env['product.pricelist.item'].create({
'applied_on': "1_product",
'base': "list_price",
@@ -214,7 +213,6 @@ class TestWebsiteSaleProductPricelist(AccountTestInvoicingCommon, TestSaleProduc
})
self.assertEqual(round(sol.price_total), 55.0, "110$ with 50% discount 10% included tax")
self.assertEqual(round(sol.price_tax), 5.0, "110$ with 50% discount 10% included tax")
so.pricelist_id = pricelist
so.fiscal_position_id = fpos
sol._compute_tax_id()
with MockRequest(self.env, website=current_website, sale_order_id=so.id):
@@ -225,8 +223,6 @@ class TestWebsiteSaleProductPricelist(AccountTestInvoicingCommon, TestSaleProduc
# We will test that the mapping of an 10% included tax by a 0% by a fiscal position is taken into account when updating the cart for no_variant product
self.env.user.partner_id.country_id = False
current_website = self.env['website'].get_current_website()
pricelist = current_website.get_current_pricelist()
(self.env['product.pricelist'].search([]) - pricelist).write({'active': False})
# Add 10% tax on product
tax10 = self.env['account.tax'].create({'name': "Test tax 10", 'amount': 10, 'price_include': True, 'amount_type': 'percent', 'type_tax_use': 'sale'})
tax0 = self.env['account.tax'].create({'name': "Test tax 0", 'amount': 0, 'price_include': True, 'amount_type': 'percent', 'type_tax_use': 'sale'})
@@ -289,7 +285,6 @@ class TestWebsiteSaleProductPricelist(AccountTestInvoicingCommon, TestSaleProduc
'tax_id': [(6, 0, [tax10.id])],
})
self.assertEqual(round(sol.price_total), 110.0, "110$ with 10% included tax")
so.pricelist_id = pricelist
so.fiscal_position_id = fpos
sol._compute_tax_id()
with MockRequest(self.env, website=current_website, sale_order_id=so.id):
+6 -6
View File
@@ -854,9 +854,9 @@
</label>
<input type="range" multiple="multiple"
t-attf-class="form-range range-with-input {{_classes_input}}"
t-att-data-currency="pricelist.currency_id.symbol"
t-att-data-currency-position="pricelist.currency_id.position"
t-att-step="pricelist.currency_id.rounding" t-att-min="'%f' % (available_min_price)"
t-att-data-currency="website.currency_id.symbol"
t-att-data-currency-position="website.currency_id.position"
t-att-step="website.currency_id.rounding" t-att-min="'%f' % (available_min_price)"
t-att-max="'%f' % (available_max_price)" t-att-value="'%f,%f' % (min_price, max_price)"/>
</div>
</template>
@@ -2079,7 +2079,7 @@
<div class="card">
<div class="card-body p-xl-0">
<div class="toggle_summary d-xl-none">
<b>Your order: </b> <span id="amount_total_summary" class="monetary_field" t-field="website_sale_order.amount_total" t-options='{"widget": "monetary", "display_currency": website_sale_order.pricelist_id.currency_id}'/>
<b>Your order: </b> <span id="amount_total_summary" class="monetary_field" t-field="website_sale_order.amount_total" t-options='{"widget": "monetary", "display_currency": website_sale_order.currency_id}'/>
<span class='fa fa-chevron-down fa-border float-end' role="img" aria-label="Details" title="Details"></span>
</div>
<div t-if="not website_sale_order or not website_sale_order.website_order_line" class="alert alert-info">
@@ -2180,7 +2180,7 @@
<strong>Total:</strong>
</td>
<td class="text-end" width="100">
<strong t-field="payment_tx_id.amount" t-options="{'widget': 'monetary', 'display_currency': order.pricelist_id.currency_id}" />
<strong t-field="payment_tx_id.amount" t-options="{'widget': 'monetary', 'display_currency': order.currency_id}" />
</td>
</tr>
</tbody>
@@ -2234,7 +2234,7 @@
<td class="text-end border-top border-bottom-0"><strong>Total:</strong></td>
<td class="text-xl-end border-top border-bottom-0">
<strong t-field="website_sale_order.amount_total" class="monetary_field"
t-options='{"widget": "monetary", "display_currency": website_sale_order.pricelist_id.currency_id}'/>
t-options='{"widget": "monetary", "display_currency": website_sale_order.currency_id}'/>
</td>
</tr>
</table>
@@ -10,16 +10,6 @@ from odoo.addons.sale.tests.test_sale_product_attribute_value_config import Test
@tagged('post_install', '-at_install')
class TestUi(TestSaleProductAttributeValueCommon, HttpCase):
@classmethod
def setUpClass(cls):
super(TestUi, cls).setUpClass()
# set currency to not rely on demo data and avoid possible race condition
cls.currency_ratio = 1.0
pricelist = cls.env.ref('product.list0')
new_currency = cls._setup_currency(cls.currency_ratio)
pricelist.currency_id = new_currency
cls.env.flush_all()
def test_01_admin_shop_sale_loyalty_tour(self):
# pre enable "Show # found" option to avoid race condition...
public_category = self.env['product.public.category'].create({'name': 'Public Category'})
@@ -14,10 +14,7 @@ tour.register("a_shop_custom_attribute_value", {
trigger: 'a.js_add_cart_json:has(i.fa-plus)',
run: 'click',
}, {
trigger: 'span.text-danger span:contains(750)',
run: function (){}, // check
}, {
trigger: 'span.oe_price span:contains(600)',
trigger: 'span.oe_currency_value:contains(750)',
run: function (){}, // check
}, {
id: 'add_cart_step',
@@ -53,7 +50,7 @@ tour.register("a_shop_custom_attribute_value", {
extra_trigger: '.oe_advanced_configurator_modal .js_product:has(strong:contains(Chair floor protection))',
run: 'click'
}, {
trigger: 'span:contains(1,257.00)',
trigger: 'span:contains(1,557.00)',
run: function (){}, // check
}, {
trigger: 'button:has(span:contains(Proceed to Checkout))',
@@ -9,15 +9,7 @@ from odoo.tests import tagged
class TestUi(HttpCase, TestProductConfiguratorCommon):
def test_01_admin_shop_custom_attribute_value_tour(self):
# Ensure that only one pricelist is available during the test, with the company currency.
# This ensures that tours with triggers on the amounts will run properly.
# To this purpose, we will ensure that only the public_pricelist is available for the default_website.
public_pricelist = self.env.ref('product.list0')
default_website = self.env.ref('website.default_website')
self.env['product.pricelist'].search([
('id', '!=', public_pricelist.id),
('website_id', 'in', [False, default_website.id])]
).website_id = self.env.ref('website.website2')
public_pricelist.currency_id = self.env.company.currency_id
self._create_pricelist(public_pricelist)
# Ensure that no pricelist is available during the test.
# This ensures that tours which triggers on the amounts will run properly.
self.env['product.pricelist'].search([]).action_archive()
self.start_tour("/", 'a_shop_custom_attribute_value', login="admin")
@@ -31,14 +31,7 @@ class TestWebsiteSaleProductConfigurator(TestProductConfiguratorCommon, HttpCase
# in this case. However, we still want to make sure that the correct
# variant attributes are taken into account when calculating the price.
url = self.product_product_custo_desk.website_url
# Ensure that only one pricelist is available during the test, with the company currency.
# Ensure that no pricelist is available during the test.
# This ensures that tours with triggers on the amounts will run properly.
# To this purpose, we will ensure that only the public_pricelist is available for the default_website.
public_pricelist = self.env.ref('product.list0')
default_website = self.env.ref('website.default_website')
self.env['product.pricelist'].search([
('id', '!=', public_pricelist.id),
('website_id', 'in', [False, default_website.id])]
).website_id = self.env.ref('website.website2')
public_pricelist.currency_id = self.env.company.currency_id
self.env['product.pricelist'].search([]).action_archive()
self.start_tour(url, 'website_sale_product_configurator_optional_products_tour', login='portal')
@@ -10,7 +10,6 @@
<field name="partner_invoice_id" ref="base.partner_demo"/>
<field name="partner_shipping_id" ref="base.partner_demo"/>
<field name="user_id" ref="base.user_admin"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="date_order" eval="DateTime.now() - relativedelta(days=30)"/>
<field name="team_id" ref="sales_team.salesteam_website_sales"/>
<field name="website_id" eval="1"/>
@@ -32,7 +31,6 @@
<field name="partner_invoice_id" ref="base.partner_demo_portal"/>
<field name="partner_shipping_id" ref="base.partner_demo_portal"/>
<field name="user_id" ref="base.user_admin"/>
<field name="pricelist_id" ref="product.list0"/>
<field name="date_order" eval="DateTime.now() - relativedelta(days=100)"/>
<field name="team_id" ref="sales_team.salesteam_website_sales"/>
<field name="website_id" eval="1"/>
@@ -8,6 +8,6 @@ from odoo.addons.website_sale.controllers.variant import WebsiteSaleVariantContr
class WebsiteSaleStockVariantController(WebsiteSaleVariantController):
@http.route()
def get_combination_info_website(self, product_template_id, product_id, combination, add_qty, **kw):
kw['context'] = kw.get('context', {})
kw['context'] = kw.get('context') or {} # context may be passed as None in some cases
kw['context'].update(website_sale_stock_get_quantity=True)
return super(WebsiteSaleStockVariantController, self).get_combination_info_website(product_template_id, product_id, combination, add_qty, **kw)
@@ -8,6 +8,6 @@ from odoo.addons.website_sale.controllers.variant import WebsiteSaleVariantContr
class WebsiteSaleStockWishlistVariantController(WebsiteSaleVariantController):
@http.route()
def get_combination_info_website(self, product_template_id, product_id, combination, add_qty, **kw):
kw['context'] = kw.get('context', {})
kw['context'] = kw.get('context') or {} # context may be passed as None in some cases
kw['context'].update(website_sale_stock_wishlist_get_wish=True)
return super().get_combination_info_website(product_template_id, product_id, combination, add_qty, **kw)
@@ -14,9 +14,7 @@ class WebsiteSaleWishlist(WebsiteSale):
pricelist = website.pricelist_id
product = request.env['product.product'].browse(product_id)
price = product._get_combination_info_variant(
pricelist=website.pricelist_id,
)['price']
price = product._get_combination_info_variant(pricelist=website.pricelist_id)['price']
Wishlist = request.env['product.wishlist']
if request.website.is_public_user():
@@ -15,7 +15,7 @@ class ProductWishlist(models.Model):
partner_id = fields.Many2one('res.partner', string='Owner')
product_id = fields.Many2one('product.product', string='Product', required=True)
currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id', readonly=True)
currency_id = fields.Many2one('res.currency', related='website_id.currency_id', readonly=True)
pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', help='Pricelist when added')
price = fields.Monetary(currency_field='currency_id', string='Price', help='Price of the product when it has been added in the wishlist')
website_id = fields.Many2one('website', ondelete='cascade', required=True)
@@ -192,7 +192,7 @@
<span t-field="website.prevent_zero_price_sale_text"/>
</td>
<td class="align-middle o_wish_price" t-else="">
<t t-esc="combination_info['price']" t-options="{'widget': 'monetary', 'display_currency': website.pricelist_id.currency_id}"/>
<t t-esc="combination_info['price']" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"/>
<small t-if="combination_info['base_unit_price']" class="cart_product_base_unit_price d-block text-muted" groups="website_sale.group_show_uom_price">
<t t-call='website_sale.base_unit_price'><t t-set='product' t-value='wish.product_id' /></t>
</small>
+1 -1
View File
@@ -427,7 +427,7 @@ class TestUsersGroupWarning(TransactionCase):
# 97 requests if only base is installed
# 412 runbot community
# 549 runbot enterprise
with self.assertQueryCount(__system__=436), \
with self.assertQueryCount(__system__=437), \
Form(self.test_group_user.with_context(show_user_group_warning=True), view='base.view_users_form') as UserForm:
UserForm._values[self.sales_categ_field] = self.group_sales_user.id
UserForm._perform_onchange([self.sales_categ_field])