mirror of
https://gitlab.com/flectra-hq/flectra.git
synced 2025-02-25 18:55:21 -06:00
[PATCH] Upstream patch - 24032022
This commit is contained in:
@@ -715,7 +715,7 @@ class AccountBankStatementLine(models.Model):
|
||||
**counterpart_vals,
|
||||
'name': counterpart_vals.get('name', move_line.name if move_line else ''),
|
||||
'move_id': self.move_id.id,
|
||||
'partner_id': self.partner_id.id or (move_line.partner_id.id if move_line else False),
|
||||
'partner_id': self.partner_id.id or counterpart_vals.get('partner_id', move_line.partner_id.id if move_line else False),
|
||||
'currency_id': currency_id,
|
||||
'account_id': counterpart_vals.get('account_id', move_line.account_id.id if move_line else False),
|
||||
'debit': balance if balance > 0.0 else 0.0,
|
||||
|
||||
@@ -7,6 +7,7 @@ from ldap.filter import filter_format
|
||||
|
||||
from flectra import _, api, fields, models, tools
|
||||
from flectra.exceptions import AccessDenied
|
||||
from flectra.tools.misc import str2bool
|
||||
from flectra.tools.pycompat import to_text
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -74,6 +75,9 @@ class CompanyLDAP(models.Model):
|
||||
uri = 'ldap://%s:%d' % (conf['ldap_server'], conf['ldap_server_port'])
|
||||
|
||||
connection = ldap.initialize(uri)
|
||||
ldap_chase_ref_disabled = self.env['ir.config_parameter'].sudo().get_param('auth_ldap.disable_chase_ref')
|
||||
if str2bool(ldap_chase_ref_disabled):
|
||||
connection.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
|
||||
if conf['ldap_tls']:
|
||||
connection.start_tls_s()
|
||||
return connection
|
||||
|
||||
@@ -265,7 +265,7 @@ class DeliveryCarrier(models.Model):
|
||||
'price': 0.0,
|
||||
'error_message': _('Error: this delivery method is not available for this address.'),
|
||||
'warning_message': False}
|
||||
price = self.fixed_price
|
||||
price = order.pricelist_id.get_product_price(self.product_id, 1.0, order.partner_id)
|
||||
company = self.company_id or order.company_id or self.env.company
|
||||
if company.currency_id and company.currency_id != order.currency_id:
|
||||
price = company.currency_id._convert(price, order.currency_id, company, fields.Date.today())
|
||||
|
||||
@@ -155,3 +155,44 @@ class TestDeliveryCost(common.TransactionCase):
|
||||
self.default_delivery_policy = self.SaleConfigSetting.create({})
|
||||
|
||||
self.default_delivery_policy.execute()
|
||||
|
||||
def test_01_delivery_cost_from_pricelist(self):
|
||||
""" This test aims to validate the use of a pricelist to compute the delivery cost in the case the associated
|
||||
product of the shipping method is defined in the pricelist """
|
||||
|
||||
# Create pricelist with a custom price for the standard shipping method
|
||||
my_pricelist = self.env['product.pricelist'].create({
|
||||
'name': 'shipping_cost_change',
|
||||
'item_ids': [(0, 0, {
|
||||
'compute_price': 'fixed',
|
||||
'fixed_price': 5,
|
||||
'applied_on': '0_product_variant',
|
||||
'product_id': self.normal_delivery.product_id.id,
|
||||
})],
|
||||
})
|
||||
|
||||
# Create sales order with Normal Delivery Charges
|
||||
sale_pricelist_based_delivery_charges = self.SaleOrder.create({
|
||||
'partner_id': self.partner_18.id,
|
||||
'pricelist_id': my_pricelist.id,
|
||||
'order_line': [(0, 0, {
|
||||
'name': 'PC Assamble + 2GB RAM',
|
||||
'product_id': self.product_4.id,
|
||||
'product_uom_qty': 1,
|
||||
'product_uom': self.product_uom_unit.id,
|
||||
'price_unit': 750.00,
|
||||
})],
|
||||
})
|
||||
|
||||
# Add of delivery cost in Sales order
|
||||
delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({
|
||||
'default_order_id': sale_pricelist_based_delivery_charges.id,
|
||||
'default_carrier_id': self.normal_delivery.id
|
||||
}))
|
||||
self.assertEqual(delivery_wizard.delivery_price, 5.0, "Delivery cost does not correspond to 5.0 in wizard")
|
||||
delivery_wizard.save().button_confirm()
|
||||
|
||||
line = self.SaleOrderLine.search([('order_id', '=', sale_pricelist_based_delivery_charges.id),
|
||||
('product_id', '=', self.normal_delivery.product_id.id)])
|
||||
self.assertEqual(len(line), 1, "Delivery cost hasn't been added to SO")
|
||||
self.assertEqual(line.price_subtotal, 5.0, "Delivery cost does not correspond to 5.0")
|
||||
|
||||
@@ -854,11 +854,10 @@ class HolidaysRequest(models.Model):
|
||||
# Business methods
|
||||
####################################################
|
||||
|
||||
def _create_resource_leave(self):
|
||||
""" This method will create entry in resource calendar time off object at the time of holidays validated
|
||||
:returns: created `resource.calendar.leaves`
|
||||
def _prepare_resource_leave_vals_list(self):
|
||||
"""Hook method for others to inject data
|
||||
"""
|
||||
vals_list = [{
|
||||
return [{
|
||||
'name': leave.name,
|
||||
'date_from': leave.date_from,
|
||||
'holiday_id': leave.id,
|
||||
@@ -866,7 +865,13 @@ class HolidaysRequest(models.Model):
|
||||
'resource_id': leave.employee_id.resource_id.id,
|
||||
'calendar_id': leave.employee_id.resource_calendar_id.id,
|
||||
'time_type': leave.holiday_status_id.time_type,
|
||||
} for leave in self]
|
||||
} for leave in self]
|
||||
|
||||
def _create_resource_leave(self):
|
||||
""" This method will create entry in resource calendar time off object at the time of holidays validated
|
||||
:returns: created `resource.calendar.leaves`
|
||||
"""
|
||||
vals_list = self._prepare_resource_leave_vals_list()
|
||||
return self.env['resource.calendar.leaves'].sudo().create(vals_list)
|
||||
|
||||
def _remove_resource_leave(self):
|
||||
|
||||
@@ -21,7 +21,11 @@ class AccountMove(models.Model):
|
||||
self.journal_id.l10n_latam_use_documents:
|
||||
return super()._get_l10n_latam_documents_domain()
|
||||
if self.journal_id.type == 'sale':
|
||||
domain = [('country_id.code', '=', "CL"), ('internal_type', '!=', 'invoice_in')]
|
||||
if self.move_type == 'out_refund':
|
||||
internal_types_domain = ('internal_type', '=', 'credit_note')
|
||||
else:
|
||||
internal_types_domain = ('internal_type', 'not in', ['invoice_in', 'credit_note'])
|
||||
domain = [('country_id.code', '=', 'CL'), internal_types_domain]
|
||||
if self.company_id.partner_id.l10n_cl_sii_taxpayer_type == '1':
|
||||
domain += [('code', '!=', '71')] # Companies with VAT Affected doesn't have "Boleta de honorarios Electrónica"
|
||||
return domain
|
||||
|
||||
@@ -28,6 +28,8 @@ class SaleOrder(models.Model):
|
||||
data.append((_('Customer Reference'), record.client_order_ref))
|
||||
if record.user_id:
|
||||
data.append((_("Salesperson"), record.user_id.name))
|
||||
if 'incoterm' in record._fields and record.incoterm:
|
||||
data.append((_("Incoterm"), record.incoterm.code))
|
||||
|
||||
def _compute_l10n_de_document_title(self):
|
||||
for record in self:
|
||||
|
||||
@@ -242,7 +242,7 @@ class MockModels {
|
||||
email_cc: { type: 'char' },
|
||||
partner_ids: {
|
||||
string: "Related partners",
|
||||
type: 'many2one',
|
||||
type: 'one2many',
|
||||
relation: 'res.partner'
|
||||
},
|
||||
},
|
||||
|
||||
@@ -108,6 +108,18 @@ class MrpBom(models.Model):
|
||||
bom_product=bom_line.parent_product_tmpl_id.display_name
|
||||
))
|
||||
|
||||
@api.onchange('bom_line_ids', 'product_qty')
|
||||
def onchange_bom_structure(self):
|
||||
if self.type == 'phantom' and self._origin and self.env['stock.move'].search([('bom_line_id', 'in', self._origin.bom_line_ids.ids)], limit=1):
|
||||
return {
|
||||
'warning': {
|
||||
'title': _('Warning'),
|
||||
'message': _(
|
||||
'The product has already been used at least once, editing its structure may lead to undesirable behaviours. '
|
||||
'You should rather archive the product and create a new one with a new bill of materials.'),
|
||||
}
|
||||
}
|
||||
|
||||
@api.onchange('product_uom_id')
|
||||
def onchange_product_uom_id(self):
|
||||
res = {}
|
||||
|
||||
@@ -12,7 +12,7 @@ from dateutil.relativedelta import relativedelta
|
||||
from itertools import groupby
|
||||
|
||||
from flectra import api, fields, models, _
|
||||
from flectra.exceptions import AccessError, UserError
|
||||
from flectra.exceptions import AccessError, UserError, ValidationError
|
||||
from flectra.tools import float_compare, float_round, float_is_zero, format_datetime
|
||||
from flectra.tools.misc import format_date
|
||||
|
||||
@@ -709,6 +709,13 @@ class MrpProduction(models.Model):
|
||||
else:
|
||||
self.workorder_ids = False
|
||||
|
||||
@api.constrains('product_id', 'move_raw_ids')
|
||||
def _check_production_lines(self):
|
||||
for production in self:
|
||||
for move in production.move_raw_ids:
|
||||
if production.product_id == move.product_id:
|
||||
raise ValidationError(_("The component %s should not be the same as the product to produce.") % production.product_id.display_name)
|
||||
|
||||
def write(self, vals):
|
||||
if 'workorder_ids' in self:
|
||||
production_to_replan = self.filtered(lambda p: p.is_planned)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import lib
|
||||
from . import tools
|
||||
from . import models
|
||||
from . import wizard
|
||||
|
||||
4
addons/phone_validation/lib/__init__.py
Normal file
4
addons/phone_validation/lib/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import phonenumbers_patch
|
||||
8
addons/phone_validation/lib/phonemetadata.py
Normal file
8
addons/phone_validation/lib/phonemetadata.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
try:
|
||||
# import for usage in phonenumbers_patch/region_*.py files
|
||||
from phonenumbers.phonemetadata import NumberFormat, PhoneNumberDesc, PhoneMetadata # pylint: disable=unused-import
|
||||
except ImportError:
|
||||
pass
|
||||
31
addons/phone_validation/lib/phonenumbers_patch/__init__.py
Normal file
31
addons/phone_validation/lib/phonenumbers_patch/__init__.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2009 The Libphonenumber Authors
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# https://github.com/google/libphonenumber
|
||||
|
||||
from flectra.tools.parse_version import parse_version
|
||||
|
||||
try:
|
||||
import phonenumbers
|
||||
# MONKEY PATCHING phonemetadata of Ivory Coast if phonenumbers is too old
|
||||
if parse_version('7.6.1') <= parse_version(phonenumbers.__version__) < parse_version('8.12.32'):
|
||||
def _local_load_region(code):
|
||||
__import__("region_%s" % code, globals(), locals(),
|
||||
fromlist=["PHONE_METADATA_%s" % code], level=1)
|
||||
# loading updated region_CI.py from current directory
|
||||
# https://github.com/daviddrysdale/python-phonenumbers/blob/v8.12.32/python/phonenumbers/data/region_CI.py
|
||||
phonenumbers.phonemetadata.PhoneMetadata.register_region_loader('CI', _local_load_region)
|
||||
except ImportError:
|
||||
pass
|
||||
@@ -0,0 +1,9 @@
|
||||
"""Auto-generated file, do not edit by hand. CI metadata"""
|
||||
from ..phonemetadata import NumberFormat, PhoneNumberDesc, PhoneMetadata
|
||||
|
||||
PHONE_METADATA_CI = PhoneMetadata(id='CI', country_code=225, international_prefix='00',
|
||||
general_desc=PhoneNumberDesc(national_number_pattern='[02]\\d{9}', possible_length=(10,)),
|
||||
fixed_line=PhoneNumberDesc(national_number_pattern='2(?:[15]\\d{3}|7(?:2(?:0[23]|1[2357]|[23][45]|4[3-5])|3(?:06|1[69]|[2-6]7)))\\d{5}', example_number='2123456789', possible_length=(10,)),
|
||||
mobile=PhoneNumberDesc(national_number_pattern='0704[0-7]\\d{5}|0(?:[15]\\d\\d|7(?:0[0-37-9]|[4-9][7-9]))\\d{6}', example_number='0123456789', possible_length=(10,)),
|
||||
number_format=[NumberFormat(pattern='(\\d{2})(\\d{2})(\\d)(\\d{5})', format='\\1 \\2 \\3 \\4', leading_digits_pattern=['2']),
|
||||
NumberFormat(pattern='(\\d{2})(\\d{2})(\\d{2})(\\d{4})', format='\\1 \\2 \\3 \\4', leading_digits_pattern=['0'])])
|
||||
4
addons/phone_validation/tests/__init__.py
Normal file
4
addons/phone_validation/tests/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_phonenumbers_patch
|
||||
32
addons/phone_validation/tests/test_phonenumbers_patch.py
Normal file
32
addons/phone_validation/tests/test_phonenumbers_patch.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
try:
|
||||
import phonenumbers
|
||||
except ImportError:
|
||||
phonenumbers = None
|
||||
|
||||
from flectra.tests.common import BaseCase
|
||||
from flectra.tools.parse_version import parse_version
|
||||
from flectra.addons.phone_validation.lib import phonenumbers_patch
|
||||
|
||||
class TestPhonenumbersPatch(BaseCase):
|
||||
def test_region_CI_monkey_patch(self):
|
||||
"""Test if the patch is apply on the good version of the lib
|
||||
And test some phonenumbers"""
|
||||
if not phonenumbers:
|
||||
self.skipTest('Cannot test without phonenumbers module installed.')
|
||||
# MONKEY PATCHING phonemetadata of Ivory Coast if phonenumbers is too old
|
||||
if parse_version('7.6.1') <= parse_version(phonenumbers.__version__) < parse_version('8.12.32'):
|
||||
# check that _local_load_region is set to `flectra.addons.phone_validation.lib.phonenumbers_patch._local_load_region`
|
||||
# check that you can load a new ivory coast phone number without error
|
||||
parsed_phonenumber_1 = phonenumbers.parse("20 25/35-51 ", region="CI", keep_raw_input=True)
|
||||
self.assertEqual(parsed_phonenumber_1.national_number, 20253551, "The national part of the phonenumber should be 22522586")
|
||||
self.assertEqual(parsed_phonenumber_1.country_code, 225, "The country code of Ivory Coast is 225")
|
||||
|
||||
parsed_phonenumber_2 = phonenumbers.parse("+225 22 52 25 86 ", region="CI", keep_raw_input=True)
|
||||
self.assertEqual(parsed_phonenumber_2.national_number, 22522586, "The national part of the phonenumber should be 22522586")
|
||||
self.assertEqual(parsed_phonenumber_2.country_code, 225, "The country code of Ivory Coast is 225")
|
||||
else:
|
||||
self.assertFalse(hasattr(phonenumbers_patch, '_local_load_region'),
|
||||
"The code should not be monkey patched with phonenumbers > 8.12.32.")
|
||||
@@ -21,7 +21,7 @@
|
||||
<h2>Request for Quotation <span t-field="o.name"/></h2>
|
||||
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<thead style="display: table-row-group">
|
||||
<tr>
|
||||
<th name="th_description"><strong>Description</strong></th>
|
||||
<th name="th_expected_date" class="text-center"><strong>Expected Date</strong></th>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from flectra import models
|
||||
from flectra import _, models
|
||||
from flectra.tools.float_utils import float_is_zero
|
||||
from flectra.exceptions import UserError
|
||||
|
||||
|
||||
class StockMove(models.Model):
|
||||
@@ -24,4 +26,6 @@ class StockMove(models.Model):
|
||||
}
|
||||
valuation_total_qty = self._compute_kit_quantities(related_aml.product_id, order_qty, kit_bom, filters)
|
||||
valuation_total_qty = kit_bom.product_uom_id._compute_quantity(valuation_total_qty, related_aml.product_id.uom_id)
|
||||
if float_is_zero(valuation_total_qty, precision_rounding=related_aml.product_uom_id.rounding or related_aml.product_id.uom_id.rounding):
|
||||
raise UserError(_('Flectra is not able to generate the anglo saxon entries. The total valuation of %s is zero.') % related_aml.product_id.display_name)
|
||||
return valuation_price_unit_total, valuation_total_qty
|
||||
|
||||
@@ -338,7 +338,7 @@ class SaleOrder(models.Model):
|
||||
for order in self:
|
||||
total = 0.0
|
||||
for line in order.order_line:
|
||||
total += line.price_subtotal + line.price_unit * ((line.discount or 0.0) / 100.0) * line.product_uom_qty # why is there a discount in a field named amount_undiscounted ??
|
||||
total += (line.price_subtotal * 100)/(100-line.discount) if line.discount != 100 else (line.price_unit * line.product_uom_qty)
|
||||
order.amount_undiscounted = total
|
||||
|
||||
@api.depends('state')
|
||||
@@ -477,24 +477,10 @@ class SaleOrder(models.Model):
|
||||
|
||||
def update_prices(self):
|
||||
self.ensure_one()
|
||||
lines_to_update = []
|
||||
for line in self._get_update_prices_lines():
|
||||
product = line.product_id.with_context(
|
||||
partner=self.partner_id,
|
||||
quantity=line.product_uom_qty,
|
||||
date=self.date_order,
|
||||
pricelist=self.pricelist_id.id,
|
||||
uom=line.product_uom.id
|
||||
)
|
||||
price_unit = self.env['account.tax']._fix_tax_included_price_company(
|
||||
line._get_display_price(product), line.product_id.taxes_id, line.tax_id, line.company_id)
|
||||
if self.pricelist_id.discount_policy == 'without_discount' and price_unit:
|
||||
price_discount_unrounded = self.pricelist_id.get_product_price(product, line.product_uom_qty, self.partner_id, self.date_order, line.product_uom.id)
|
||||
discount = max(0, (price_unit - price_discount_unrounded) * 100 / price_unit)
|
||||
else:
|
||||
discount = 0
|
||||
lines_to_update.append((1, line.id, {'price_unit': price_unit, 'discount': discount}))
|
||||
self.update({'order_line': lines_to_update})
|
||||
line.product_uom_change()
|
||||
line.discount = 0 # Force 0 as discount for the cases when _onchange_discount directly returns
|
||||
line._onchange_discount()
|
||||
self.show_update_pricelist = False
|
||||
self.message_post(body=_("Product prices have been recomputed according to pricelist <b>%s<b> ", self.pricelist_id.display_name))
|
||||
|
||||
|
||||
@@ -583,6 +583,59 @@ class TestSaleOrder(TestSaleCommon):
|
||||
self.assertEqual(line.price_subtotal, 17527.41)
|
||||
self.assertEqual(line.untaxed_amount_to_invoice, line.price_subtotal)
|
||||
|
||||
def test_discount_and_amount_undiscounted(self):
|
||||
"""When adding a discount on a SO line, this test ensures that amount undiscounted is
|
||||
consistent with the used tax"""
|
||||
sale_order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [(0, 0, {
|
||||
'product_id': self.product_a.id,
|
||||
'product_uom_qty': 1,
|
||||
'price_unit': 100.0,
|
||||
'discount': 1.00,
|
||||
})]
|
||||
})
|
||||
sale_order.action_confirm()
|
||||
line = sale_order.order_line
|
||||
|
||||
# test discount and qty 1
|
||||
self.assertEqual(sale_order.amount_undiscounted, 100.0)
|
||||
self.assertEqual(line.price_subtotal, 99.0)
|
||||
|
||||
# more quantity 1 -> 3
|
||||
sale_form = Form(sale_order)
|
||||
with sale_form.order_line.edit(0) as line_form:
|
||||
line_form.product_uom_qty = 3.0
|
||||
line_form.price_unit = 100.0
|
||||
sale_order = sale_form.save()
|
||||
|
||||
self.assertEqual(sale_order.amount_undiscounted, 300.0)
|
||||
self.assertEqual(line.price_subtotal, 297.0)
|
||||
|
||||
# undiscounted
|
||||
with sale_form.order_line.edit(0) as line_form:
|
||||
line_form.discount = 0.0
|
||||
sale_order = sale_form.save()
|
||||
self.assertEqual(line.price_subtotal, 300.0)
|
||||
self.assertEqual(sale_order.amount_undiscounted, 300.0)
|
||||
|
||||
# Same with an included-in-price tax
|
||||
sale_order = sale_order.copy()
|
||||
line = sale_order.order_line
|
||||
line.tax_id = [(0, 0, {
|
||||
'name': 'Super Tax',
|
||||
'amount_type': 'percent',
|
||||
'amount': 10.0,
|
||||
'price_include': True,
|
||||
})]
|
||||
line.discount = 50.0
|
||||
sale_order.action_confirm()
|
||||
|
||||
# 300 with 10% incl tax -> 272.72 total tax excluded without discount
|
||||
# 136.36 price tax excluded with discount applied
|
||||
self.assertEqual(sale_order.amount_undiscounted, 272.72)
|
||||
self.assertEqual(line.price_subtotal, 136.36)
|
||||
|
||||
def test_free_product_and_price_include_fixed_tax(self):
|
||||
""" Check that fixed tax include are correctly computed while the price_unit is 0
|
||||
"""
|
||||
|
||||
@@ -71,6 +71,18 @@ class TestSaleOrder(TestSaleCommon):
|
||||
'percent_price': 20,
|
||||
})
|
||||
|
||||
# Create a pricelist without discount policy: percentage on all products
|
||||
cls.pricelist_discount_excl_global = cls.env['product.pricelist'].create({
|
||||
'name': 'Pricelist C',
|
||||
'discount_policy': 'without_discount',
|
||||
'company_id': cls.env.company.id,
|
||||
'item_ids': [(0, 0, {
|
||||
'applied_on': '3_global',
|
||||
'compute_price': 'percentage',
|
||||
'percent_price': 54,
|
||||
})],
|
||||
})
|
||||
|
||||
# create a generic Sale Order with all classical products and empty pricelist
|
||||
cls.sale_order = SaleOrder.create({
|
||||
'partner_id': cls.partner_a.id,
|
||||
@@ -164,18 +176,7 @@ class TestSaleOrder(TestSaleCommon):
|
||||
|
||||
def test_sale_change_of_pricelists_excluded_value_discount(self):
|
||||
""" Test SO with the pricelist 'discount displayed' and check displayed percentage value after multiple changes of pricelist """
|
||||
|
||||
# Create a pricelist without discount policy: percentage on all products
|
||||
pricelist_discount_excl_global = self.env['product.pricelist'].create({
|
||||
'name': 'Pricelist C',
|
||||
'discount_policy': 'without_discount',
|
||||
'company_id': self.env.company.id,
|
||||
'item_ids': [(0, 0, {
|
||||
'applied_on': '3_global',
|
||||
'compute_price': 'percentage',
|
||||
'percent_price': 54,
|
||||
})],
|
||||
})
|
||||
self.env.user.write({'groups_id': [(4, self.env.ref('product.group_discount_per_so_line').id)]})
|
||||
|
||||
# Create a product with a very low price
|
||||
amazing_product = self.env['product.product'].create({
|
||||
@@ -200,7 +201,7 @@ class TestSaleOrder(TestSaleCommon):
|
||||
})
|
||||
|
||||
# Change the pricelist
|
||||
sale_order.write({'pricelist_id': pricelist_discount_excl_global.id})
|
||||
sale_order.write({'pricelist_id': self.pricelist_discount_excl_global.id})
|
||||
# Update Prices
|
||||
sale_order.update_prices()
|
||||
|
||||
@@ -222,3 +223,79 @@ class TestSaleOrder(TestSaleCommon):
|
||||
sale_order.order_line.tax_id,
|
||||
"Wrong tax applied for specified product & pricelist"
|
||||
)
|
||||
|
||||
def test_sale_change_of_pricelists_excluded_value_discount_on_tax_included_price_mapped_to_tax_excluded_price(self):
|
||||
self.env.user.write({'groups_id': [(4, self.env.ref('product.group_discount_per_so_line').id)]})
|
||||
|
||||
# setting up the taxes:
|
||||
tax_a = self.tax_sale_a.copy()
|
||||
tax_b = self.tax_sale_a.copy()
|
||||
tax_a.price_include = True
|
||||
tax_b.amount = 6
|
||||
|
||||
# setting up fiscal position:
|
||||
fiscal_pos = self.fiscal_pos_a.copy()
|
||||
fiscal_pos.auto_apply = True
|
||||
country = self.env["res.country"].search([('name', '=', 'Belgium')], limit=1)
|
||||
fiscal_pos.country_id = country
|
||||
fiscal_pos.tax_ids = [
|
||||
(0, None,
|
||||
{
|
||||
'tax_src_id': tax_a.id,
|
||||
'tax_dest_id': tax_b.id
|
||||
})
|
||||
]
|
||||
|
||||
# setting up partner:
|
||||
self.partner_a.country_id = country
|
||||
|
||||
# creating product:
|
||||
|
||||
my_product = self.env['product.product'].create({
|
||||
'name': 'my Product',
|
||||
'lst_price': 115,
|
||||
'taxes_id': [tax_a.id]
|
||||
})
|
||||
|
||||
# creating SO
|
||||
|
||||
sale_order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_invoice_id': self.partner_a.id,
|
||||
'partner_shipping_id': self.partner_a.id,
|
||||
'pricelist_id': self.company_data['default_pricelist'].id,
|
||||
'order_line': [(0, 0, {
|
||||
'name': my_product.name,
|
||||
'product_id': my_product.id,
|
||||
'product_uom_qty': 1,
|
||||
'product_uom': my_product.uom_id.id,
|
||||
})],
|
||||
})
|
||||
|
||||
# Apply fiscal position
|
||||
|
||||
sale_order.fiscal_position_id = fiscal_pos.id
|
||||
# Change the pricelist
|
||||
sale_order.write({'pricelist_id': self.pricelist_discount_excl_global.id})
|
||||
# Update Prices
|
||||
sale_order.update_prices()
|
||||
|
||||
|
||||
# Check that the discount displayed is the correct one
|
||||
self.assertEqual(
|
||||
sale_order.order_line.discount, 54,
|
||||
"Wrong discount computed for specified product & pricelist"
|
||||
)
|
||||
# Additional to check for overall consistency
|
||||
self.assertEqual(
|
||||
sale_order.order_line.price_unit, 100,
|
||||
"Wrong unit price computed for specified product & pricelist"
|
||||
)
|
||||
self.assertEqual(
|
||||
sale_order.order_line.price_subtotal, 46,
|
||||
"Wrong subtotal price computed for specified product & pricelist"
|
||||
)
|
||||
self.assertEqual(
|
||||
sale_order.order_line.tax_id.id, tax_b.id,
|
||||
"Wrong tax applied for specified product & pricelist"
|
||||
)
|
||||
|
||||
@@ -86,8 +86,7 @@ class SaleOrder(models.Model):
|
||||
def update_prices(self):
|
||||
self.ensure_one()
|
||||
res = super().update_prices()
|
||||
for line in self.sale_order_option_ids:
|
||||
line.price_unit = self.pricelist_id.get_product_price(line.product_id, line.quantity, self.partner_id, uom_id=line.uom_id.id)
|
||||
self.sale_order_option_ids._update_price_and_discount()
|
||||
return res
|
||||
|
||||
@api.onchange('sale_order_template_id')
|
||||
@@ -150,9 +149,12 @@ class SaleOrder(models.Model):
|
||||
|
||||
def action_confirm(self):
|
||||
res = super(SaleOrder, self).action_confirm()
|
||||
if self.env.su:
|
||||
self = self.with_user(SUPERUSER_ID)
|
||||
|
||||
for order in self:
|
||||
if order.sale_order_template_id and order.sale_order_template_id.mail_template_id:
|
||||
order.sale_order_template_id.mail_template_id.with_user(SUPERUSER_ID).send_mail(order.id)
|
||||
order.sale_order_template_id.mail_template_id.send_mail(order.id)
|
||||
return res
|
||||
|
||||
def get_access_action(self, access_uid=None):
|
||||
@@ -208,6 +210,26 @@ class SaleOrderOption(models.Model):
|
||||
quantity = fields.Float('Quantity', required=True, digits='Product Unit of Measure', default=1)
|
||||
sequence = fields.Integer('Sequence', help="Gives the sequence order when displaying a list of optional products.")
|
||||
|
||||
def _update_price_and_discount(self):
|
||||
for option in self:
|
||||
if not option.product_id:
|
||||
continue
|
||||
# To compute the discount a so line is created in cache
|
||||
values = option._get_values_to_add_to_order()
|
||||
new_sol = option.env['sale.order.line'].new(values)
|
||||
new_sol._onchange_discount()
|
||||
option.discount = new_sol.discount
|
||||
if option.order_id.pricelist_id and option.order_id.partner_id:
|
||||
product = option.product_id.with_context(
|
||||
partner=option.order_id.partner_id,
|
||||
quantity=option.quantity,
|
||||
date=option.order_id.date_order,
|
||||
pricelist=option.order_id.pricelist_id.id,
|
||||
uom=option.uom_id.id,
|
||||
fiscal_position=option.env.context.get('fiscal_position')
|
||||
)
|
||||
option.price_unit = new_sol._get_display_price(product)
|
||||
|
||||
@api.depends('line_id', 'order_id.order_line', 'product_id')
|
||||
def _compute_is_present(self):
|
||||
# NOTE: this field cannot be stored as the line_id is usually removed
|
||||
@@ -226,22 +248,9 @@ class SaleOrderOption(models.Model):
|
||||
return
|
||||
product = self.product_id.with_context(
|
||||
lang=self.order_id.partner_id.lang,
|
||||
partner=self.order_id.partner_id,
|
||||
quantity=self.quantity,
|
||||
date=self.order_id.date_order,
|
||||
pricelist=self.order_id.pricelist_id.id,
|
||||
uom=self.uom_id.id,
|
||||
fiscal_position=self.env.context.get('fiscal_position')
|
||||
)
|
||||
self.name = product.get_product_multiline_description_sale()
|
||||
self.uom_id = self.uom_id or product.uom_id
|
||||
# To compute the discount a so line is created in cache
|
||||
values = self._get_values_to_add_to_order()
|
||||
new_sol = self.env['sale.order.line'].new(values)
|
||||
new_sol._onchange_discount()
|
||||
self.discount = new_sol.discount
|
||||
if self.order_id.pricelist_id and self.order_id.partner_id:
|
||||
self.price_unit = new_sol._get_display_price(product)
|
||||
self._update_price_and_discount()
|
||||
|
||||
def button_add_to_order(self):
|
||||
self.add_option_to_order()
|
||||
|
||||
@@ -283,3 +283,61 @@ class TestSaleOrder(TestSaleCommon):
|
||||
"If a pricelist is set without discount included, the discount "
|
||||
"shall be computed according to the price unit and the subtotal."
|
||||
"price")
|
||||
|
||||
def test_04_update_pricelist_option_line(self):
|
||||
"""
|
||||
This test checks that option line's values are correctly
|
||||
updated after a pricelist update
|
||||
"""
|
||||
|
||||
# Necessary for _onchange_discount() check
|
||||
self.env.user.write({
|
||||
'groups_id': [(4, self.env.ref('product.group_discount_per_so_line').id)],
|
||||
})
|
||||
|
||||
self.sale_order.write({
|
||||
'sale_order_template_id': self.quotation_template_no_discount.id
|
||||
})
|
||||
self.sale_order.onchange_sale_order_template_id()
|
||||
|
||||
self.assertEqual(
|
||||
self.sale_order.sale_order_option_ids[0].price_unit,
|
||||
self.pub_option_price,
|
||||
"If no pricelist is set, the unit price shall be the option's product price.")
|
||||
|
||||
self.assertEqual(
|
||||
self.sale_order.sale_order_option_ids[0].discount, 0,
|
||||
"If no pricelist is set, the discount should be 0.")
|
||||
|
||||
self.sale_order.write({
|
||||
'pricelist_id': self.discount_included_price_list.id,
|
||||
})
|
||||
self.sale_order.update_prices()
|
||||
|
||||
self.assertEqual(
|
||||
self.sale_order.sale_order_option_ids[0].price_unit,
|
||||
self.pl_option_price,
|
||||
"If a pricelist is set with discount included,"
|
||||
" the unit price shall be the option's product discounted price.")
|
||||
|
||||
self.assertEqual(
|
||||
self.sale_order.sale_order_option_ids[0].discount, 0,
|
||||
"If a pricelist is set with discount included,"
|
||||
" the discount should be 0.")
|
||||
|
||||
self.sale_order.write({
|
||||
'pricelist_id': self.discount_excluded_price_list.id,
|
||||
})
|
||||
self.sale_order.update_prices()
|
||||
|
||||
self.assertEqual(
|
||||
self.sale_order.sale_order_option_ids[0].price_unit,
|
||||
self.pub_option_price,
|
||||
"If a pricelist is set without discount included,"
|
||||
" the unit price shall be the option's product sale price.")
|
||||
|
||||
self.assertEqual(
|
||||
self.sale_order.sale_order_option_ids[0].discount,
|
||||
self.pl_option_discount,
|
||||
"If a pricelist is set without discount included,"
|
||||
" the discount should be correctly computed.")
|
||||
|
||||
@@ -122,12 +122,12 @@ class SaleOrderLine(models.Model):
|
||||
index=True, copy=False, help="Task generated by the sales order item")
|
||||
is_service = fields.Boolean("Is a Service", compute='_compute_is_service', store=True, compute_sudo=True, help="Sales Order item should generate a task and/or a project, depending on the product settings.")
|
||||
|
||||
@api.depends('product_id')
|
||||
@api.depends('product_id.type')
|
||||
def _compute_is_service(self):
|
||||
for so_line in self:
|
||||
so_line.is_service = so_line.product_id.type == 'service'
|
||||
|
||||
@api.depends('product_id')
|
||||
@api.depends('product_id.type')
|
||||
def _compute_product_updatable(self):
|
||||
for line in self:
|
||||
if line.product_id.type == 'service' and line.state == 'sale':
|
||||
|
||||
@@ -139,3 +139,24 @@ class TestSaleProject(SavepointCase):
|
||||
# service_tracking 'project_only'
|
||||
self.assertFalse(so_line_order_only_project.task_id, "Task should not be created")
|
||||
self.assertTrue(so_line_order_only_project.project_id, "Sales order line should be linked to newly created project")
|
||||
|
||||
def test_sol_product_type_update(self):
|
||||
partner = self.env['res.partner'].create({'name': "Mur en brique"})
|
||||
sale_order = self.env['sale.order'].with_context(tracking_disable=True).create({
|
||||
'partner_id': partner.id,
|
||||
'partner_invoice_id': partner.id,
|
||||
'partner_shipping_id': partner.id,
|
||||
})
|
||||
self.product_order_service3.type = 'consu'
|
||||
sale_order_line = self.env['sale.order.line'].create({
|
||||
'order_id': sale_order.id,
|
||||
'name': self.product_order_service3.name,
|
||||
'product_id': self.product_order_service3.id,
|
||||
'product_uom_qty': 5,
|
||||
'product_uom': self.product_order_service3.uom_id.id,
|
||||
'price_unit': self.product_order_service3.list_price
|
||||
})
|
||||
self.assertFalse(sale_order_line.is_service, "As the product is consumable, the SOL should not be a service")
|
||||
|
||||
self.product_order_service3.type = 'service'
|
||||
self.assertTrue(sale_order_line.is_service, "As the product is a service, the SOL should be a service")
|
||||
|
||||
@@ -648,19 +648,22 @@ class Picking(models.Model):
|
||||
# As the on_change in one2many list is WIP, we will overwrite the locations on the stock moves here
|
||||
# As it is a create the format will be a list of (0, 0, dict)
|
||||
moves = vals.get('move_lines', []) + vals.get('move_ids_without_package', [])
|
||||
if moves and vals.get('location_id') and vals.get('location_dest_id'):
|
||||
if moves and ((vals.get('location_id') and vals.get('location_dest_id')) or vals.get('partner_id')):
|
||||
for move in moves:
|
||||
if len(move) == 3 and move[0] == 0:
|
||||
move[2]['location_id'] = vals['location_id']
|
||||
move[2]['location_dest_id'] = vals['location_dest_id']
|
||||
# When creating a new picking, a move can have no `company_id` (create before
|
||||
# picking type was defined) or a different `company_id` (the picking type was
|
||||
# changed for an another company picking type after the move was created).
|
||||
# So, we define the `company_id` in one of these cases.
|
||||
picking_type = self.env['stock.picking.type'].browse(vals['picking_type_id'])
|
||||
if 'picking_type_id' not in move[2] or move[2]['picking_type_id'] != picking_type.id:
|
||||
move[2]['picking_type_id'] = picking_type.id
|
||||
move[2]['company_id'] = picking_type.company_id.id
|
||||
if vals.get('location_id') and vals.get('location_dest_id'):
|
||||
move[2]['location_id'] = vals['location_id']
|
||||
move[2]['location_dest_id'] = vals['location_dest_id']
|
||||
# When creating a new picking, a move can have no `company_id` (create before
|
||||
# picking type was defined) or a different `company_id` (the picking type was
|
||||
# changed for an another company picking type after the move was created).
|
||||
# So, we define the `company_id` in one of these cases.
|
||||
picking_type = self.env['stock.picking.type'].browse(vals['picking_type_id'])
|
||||
if 'picking_type_id' not in move[2] or move[2]['picking_type_id'] != picking_type.id:
|
||||
move[2]['picking_type_id'] = picking_type.id
|
||||
move[2]['company_id'] = picking_type.company_id.id
|
||||
if vals.get('partner_id'):
|
||||
move[2]['partner_id'] = vals.get('partner_id')
|
||||
# make sure to write `schedule_date` *after* the `stock.move` creation in
|
||||
# order to get a determinist execution of `_set_scheduled_date`
|
||||
scheduled_date = vals.pop('scheduled_date', False)
|
||||
@@ -700,6 +703,8 @@ class Picking(models.Model):
|
||||
after_vals['location_id'] = vals['location_id']
|
||||
if vals.get('location_dest_id'):
|
||||
after_vals['location_dest_id'] = vals['location_dest_id']
|
||||
if 'partner_id' in vals:
|
||||
after_vals['partner_id'] = vals['partner_id']
|
||||
if after_vals:
|
||||
self.mapped('move_lines').filtered(lambda move: not move.scrapped).write(after_vals)
|
||||
if vals.get('move_lines'):
|
||||
|
||||
@@ -626,7 +626,8 @@ The correction could unreserve some operations with problematics products.""", p
|
||||
:param domain: List for the domain, empty by default.
|
||||
:param extend: If True, enables form, graph and pivot views. False by default.
|
||||
"""
|
||||
self._quant_tasks()
|
||||
if not self.env['ir.config_parameter'].sudo().get_param('stock.skip_quant_tasks'):
|
||||
self._quant_tasks()
|
||||
ctx = dict(self.env.context or {})
|
||||
ctx.pop('group_by', None)
|
||||
action = {
|
||||
|
||||
@@ -293,6 +293,15 @@ class StockRule(models.Model):
|
||||
if not self.location_id.should_bypass_reservation():
|
||||
move_dest_ids = values.get('move_dest_ids', False) and [(4, x.id) for x in values['move_dest_ids']] or []
|
||||
|
||||
# when create chained moves for inter-warehouse transfers, set the warehouses as partners
|
||||
if not partner and move_dest_ids:
|
||||
move_dest = values['move_dest_ids']
|
||||
if location_id == company_id.internal_transit_location_id:
|
||||
partners = move_dest.location_dest_id.get_warehouse().partner_id
|
||||
if len(partners) == 1:
|
||||
partner = partners
|
||||
move_dest.partner_id = partner
|
||||
|
||||
move_values = {
|
||||
'name': name[:2000],
|
||||
'company_id': self.company_id.id or self.location_src_id.company_id.id or self.location_id.company_id.id or company_id.id,
|
||||
|
||||
@@ -5,10 +5,16 @@
|
||||
<t t-set="uom_categ_unit" t-value="env.ref('uom.product_uom_categ_unit')"/>
|
||||
<t t-foreach="docs" t-as="picking">
|
||||
|
||||
<t t-set="picking_qty_done" t-value="any(picking.move_lines.move_line_ids.mapped('qty_done'))"/>
|
||||
<t t-foreach="picking.move_lines" t-as="move">
|
||||
<t t-foreach="move.move_line_ids" t-as="move_line">
|
||||
<t t-if="move_line.product_uom_id.category_id == uom_categ_unit">
|
||||
<t t-set="qty" t-value="int(move_line.qty_done)"/>
|
||||
<t t-if="picking_qty_done">
|
||||
<t t-set="qty" t-value="int(move_line.qty_done)"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-set="qty" t-value="int(move_line.product_uom_qty)"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-set="qty" t-value="1"/>
|
||||
@@ -42,10 +48,16 @@
|
||||
<div class="page">
|
||||
<t t-set="uom_categ_unit" t-value="env.ref('uom.product_uom_categ_unit')"/>
|
||||
<t t-foreach="docs" t-as="picking">
|
||||
<t t-set="picking_qty_done" t-value="any(picking.move_lines.move_line_ids.mapped('qty_done'))"/>
|
||||
<t t-foreach="picking.move_lines" t-as="move">
|
||||
<t t-foreach="move.move_line_ids" t-as="move_line">
|
||||
<t t-if="move_line.product_uom_id.category_id == uom_categ_unit">
|
||||
<t t-set="qty" t-value="int(move_line.qty_done)"/>
|
||||
<t t-if="picking_qty_done">
|
||||
<t t-set="qty" t-value="int(move_line.qty_done)"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-set="qty" t-value="int(move_line.product_uom_qty)"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-set="qty" t-value="1"/>
|
||||
|
||||
@@ -2139,3 +2139,25 @@ class TestStockFlow(TestStockCommon):
|
||||
validate_picking(in02)
|
||||
self.assertEqual(out02.state, 'confirmed')
|
||||
self.assertEqual(out03.state, 'assigned')
|
||||
|
||||
def test_stock_move_with_partner_id(self):
|
||||
""" Ensure that the partner_id of the picking entry is
|
||||
transmitted to the SM upon object creation.
|
||||
"""
|
||||
partner_1 = self.env['res.partner'].create({'name': 'Hubert Bonisseur de la Bath'})
|
||||
partner_2 = self.env['res.partner'].create({'name': 'Donald Clairvoyant du Bled'})
|
||||
product = self.env['product.product'].create({'name': 'Un petit coup de polish', 'type': 'product'})
|
||||
wh = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
|
||||
|
||||
f = Form(self.env['stock.picking'])
|
||||
f.partner_id = partner_1
|
||||
f.picking_type_id = wh.out_type_id
|
||||
with f.move_ids_without_package.new() as move:
|
||||
move.product_id = product
|
||||
move.product_uom_qty = 5
|
||||
picking = f.save()
|
||||
|
||||
self.assertEqual(picking.move_lines.partner_id, partner_1)
|
||||
|
||||
picking.write({'partner_id': partner_2.id})
|
||||
self.assertEqual(picking.move_lines.partner_id, partner_2)
|
||||
|
||||
@@ -256,10 +256,12 @@ class TestWarehouse(TestStockCommon):
|
||||
'code': 'STK',
|
||||
})
|
||||
|
||||
distribution_partner = self.env['res.partner'].create({'name': 'Distribution Center'})
|
||||
warehouse_distribution = self.env['stock.warehouse'].create({
|
||||
'name': 'Dist.',
|
||||
'code': 'DIST',
|
||||
'resupply_wh_ids': [(6, 0, [warehouse_stock.id])]
|
||||
'resupply_wh_ids': [(6, 0, [warehouse_stock.id])],
|
||||
'partner_id': distribution_partner.id,
|
||||
})
|
||||
|
||||
warehouse_shop = self.env['stock.warehouse'].create({
|
||||
@@ -316,6 +318,9 @@ class TestWarehouse(TestStockCommon):
|
||||
self.assertTrue(self.env['stock.move'].search([('location_dest_id', '=', warehouse_shop.lot_stock_id.id)]))
|
||||
self.assertTrue(self.env['stock.move'].search([('location_id', '=', warehouse_shop.lot_stock_id.id)]))
|
||||
|
||||
self.assertTrue(self.env['stock.picking'].search([('location_id', '=', self.env.company.internal_transit_location_id.id), ('partner_id', '=', distribution_partner.id)]))
|
||||
self.assertTrue(self.env['stock.picking'].search([('location_dest_id', '=', self.env.company.internal_transit_location_id.id), ('partner_id', '=', distribution_partner.id)]))
|
||||
|
||||
def test_mutiple_resupply_warehouse(self):
|
||||
""" Simulate the following situation:
|
||||
- 2 shops with stock are resupply by 2 distinct warehouses
|
||||
|
||||
@@ -129,6 +129,23 @@ var ListController = BasicController.extend({
|
||||
return self.model.get(db_id, {raw: true});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Returns the list of currently selected records (with the check boxes on
|
||||
* the left) or the whole domain records if it is selected
|
||||
*
|
||||
* @returns {Promise<{id, display_name}[]>}
|
||||
*/
|
||||
getSelectedRecordsWithDomain: async function () {
|
||||
if (this.isDomainSelected) {
|
||||
const state = this.model.get(this.handle, {raw: true});
|
||||
return await this._domainToRecords(state.getDomain(), session.active_ids_limit);
|
||||
} else {
|
||||
return Promise.resolve(this.selectedRecords.map(localId => {
|
||||
const data = this.model.localData[localId].data;
|
||||
return { id: data.id, display_name: data.display_name };
|
||||
}));
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Display and bind all buttons in the control panel
|
||||
*
|
||||
@@ -384,6 +401,25 @@ var ListController = BasicController.extend({
|
||||
self.updateButtons('readonly');
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Returns the records matching the given domain.
|
||||
*
|
||||
* @private
|
||||
* @param {Array[]} domain
|
||||
* @param {integer} [limit]
|
||||
* @returns {Promise<{id, display_name}[]>}
|
||||
*/
|
||||
_domainToRecords: function (domain, limit) {
|
||||
return this._rpc({
|
||||
model: this.modelName,
|
||||
method: 'search_read',
|
||||
args: [domain],
|
||||
kwargs: {
|
||||
fields: ['display_name'],
|
||||
limit: limit,
|
||||
},
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Returns the ids of records matching the given domain.
|
||||
*
|
||||
|
||||
@@ -462,8 +462,7 @@ var SelectCreateDialog = ViewDialog.extend({
|
||||
disabled: true,
|
||||
close: true,
|
||||
click: async () => {
|
||||
const resIds = await this.viewController.getSelectedIdsWithDomain();
|
||||
const values = resIds.map(e => ({id: e}));
|
||||
const values = await this.viewController.getSelectedRecordsWithDomain();
|
||||
this.on_selected(values);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -583,7 +583,7 @@ QUnit.module('Views', {
|
||||
});
|
||||
|
||||
QUnit.test('SelectCreateDialog calls on_selected with every record matching the domain', async function (assert) {
|
||||
assert.expect(1);
|
||||
assert.expect(3);
|
||||
|
||||
const parent = await createParent({
|
||||
data: this.data,
|
||||
@@ -604,7 +604,9 @@ QUnit.module('Views', {
|
||||
new dialogs.SelectCreateDialog(parent, {
|
||||
res_model: 'partner',
|
||||
on_selected: function(records) {
|
||||
assert.equal(records.length, 3)
|
||||
assert.equal(records.length, 3);
|
||||
assert.strictEqual(records.map((r) => r.display_name).toString(), "blipblip,macgyver,Jack O'Neill");
|
||||
assert.strictEqual(records.map((r) => r.id).toString(), "1,2,3");
|
||||
}
|
||||
}).open();
|
||||
await testUtils.nextTick();
|
||||
@@ -616,6 +618,42 @@ QUnit.module('Views', {
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('SelectCreateDialog calls on_selected with every record matching without selecting a domain', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
const parent = await createParent({
|
||||
data: this.data,
|
||||
archs: {
|
||||
'partner,false,list':
|
||||
'<tree limit="2" string="Partner">' +
|
||||
'<field name="display_name"/>' +
|
||||
'<field name="foo"/>' +
|
||||
'</tree>',
|
||||
'partner,false,search':
|
||||
'<search>' +
|
||||
'<field name="foo"/>' +
|
||||
'</search>',
|
||||
},
|
||||
session: {},
|
||||
});
|
||||
|
||||
new dialogs.SelectCreateDialog(parent, {
|
||||
res_model: 'partner',
|
||||
on_selected: function(records) {
|
||||
assert.equal(records.length, 2);
|
||||
assert.strictEqual(records.map((r) => r.display_name).toString(), "blipblip,macgyver");
|
||||
assert.strictEqual(records.map((r) => r.id).toString(), "1,2");
|
||||
}
|
||||
}).open();
|
||||
await testUtils.nextTick();
|
||||
|
||||
await testUtils.dom.click($('thead .o_list_record_selector input'));
|
||||
await testUtils.dom.click($('.o_list_selection_box '));
|
||||
await testUtils.dom.click($('.modal .o_select_button'));
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('propagate can_create onto the search popup o2m', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-set='groups_tooltip'>More than one group has been set on the view.</t>
|
||||
<a class="show_group_id btn btn-link mx-auto" href="/web#model=ir.ui.view&id=681" t-att-title='groups_tooltip'>Discard & Edit in backend</a>
|
||||
<a class="show_group_id btn btn-link mx-auto" t-attf-href="/web#id=#{widget.page.view_id[0]}&view_type=form&model=ir.ui.view" t-att-title='groups_tooltip'>Discard & Edit in backend</a>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -728,6 +728,10 @@ class WebsiteSale(http.Controller):
|
||||
if not kw.get('use_same'):
|
||||
kw['callback'] = kw.get('callback') or \
|
||||
(not order.only_services and (mode[0] == 'edit' and '/shop/checkout' or '/shop/address'))
|
||||
# We need to update the pricelist(by the one selected by the customer), because onchange_partner reset it
|
||||
# We only need to update the pricelist when it is not redirected to /confirm_order
|
||||
if kw.get('callback', '') != '/shop/confirm_order':
|
||||
request.website.sale_get_order(update_pricelist=True)
|
||||
elif mode[1] == 'shipping':
|
||||
order.partner_shipping_id = partner_id
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class CrmTeam(models.Model):
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'tree,form',
|
||||
'domain': [('is_abandoned_cart', '=', True)],
|
||||
'search_view_id': self.env.ref('sale.sale_order_view_search_inherit_sale').id,
|
||||
'search_view_id': [self.env.ref('sale.sale_order_view_search_inherit_sale').id],
|
||||
'context': {
|
||||
'search_default_team_id': self.id,
|
||||
'default_team_id': self.id,
|
||||
|
||||
@@ -59,7 +59,8 @@
|
||||
<field name="name">Online Sales Analysis</field>
|
||||
<field name="res_model">sale.report</field>
|
||||
<field name="view_mode">pivot,graph</field>
|
||||
<field name="domain">[('state','in',('sale', 'done')), ('website_id', '!=', False)]</field>
|
||||
<field name="domain">[('website_id', '!=', False)]</field>
|
||||
<field name="context">{'search_default_confirmed': 1}</field>
|
||||
<field name="search_view_id" ref="sale_report_view_search_website"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_empty_folder">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from flectra import api, models
|
||||
from flectra import api, models, _
|
||||
|
||||
|
||||
class Users(models.Model):
|
||||
@@ -27,6 +27,6 @@ class Users(models.Model):
|
||||
res = super(Users, self).get_gamification_redirection_data()
|
||||
res.append({
|
||||
'url': '/slides',
|
||||
'label': 'See our eLearning'
|
||||
'label': _('See our eLearning')
|
||||
})
|
||||
return res
|
||||
|
||||
Reference in New Issue
Block a user