[PATCH] Upstream patch - 20042023

This commit is contained in:
Parthiv Patel committed 2023-04-20 08:43:58 +00:00
1 parent f4b35fc120
commit 2d35ca8d13
25 files changed
+363 -39

No files matched your search

+2 -1
View File
@@ -749,8 +749,9 @@ class AccountMove(models.Model):
self.company_id,
self.date or fields.Date.context_today(self),
)
amount_currency = currency.round(taxes_map_entry['amount'])
to_write_on_line = {
'amount_currency': taxes_map_entry['amount'],
'amount_currency': amount_currency,
'currency_id': taxes_map_entry['grouping_dict']['currency_id'],
'debit': balance > 0.0 and balance or 0.0,
'credit': balance < 0.0 and -balance or 0.0,
+5 -1
View File
@@ -301,7 +301,11 @@ class AccountTax(models.Model):
# We first need to find out whether this tax computation is made for a refund
tax_type = self and self[0].type_tax_use
is_refund = is_refund or (tax_type == 'sale' and price_unit > 0) or (tax_type == 'purchase' and price_unit < 0)
if self._context.get('manual_reco_widget'):
is_refund = is_refund or (tax_type == 'sale' and price_unit > 0) or (tax_type == 'purchase' and price_unit < 0)
else:
is_refund = is_refund or (tax_type == 'sale' and price_unit < 0) or (tax_type == 'purchase' and price_unit > 0)
rslt = self.with_context(caba_no_transition_account=True)\
.compute_all(price_unit, currency=currency_id, quantity=quantity, product=product_id, partner=partner_id, is_refund=is_refund)
@@ -1620,6 +1620,43 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
'amount_total': 1409.95,
})
def test_cash_rounding_with_rounding_method_set_to_global(self):
"""
Test that cash roundings are computed correctly when
tax calculation rounding method is set to `round_globally`.
"""
self.env.company.tax_calculation_rounding_method = 'round_globally'
cash_rounding = self.env['account.cash.rounding'].create({
'name': 'add_invoice_line',
'rounding': 0.05,
'strategy': 'add_invoice_line',
'profit_account_id': self.company_data['default_account_revenue'].copy().id,
'loss_account_id': self.company_data['default_account_expense'].copy().id,
'rounding_method': 'HALF-UP',
})
tax7_7 = self.env['account.tax'].create({
'name': "tax7_7",
'amount_type': 'percent',
'amount': 7.7,
})
invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'invoice_date': '2019-01-01',
'partner_id': self.partner_a.id,
'invoice_cash_rounding_id': cash_rounding.id,
'invoice_line_ids': [
(0, 0, {
'name': 'test product',
'price_unit': 295.0,
'tax_ids': [(6, 0, tax7_7.ids)],
}),
],
})
self.assertEqual(invoice.amount_total, 317.70)
def test_out_invoice_line_onchange_currency_1(self):
move_form = Form(self.invoice.with_context(dudu=True))
move_form.currency_id = self.currency_data['currency']
+1 -1
View File
@@ -1221,7 +1221,7 @@ class Message(models.Model):
for record in self:
model = model or record.model
res_id = res_id or record.res_id
if issubclass(self.pool[model], self.pool['mail.thread']):
if model and issubclass(self.pool[model], self.pool['mail.thread']):
self.env[model].invalidate_cache(fnames=[
'message_ids',
'message_unread',
@@ -193,6 +193,7 @@ function factory(dependencies) {
this.env.bus.trigger('do-action', {
action: 'mail.action_discuss',
options: {
name: this.env._t("Discuss"),
active_id: this.threadToActiveId(this),
clear_breadcrumbs: false,
on_reverse_breadcrumb: () => this.close(),
+3
View File
@@ -128,6 +128,9 @@ class MassMailController(http.Controller):
'class="o_snippet_view_in_browser" style="display: none;"'
)
if res_id:
res[mailing_id] = mailing._render_template(res[mailing_id], mailing.mailing_model_real, [res_id], post_process=True)[res_id]
return request.render('mass_mailing.view', {
'body': res[mailing_id],
})
@@ -20,3 +20,22 @@ class TestMassMailingControllers(MassMailCommon, HttpCase):
response = self.url_open(url)
self.assertEqual(response.status_code, 400)
def test_mailing_view(self):
mailing = self.env['mailing.mailing'].create({
'name': 'TestMailing',
'subject': 'Test',
'mailing_type': 'mail',
'body_html': '<p>Hello ${object.name} ${object.hello}</p>',
'mailing_model_id': self.env['ir.model']._get('res.partner').id,
})
partner_id = self.user_admin.partner_id
self.authenticate('admin', 'admin')
url = werkzeug.urls.url_join(mailing.get_base_url(), '/mailing/%s/view?res_id=%s' % (mailing.id, partner_id.id))
response = self.url_open(url)
self.assertEqual(response.status_code, 200)
self.assertNotIn('${', response.text)
self.assertIn("<p>Hello %s </p>" % partner_id.name, response.text)
+1 -1
View File
@@ -259,7 +259,7 @@ class TestMrpAccountMove(TestAccountMove):
"name": "Product B",
"type": "product",
"default_code": "prda",
"categ_id": cls.env.ref("product.product_category_all").id,
"categ_id": cls.auto_categ.id,
"taxes_id": [(5, 0, 0)],
"supplier_taxes_id": [(5, 0, 0)],
"lst_price": 100.0,
+2 -1
View File
@@ -189,7 +189,8 @@ class PosOrder(models.Model):
order.add_payment(return_payment_vals)
def _prepare_invoice_line(self, order_line):
name = order_line.product_id.default_code + " " + order_line.product_id.display_name if order_line.product_id.default_code else order_line.product_id.display_name
display_name = order_line.product_id.get_product_multiline_description_sale()
name = order_line.product_id.default_code + " " + display_name if order_line.product_id.default_code else display_name
return {
'product_id': order_line.product_id.id,
'quantity': order_line.qty if self.amount_total >= 0 else -order_line.qty,
+7 -1
View File
@@ -62,7 +62,7 @@ class PurchaseReport(models.Model):
@property
def _table_query(self):
''' Report needs to be dynamic to take into account multi-company selected + multi-currency rates '''
return '%s %s %s' % (self._select(), self._from(), self._group_by())
return '%s %s %s %s' % (self._select(), self._from(), self._where(), self._group_by())
def _select(self):
select_str = """
@@ -120,6 +120,12 @@ class PurchaseReport(models.Model):
)
return from_str
def _where(self):
return """
WHERE
l.display_type IS NULL
"""
def _group_by(self):
group_by_str = """
GROUP BY
@@ -89,6 +89,36 @@ class TestPurchaseOrderReport(AccountTestInvoicingCommon):
self.assertEqual(round(report[0]['delay']), -10, msg="The PO has been confirmed 10 days in advance")
self.assertEqual(round(report[0]['delay_pass']), 5, msg="There are 5 days between the order date and the planned date")
def test_02_po_report_note_section_filter(self):
po = self.env['purchase.order'].create({
'partner_id': self.partner_a.id,
'currency_id': self.currency_data['currency'].id,
'order_line': [
(0, 0, {
'name': 'This is a note',
'display_type': 'line_note',
'product_id': False,
'product_qty': 0.0,
'product_uom': False,
'price_unit': 0.0,
'taxes_id': False,
}),
(0, 0, {
'name': 'This is a section',
'display_type': 'line_section',
'product_id': False,
'product_qty': 0.0,
'product_uom': False,
'price_unit': 0.0,
'taxes_id': False,
}),
],
})
po.button_confirm()
result_po = self.env['purchase.report'].search([('order_id', '=', po.id)])
self.assertFalse(result_po, "The report should ignore the notes and sections")
def test_avg_price_calculation(self):
"""
Check that the average price is calculated based on the quantity ordered in each line
+4 -9
View File
@@ -20,6 +20,7 @@ def _configure_journals(cr, registry):
'property_stock_valuation_account_id',
]
# Property Stock Accounts
categ_values = {category.id: False for category in env['product.category'].search([])}
for company_id in company_ids:
# Check if property exists for stock account journal exists
field = env['ir.model.fields']._get("product.category", "property_stock_journal")
@@ -27,10 +28,10 @@ def _configure_journals(cr, registry):
('fields_id', '=', field.id),
('company_id', '=', company_id.id)])
# If not, check if you can find a journal that is already there with the same name, otherwise create one
# If not, check if you can find a journal that is already there with the same code, otherwise create one
if not properties:
journal_id = env['account.journal'].search([
('name', '=', _('Inventory Valuation')),
('code', '=', 'STJ'),
('company_id', '=', company_id.id),
('type', '=', 'general')], limit=1).id
if not journal_id:
@@ -57,10 +58,4 @@ def _configure_journals(cr, registry):
account,
company_id,
)
for name in todo_list:
env['ir.property']._set_multi(
name,
'product.category',
{category.id: False for category in env['product.category'].search([])},
True
)
env['ir.property'].with_company(company_id.id)._set_multi(name, 'product.category', categ_values, True)
@@ -27,9 +27,11 @@ class AccountChartTemplate(models.Model):
'property_stock_account_output_categ_id',
'property_stock_valuation_account_id',
]
categ_values = {category.id: False for category in self.env['product.category'].search([])}
for field in todo_list:
account = self[field]
value = acc_template_ref[account.id] if account else False
PropertyObj._set_default(field, "product.category", value, company)
PropertyObj._set_multi(field, "product.category", categ_values, True)
return res
+4 -1
View File
@@ -222,7 +222,7 @@ class AccountMoveLine(models.Model):
# with anglo-saxon accounting.
self.ensure_one()
self = self.with_company(self.move_id.journal_id.company_id)
if self.product_id.type == 'product' \
if self._can_use_stock_accounts() \
and self.move_id.company_id.anglo_saxon_accounting \
and self.move_id.is_purchase_document():
fiscal_position = self.move_id.fiscal_position_id
@@ -231,6 +231,9 @@ class AccountMoveLine(models.Model):
return accounts['stock_input']
return super(AccountMoveLine, self)._get_computed_account()
def _can_use_stock_accounts(self):
return self.product_id.type == 'product'
def _stock_account_get_anglo_saxon_price_unit(self):
self.ensure_one()
if not self.product_id:
+45 -11
View File
@@ -4,6 +4,7 @@
from flectra.addons.account.tests.common import AccountTestInvoicingCommon
from flectra.addons.stock_account.tests.test_stockvaluation import _create_accounting_data
from flectra.tests.common import tagged, Form
from flectra import fields
@tagged("post_install", "-at_install")
@@ -20,12 +21,25 @@ class TestAccountMove(AccountTestInvoicingCommon):
cls.stock_journal,
) = _create_accounting_data(cls.env)
# `all_categ` should not be altered, so we can test the `post_init` hook of `stock_account`
cls.all_categ = cls.env.ref('product.product_category_all')
cls.auto_categ = cls.env['product.category'].create({
'name': 'child_category',
'parent_id': cls.all_categ.id,
"property_stock_account_input_categ_id": cls.stock_input_account.id,
"property_stock_account_output_categ_id": cls.stock_output_account.id,
"property_stock_valuation_account_id": cls.stock_valuation_account.id,
"property_stock_journal": cls.stock_journal.id,
"property_valuation": "real_time",
"property_cost_method": "standard",
})
cls.product_A = cls.env["product.product"].create(
{
"name": "Product A",
"type": "product",
"default_code": "prda",
"categ_id": cls.env.ref("product.product_category_all").id,
"categ_id": cls.auto_categ.id,
"taxes_id": [(5, 0, 0)],
"supplier_taxes_id": [(5, 0, 0)],
"lst_price": 100.0,
@@ -34,16 +48,6 @@ class TestAccountMove(AccountTestInvoicingCommon):
"property_account_expense_id": cls.company_data["default_account_expense"].id,
}
)
cls.product_A.categ_id.write(
{
"property_stock_account_input_categ_id": cls.stock_input_account.id,
"property_stock_account_output_categ_id": cls.stock_output_account.id,
"property_stock_valuation_account_id": cls.stock_valuation_account.id,
"property_stock_journal": cls.stock_journal.id,
"property_valuation": "real_time",
"property_cost_method": "standard",
}
)
def test_standard_perpetual_01_mc_01(self):
rate = self.currency_data["rates"].sorted()[0].rate
@@ -118,3 +122,33 @@ class TestAccountMove(AccountTestInvoicingCommon):
self.assertEqual(len(invoice.mapped("line_ids")), 4)
self.assertEqual(len(invoice.mapped("line_ids").filtered("is_anglo_saxon_line")), 2)
self.assertEqual(len(invoice.mapped("line_ids.currency_id")), 2)
def test_basic_bill(self):
"""
When billing a storable product with a basic category (manual
valuation), the account used should be the expenses one. This test
checks the flow with two companies:
- One that existed before the installation of `stock_account` (to test
the post-install hook)
- One created after the module installation
"""
first_company = self.env['res.company'].browse(1)
self.env.user.company_ids |= first_company
basic_product = self.env['product.product'].create({
'name': 'SuperProduct',
'type': 'product',
'categ_id': self.all_categ.id,
})
for company in (self.env.company | first_company):
bill_form = Form(self.env['account.move'].with_company(company.id).with_context(default_move_type='in_invoice'))
bill_form.partner_id = self.partner_a
bill_form.invoice_date = fields.Date.today()
with bill_form.invoice_line_ids.new() as line:
line.product_id = basic_product
line.price_unit = 100
bill = bill_form.save()
bill.action_post()
product_accounts = basic_product.product_tmpl_id.with_company(company.id).get_product_accounts()
self.assertEqual(bill.invoice_line_ids.account_id, product_accounts['expense'])
@@ -53,9 +53,6 @@ class AccountMoveLine(models.Model):
product_type = fields.Selection(related='product_id.type', readonly=True)
is_landed_costs_line = fields.Boolean()
def _get_landed_costs_account(self, accounts):
return accounts['stock_input' if self.move_id.company_id.anglo_saxon_accounting else 'expense']
@api.onchange('is_landed_costs_line')
def _onchange_is_landed_costs_line(self):
"""Mark an invoice line as a landed cost line and adapt `self.account_id`. The default
@@ -66,7 +63,7 @@ class AccountMoveLine(models.Model):
if self.product_type != 'service':
self.is_landed_costs_line = False
elif self.is_landed_costs_line:
aml_account = self._get_landed_costs_account(accounts)
aml_account = (self.move_id.company_id.anglo_saxon_accounting and accounts['stock_input']) or aml_account
self.account_id = aml_account
@api.onchange('product_id')
@@ -76,8 +73,5 @@ class AccountMoveLine(models.Model):
else:
self.is_landed_costs_line = False
def create(self, vals_list):
lines = super().create(vals_list)
for line in lines.filtered(lambda x: x.is_landed_costs_line and x.product_id.categ_id.property_valuation == 'real_time'):
line.account_id = line._get_landed_costs_account(line.product_id.product_tmpl_id._get_product_accounts())
return lines
def _can_use_stock_accounts(self):
return super()._can_use_stock_accounts() or (self.product_id.type == 'service' and self.product_id.landed_cost_ok)
@@ -4,6 +4,7 @@
from flectra.addons.stock_landed_costs.tests.common import TestStockLandedCostsCommon
from flectra.exceptions import ValidationError
from flectra.tests import tagged
from flectra import fields
@tagged('post_install', '-at_install')
@@ -159,3 +160,53 @@ class TestStockLandedCosts(TestStockLandedCostsCommon):
self.assertEqual(len(picking_landed_cost_1.move_lines.stock_valuation_layer_ids), 5)
self.assertEqual(len(picking_landed_cost_2.move_lines.stock_valuation_layer_ids), 5)
def test_aml_account_selection(self):
"""
Process a PO with a landed cost, then create and post the bill. The
account of the landed cost AML should be:
- Expense if the categ valuation is manual
- Stock IN if the categ valuation is real time
"""
self.landed_cost.landed_cost_ok = True
for valuation, account in [
('manual_periodic', self.company_data['default_account_expense']),
('real_time', self.env.company.property_stock_account_input_categ_id),
]:
self.landed_cost.categ_id.property_valuation = valuation
po = self.env['purchase.order'].create({
'partner_id': self.partner_a.id,
'currency_id': self.company_data['currency'].id,
'order_line': [
(0, 0, {
'name': self.product_a.name,
'product_id': self.product_a.id,
'product_qty': 1.0,
'product_uom': self.product_a.uom_po_id.id,
'price_unit': 100.0,
'taxes_id': False,
}),
(0, 0, {
'name': self.landed_cost.name,
'product_id': self.landed_cost.id,
'product_qty': 1.0,
'price_unit': 100.0,
}),
],
})
po.button_confirm()
receipt = po.picking_ids
receipt.move_lines.quantity_done = 1
receipt.button_validate()
po.order_line[1].qty_received = 1
po.action_create_invoice()
bill = po.invoice_ids
bill.invoice_date = fields.Date.today()
bill._post()
landed_cost_aml = bill.invoice_line_ids.filtered(lambda l: l.product_id == self.landed_cost)
self.assertEqual(bill.state, 'posted', 'Incorrect value with valuation %s' % valuation)
self.assertEqual(landed_cost_aml.account_id, account, 'Incorrect value with valuation %s' % valuation)
@@ -69,6 +69,8 @@ class TestLandedCosts(TestStockLandedCostsCommon):
# 3.transportation 250 By Weight
# 4.packaging 20 By Volume
self.landed_cost.categ_id.property_valuation = 'real_time'
# Process incoming shipment
income_ship = self._process_incoming_shipment()
# Create landed costs
@@ -181,6 +183,8 @@ class TestLandedCosts(TestStockLandedCostsCommon):
# 3.transportation -50 By Weight
# 4.packaging -5 By Volume
self.landed_cost.categ_id.property_valuation = 'real_time'
# Process incoming shipment
income_ship = self._process_incoming_shipment()
# Refrigerator outgoing shipment.
@@ -1164,6 +1164,9 @@ var SnippetsMenu = Widget.extend({
}
const $oeStructure = $target.closest('.oe_structure');
if ($oeStructure.length && !$oeStructure.children().length && this.$snippets) {
// Deselect any snippet that might be selected (e.g. within a
// mega menu)
this._activateSnippet(false);
// If empty oe_structure, encourage using snippets in there by
// making them "wizz" in the panel.
this.$snippets.flectraBounce();
@@ -664,11 +664,31 @@ publicWidget.registry.hoverableDropdown = animations.Animation.extend({
publicWidget.registry.HeaderMainCollapse = publicWidget.Widget.extend({
selector: 'header#top',
disabledInEditableMode: false,
events: {
'show.bs.collapse #top_menu_collapse': '_onCollapseShow',
'hidden.bs.collapse #top_menu_collapse': '_onCollapseHidden',
},
/**
* @override
*/
start() {
// This is a fix in stable to move the "call to action" button in the
// navbar for the "boxed" header when the "off-canvas" mobile menu is
// enabled. Without this, the "call to action" button is inaccessible in
// the "off-canvas" mobile menu.
this.offcanvasAndBoxedHeader = false;
// If mobile menu is "off-canvas" and header template is "boxed".
if (this.$target[0].querySelector('.o_offcanvas_menu_toggler')
&& this.$target[0].querySelector('.o_header_boxed_background')) {
this.navbarEl = this.$target[0].querySelector('#top_menu');
this.callToActionEl = this.$target[0].querySelector('#oe_structure_header_boxed_2');
this.offcanvasAndBoxedHeader = !!this.callToActionEl;
}
return this._super(...arguments);
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
@@ -678,12 +698,20 @@ publicWidget.registry.HeaderMainCollapse = publicWidget.Widget.extend({
*/
_onCollapseShow() {
this.el.classList.add('o_top_menu_collapse_shown');
if (this.offcanvasAndBoxedHeader) {
this.callToActionEl.classList.add('nav-item');
this.navbarEl.append(this.callToActionEl);
}
},
/**
* @private
*/
_onCollapseHidden() {
this.el.classList.remove('o_top_menu_collapse_shown');
if (this.offcanvasAndBoxedHeader) {
this.callToActionEl.classList.remove('nav-item');
this.navbarEl.after(this.callToActionEl);
}
},
});
@@ -3,6 +3,7 @@ flectra.define('website.snippet.editor', function (require) {
const {qweb, _t, _lt} = require('web.core');
const Dialog = require('web.Dialog');
const publicWidget = require('web.public.widget');
const weSnippetEditor = require('web_editor.snippet.editor');
const wSnippetOptions = require('website.editor.snippets.options');
@@ -268,4 +269,83 @@ weSnippetEditor.Editor.include({
'.o_bg_video_container',
].join(','),
});
// Edit mode customizations of public widgets.
publicWidget.registry.hoverableDropdown.include({
/**
* @override
*/
start() {
if (this.editableMode) {
this._onPageClick = this._onPageClick.bind(this);
this.el.closest('#wrapwrap').addEventListener('click', this._onPageClick, {capture: true});
}
return this._super.apply(this, arguments);
},
/**
* @override
*/
destroy() {
if (this.editableMode) {
this.el.closest('#wrapwrap').removeEventListener('click', this._onPageClick, {capture: true});
}
return this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Hides all opened dropdowns.
*
* @private
*/
_hideDropdowns() {
for (const toggleEl of this.el.querySelectorAll('.dropdown.show .dropdown-toggle')) {
$(toggleEl).dropdown('hide');
}
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Called when the page is clicked anywhere.
* Closes the shown dropdown if the click is outside of it.
*
* @private
* @param {Event} ev
*/
_onPageClick(ev) {
if (ev.target.closest('.dropdown.show')) {
return;
}
this._hideDropdowns();
},
/**
* @override
*/
_onMouseEnter(ev) {
if (this.editableMode) {
// Do not handle hover if another dropdown is opened.
if (this.el.querySelector('.dropdown.show')) {
return;
}
}
this._super(...arguments);
},
/**
* @override
*/
_onMouseLeave(ev) {
if (this.editableMode) {
// Cancel handling from view mode.
return;
}
this._super(...arguments);
},
});
});
@@ -1958,6 +1958,15 @@ options.registry.HeaderNavbar = options.Class.extend({
// Public
//--------------------------------------------------------------------------
/**
* @override
*/
async start() {
await this._super(...arguments);
// TODO Remove in master.
const signInOptionEl = this.el.querySelector('[data-customize-website-views="portal.user_sign_in"]');
signInOptionEl.dataset.noPreview = 'true';
},
/**
* @private
*/
@@ -143,7 +143,8 @@ function changePaddingSize(direction) {
/**
* Click on the top right edit button
* @param {*} position Where the purple arrow will show up
*
* @deprecated use `clickOnEditAndWaitEditMode` instead to avoid race condition
*/
function clickOnEdit(position = "bottom") {
return {
@@ -154,6 +155,23 @@ function clickOnEdit(position = "bottom") {
};
}
/**
* Click on the top right edit button and wait for the edit mode
*
* @param {string} position Where the purple arrow will show up
*/
function clickOnEditAndWaitEditMode(position = "bottom") {
return [{
content: _t("<b>Click Edit</b> to start designing your homepage."),
trigger: "a[data-action=edit]",
position: position,
}, {
content: "Check that we are in edit mode",
trigger: '#oe_snippets.o_loaded',
run: () => null, // it's a check
}];
}
/**
* Simple click on a snippet in the edition area
* @param {*} snippet
@@ -278,6 +296,7 @@ return {
changeOption,
changePaddingSize,
clickOnEdit,
clickOnEditAndWaitEditMode,
clickOnSave,
clickOnSnippet,
clickOnText,
@@ -8,7 +8,7 @@ tour.register("snippet_image_gallery", {
test: true,
url: "/",
}, [
wTourUtils.clickOnEdit(),
...wTourUtils.clickOnEditAndWaitEditMode(),
wTourUtils.dragNDrop({
id: "s_image_gallery",
name: "Image Gallery",
+1 -1
View File
@@ -794,7 +794,7 @@
data-save-unit="rem"
data-dependencies="!header_effect_scroll_opt"/>
<we-checkbox string="Show Sign In" data-customize-website-views="portal.user_sign_in" data-reload="/"/>
<we-checkbox string="Show Sign In" data-customize-website-views="portal.user_sign_in" data-reload="/" data-no-preview="true"/>
<we-select string="Language Selector" data-reload="/">
<we-button data-name="header_language_selector_none_opt"
data-customize-website-views="">None</we-button>