[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:
@@ -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">
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+12
-2
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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'))]"/>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -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:
|
||||
|
||||
@@ -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) > 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) > 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) >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) >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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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'})
|
||||
|
||||
+2
-5
@@ -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>
|
||||
|
||||
@@ -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])
|
||||
|
||||
Reference in New Issue
Block a user