[MERGE] forward port branch saas-12.4 up to bef26222b8
This commit is contained in:
@@ -270,7 +270,7 @@ class ResPartner(models.Model):
|
||||
RIGHT JOIN account_account acc ON aml.account_id = acc.id
|
||||
WHERE acc.internal_type = %s
|
||||
AND NOT acc.deprecated AND acc.company_id = %s
|
||||
AND move.state == 'posted'
|
||||
AND move.state = 'posted'
|
||||
GROUP BY partner.id
|
||||
HAVING %s * COALESCE(SUM(aml.amount_residual), 0) ''' + operator + ''' %s''', (account_type, self.env.user.company_id.id, sign, operand))
|
||||
res = self._cr.fetchall()
|
||||
|
||||
@@ -68,6 +68,28 @@ class AccountAnalyticDefault(models.Model):
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = 'account.move.line'
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields_list):
|
||||
defaults = super().default_get(fields_list)
|
||||
if {'account_analytic_id', 'analytic_tag_ids'} & set(fields_list):
|
||||
rec = self.env['account.analytic.default'].account_get(
|
||||
product_id=self.product_id.id,
|
||||
partner_id=self.move_id.commercial_partner_id.id,
|
||||
user_id=self.move_id.user_id.id or self.env.uid,
|
||||
date=fields.Date.today(),
|
||||
company_id=self.company_id.id,
|
||||
)
|
||||
if rec:
|
||||
if 'account_analytic_id' in fields_list:
|
||||
defaults.update({
|
||||
'account_analytic_id': rec.analytic_id.id,
|
||||
})
|
||||
if 'analytic_tag_ids' in fields_list:
|
||||
defaults.update({
|
||||
'analytic_tag_ids': rec.analytic_tag_ids.ids,
|
||||
})
|
||||
return defaults
|
||||
|
||||
@api.onchange('product_id', 'account_id')
|
||||
def _onchange_product_id_account_id(self):
|
||||
rec = self.env['account.analytic.default'].account_get(
|
||||
|
||||
@@ -922,6 +922,7 @@ class Import(models.TransientModel):
|
||||
if dryrun:
|
||||
self._cr.execute('ROLLBACK TO SAVEPOINT import')
|
||||
# cancel all changes done to the registry/ormcache
|
||||
self.pool.clear_caches()
|
||||
self.pool.reset_changes()
|
||||
else:
|
||||
self._cr.execute('RELEASE SAVEPOINT import')
|
||||
|
||||
@@ -398,7 +398,17 @@ class GoogleCalendar(models.AbstractModel):
|
||||
url = "/calendar/v3/calendars/%s/events/%s?access_token=%s" % ('primary', instance_id, self.get_token())
|
||||
headers = {'Content-type': 'application/json'}
|
||||
|
||||
data.update(recurringEventId=event_ori_google_id, originalStartTime=event_new.recurrent_id_date, sequence=self.get_sequence(instance_id))
|
||||
_originalStartTime = dict()
|
||||
if event_new.allday:
|
||||
_originalStartTime['date'] = event_new.recurrent_id_date.strftime("%Y-%m-%d")
|
||||
else:
|
||||
_originalStartTime['datetime'] = event_new.recurrent_id_date.strftime("%Y-%m-%dT%H:%M:%S.%fz")
|
||||
|
||||
data.update(
|
||||
recurringEventId=event_ori_google_id,
|
||||
originalStartTime=_originalStartTime,
|
||||
sequence=self.get_sequence(instance_id)
|
||||
)
|
||||
data_json = json.dumps(data)
|
||||
return self.env['google.service']._do_request(url, data_json, headers, type='PUT')
|
||||
|
||||
@@ -665,8 +675,9 @@ class GoogleCalendar(models.AbstractModel):
|
||||
else:
|
||||
_logger.warning("Impossible to create event %s. [%s]", att.event_id.id, status)
|
||||
_logger.debug("Response : %s", response)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
_logger.warning("Exception when updating recurrent event exclusions on google: %s", e)
|
||||
|
||||
return new_ids
|
||||
|
||||
def update_events(self, lastSync=False):
|
||||
|
||||
@@ -149,6 +149,7 @@
|
||||
<field name="date"/>
|
||||
<field name="state"/>
|
||||
<field name="activity_state"/>
|
||||
<field name="currency_id"/>
|
||||
<progressbar field="activity_state" colors='{"planned": "success", "today": "warning", "overdue": "danger"}'/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
@@ -156,7 +157,7 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<strong class="o_kanban_record_title"><span><t t-esc="record.name.value"/></span></strong>
|
||||
<strong class="o_kanban_record_subtitle float-right"><span class="text-right"><t t-esc="record.total_amount.value"/></span></strong>
|
||||
<strong class="o_kanban_record_subtitle float-right"><span class="text-right"><field name="total_amount" widget="monetary"/></span></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt8">
|
||||
@@ -560,13 +561,14 @@
|
||||
<field name="total_amount"/>
|
||||
<field name="accounting_date"/>
|
||||
<field name="state"/>
|
||||
<field name="currency_id"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_card oe_kanban_global_click">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<strong class="o_kanban_record_title"><span><t t-esc="record.name.value"/></span></strong>
|
||||
<strong class="o_kanban_record_subtitle float-right"><span class="text-right"><t t-esc="record.total_amount.value"/></span></strong>
|
||||
<strong class="o_kanban_record_subtitle float-right"><span class="text-right"><field name="total_amount" widget="monetary"/></span></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt8">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='invoice_sent']" position="after">
|
||||
<field name="l10n_ch_isr_sent" invisible="1"/>
|
||||
<field name="l10n_ch_currency_name" invisible="1"/>
|
||||
<field name="l10n_ch_currency_name" invisible="1" readonly="1"/>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//button[@id='account_invoice_payment_btn']" position="before">
|
||||
|
||||
@@ -112,6 +112,7 @@
|
||||
<IdCodice t-esc="get_vat_number(record.commercial_partner_id.vat)"/>
|
||||
</IdFiscaleIVA>
|
||||
<CodiceFiscale t-if="not record.commercial_partner_id.vat" t-esc="record.commercial_partner_id.l10n_it_codice_fiscale"/>
|
||||
<CodiceFiscale t-if="not record.commercial_partner_id.vat and not record.commercial_partner_id.l10n_it_codice_fiscale" t-esc="99999999999"/>
|
||||
<Anagrafica>
|
||||
<Denominazione t-if="record.commercial_partner_id.is_company" t-esc="record.commercial_partner_id.display_name"/>
|
||||
<Nome t-if="not record.commercial_partner_id.is_company" t-esc="record.commercial_partner_id.name"/>
|
||||
|
||||
@@ -505,6 +505,7 @@ var Discuss = AbstractAction.extend({
|
||||
*/
|
||||
_initRender: function () {
|
||||
var self = this;
|
||||
this.$('.o_mail_discuss_loading').remove();
|
||||
this._basicComposer = new BasicComposer(this, {
|
||||
mentionPartnersRestricted: true,
|
||||
showTyping: true,
|
||||
@@ -1419,7 +1420,6 @@ var Discuss = AbstractAction.extend({
|
||||
if (!this._isStarted) {
|
||||
return;
|
||||
}
|
||||
this.$('.o_mail_discuss_loading').remove();
|
||||
this._initRender();
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
odoo.define('mail.discuss_test', function (require) {
|
||||
"use strict";
|
||||
|
||||
const Discuss = require('mail.Discuss');
|
||||
var mailTestUtils = require('mail.testUtils');
|
||||
|
||||
var testUtils = require('web.test_utils');
|
||||
@@ -191,6 +192,59 @@ QUnit.test('messaging not ready', async function (assert) {
|
||||
discuss.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('messaging initially ready', async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
const startDiscussProm = testUtils.makeTestPromise();
|
||||
|
||||
testUtils.mock.patch(Discuss, {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async start() {
|
||||
const _super = this._super.bind(this, ...arguments); // due to limitation of class.js
|
||||
assert.step('discuss:starting');
|
||||
await startDiscussProm;
|
||||
assert.step('discuss:started');
|
||||
return _super();
|
||||
},
|
||||
});
|
||||
|
||||
const discussProm = createDiscuss({
|
||||
id: 1,
|
||||
context: {},
|
||||
params: {},
|
||||
data: this.data,
|
||||
services: this.services,
|
||||
mockRPC(route) {
|
||||
if (route === '/mail/init_messaging') {
|
||||
assert.step(route);
|
||||
}
|
||||
return this._super(...arguments);
|
||||
}
|
||||
});
|
||||
await testUtils.nextTick();
|
||||
assert.verifySteps([
|
||||
'/mail/init_messaging',
|
||||
'discuss:starting',
|
||||
]);
|
||||
|
||||
startDiscussProm.resolve();
|
||||
await testUtils.nextTick();
|
||||
assert.verifySteps(['discuss:started']);
|
||||
const discuss = await discussProm;
|
||||
assert.ok(
|
||||
discuss.el,
|
||||
"discuss should be rendered");
|
||||
assert.containsNone(
|
||||
discuss,
|
||||
'.o_mail_discuss_loading',
|
||||
"should not display sidebar or content is loading (messaging is ready)");
|
||||
|
||||
testUtils.mock.unpatch(Discuss);
|
||||
discuss.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('searchview options visibility', async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
|
||||
@@ -572,7 +572,7 @@ class ProductProduct(models.Model):
|
||||
'target': 'new'}
|
||||
|
||||
def _prepare_sellers(self, params):
|
||||
return self.seller_ids.sorted(lambda s: (s.sequence, -s.min_qty, s.price))
|
||||
return self.seller_ids.filtered(lambda s: s.name.active).sorted(lambda s: (s.sequence, -s.min_qty, s.price))
|
||||
|
||||
def _select_seller(self, partner_id=False, quantity=0.0, date=None, uom_id=False, params=False):
|
||||
self.ensure_one()
|
||||
@@ -710,7 +710,6 @@ class SupplierInfo(models.Model):
|
||||
'res.partner', 'Vendor',
|
||||
ondelete='cascade', required=True,
|
||||
help="Vendor of this product", check_company=True)
|
||||
active = fields.Boolean(related='name.active', readonly=True)
|
||||
product_name = fields.Char(
|
||||
'Vendor Product Name',
|
||||
help="This vendor's product name will be used when printing a request for quotation. Keep empty to use the internal one.")
|
||||
|
||||
@@ -115,12 +115,11 @@ class PurchaseOrderLine(models.Model):
|
||||
def _onchange_quantity(self):
|
||||
res = super(PurchaseOrderLine, self)._onchange_quantity()
|
||||
if self.order_id.requisition_id:
|
||||
for line in self.order_id.requisition_id.line_ids:
|
||||
if line.product_id == self.product_id:
|
||||
if line.product_uom_id != self.product_uom:
|
||||
self.price_unit = line.product_uom_id._compute_price(
|
||||
line.price_unit, self.product_uom)
|
||||
else:
|
||||
self.price_unit = line.price_unit
|
||||
break
|
||||
for line in self.order_id.requisition_id.line_ids.filtered(lambda l: l.product_id == self.product_id):
|
||||
if line.product_uom_id != self.product_uom:
|
||||
self.price_unit = line.product_uom_id._compute_price(
|
||||
line.price_unit, self.product_uom)
|
||||
else:
|
||||
self.price_unit = line.price_unit
|
||||
break
|
||||
return res
|
||||
|
||||
@@ -588,11 +588,10 @@ class ProductTemplate(models.Model):
|
||||
outgoing_qty = fields.Float(
|
||||
'Outgoing', compute='_compute_quantities', search='_search_outgoing_qty',
|
||||
compute_sudo=False, digits='Product Unit of Measure')
|
||||
# The goal of these fields is not to be able to search a location_id/warehouse_id but
|
||||
# to properly make these fields "dummy": only used to put some keys in context from
|
||||
# the search view in order to influence computed field
|
||||
location_id = fields.Many2one('stock.location', 'Location', store=False, search=lambda operator, operand, vals: [])
|
||||
warehouse_id = fields.Many2one('stock.warehouse', 'Warehouse', store=False, search=lambda operator, operand, vals: [])
|
||||
# The goal of these fields is to be able to put some keys in context from search view in order
|
||||
# to influence computed field.
|
||||
location_id = fields.Many2one('stock.location', 'Location', store=False)
|
||||
warehouse_id = fields.Many2one('stock.warehouse', 'Warehouse', store=False)
|
||||
route_ids = fields.Many2many(
|
||||
'stock.location.route', 'stock_route_product', 'product_id', 'route_id', 'Routes',
|
||||
domain=[('product_selectable', '=', True)],
|
||||
|
||||
@@ -73,8 +73,8 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='pricelist_id']" position="after">
|
||||
<separator/>
|
||||
<field name="location_id" context="{'location': self}"/>
|
||||
<field name="warehouse_id" context="{'warehouse': self}"/>
|
||||
<field name="location_id" context="{'location': self}" filter_domain="[]"/>
|
||||
<field name="warehouse_id" context="{'warehouse': self}" filter_domain="[]"/>
|
||||
<separator/>
|
||||
<filter name="real_stock_available" string="Available Products" domain="[('qty_available','>',0)]"/>
|
||||
<filter name="real_stock_negative" string="Negative Forecasted Quantity" domain="[('virtual_available','<',0)]"/>
|
||||
|
||||
@@ -404,10 +404,13 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
|
||||
options = options || {};
|
||||
return this.canBeDiscarded(recordID)
|
||||
.then(function (needDiscard) {
|
||||
if (options.noAbandon || (options.readonlyIfRealDiscard && !needDiscard)) {
|
||||
if (options.readonlyIfRealDiscard && !needDiscard) {
|
||||
return;
|
||||
}
|
||||
self.model.discardChanges(recordID);
|
||||
if (options.noAbandon) {
|
||||
return;
|
||||
}
|
||||
if (self.model.canBeAbandoned(recordID)) {
|
||||
self._abandonRecord(recordID);
|
||||
return;
|
||||
|
||||
@@ -4227,6 +4227,45 @@ QUnit.module('ActionManager', {
|
||||
|
||||
actionManager.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('Call twice clearUncommittedChanges in a row does not display twice the discard warning', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
var actionManager = await createActionManager({
|
||||
actions: this.actions,
|
||||
archs: this.archs,
|
||||
data: this.data,
|
||||
intercepts: {
|
||||
clear_uncommitted_changes: function () {
|
||||
actionManager.clearUncommittedChanges();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// execute an action and edit existing record
|
||||
await actionManager.doAction(3);
|
||||
|
||||
await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first'));
|
||||
assert.containsOnce(actionManager, '.o_form_view.o_form_readonly');
|
||||
|
||||
await testUtils.dom.click($('.o_control_panel .o_form_button_edit'));
|
||||
assert.containsOnce(actionManager, '.o_form_view.o_form_editable');
|
||||
|
||||
await testUtils.fields.editInput(actionManager.$('input[name=foo]'), 'val');
|
||||
actionManager.trigger_up('clear_uncommitted_changes');
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.containsOnce($('body'), '.modal'); // confirm discard dialog
|
||||
// confirm discard changes
|
||||
await testUtils.dom.click($('.modal .modal-footer .btn-primary'));
|
||||
|
||||
actionManager.trigger_up('clear_uncommitted_changes');
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.containsNone($('body'), '.modal');
|
||||
|
||||
actionManager.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -26,8 +26,10 @@ class WebsiteBackend(http.Controller):
|
||||
|
||||
current_website = website_id and Website.browse(website_id) or Website.get_current_website()
|
||||
multi_website = request.env.user.has_group('website.group_multi_website')
|
||||
dashboard_data['websites'] = (multi_website and request.env['website'].search([]) or current_website).read(['id', 'name'])
|
||||
for website in dashboard_data['websites']:
|
||||
websites = multi_website and request.env['website'].search([]) or current_website
|
||||
dashboard_data['websites'] = websites.read(['id', 'name'])
|
||||
for rec, website in zip(websites, dashboard_data['websites']):
|
||||
website['domain'] = rec._get_http_domain()
|
||||
if website['id'] == current_website.id:
|
||||
website['selected'] = True
|
||||
|
||||
|
||||
@@ -338,9 +338,9 @@ class View(models.Model):
|
||||
|
||||
cur = Website.get_current_website()
|
||||
if self.env.user.has_group('website.group_website_publisher') and self.env.user.has_group('website.group_multi_website'):
|
||||
qcontext['multi_website_websites_current'] = {'website_id': cur.id, 'name': cur.name, 'domain': cur.domain}
|
||||
qcontext['multi_website_websites_current'] = {'website_id': cur.id, 'name': cur.name, 'domain': cur._get_http_domain()}
|
||||
qcontext['multi_website_websites'] = [
|
||||
{'website_id': website.id, 'name': website.name, 'domain': website.domain}
|
||||
{'website_id': website.id, 'name': website.name, 'domain': website._get_http_domain()}
|
||||
for website in Website.search([]) if website != cur
|
||||
]
|
||||
|
||||
|
||||
@@ -133,6 +133,12 @@ var Dashboard = AbstractAction.extend({
|
||||
}).open();
|
||||
},
|
||||
|
||||
on_go_to_website: function (ev) {
|
||||
ev.preventDefault();
|
||||
var website = _.findWhere(this.websites, {selected: true});
|
||||
window.location.href = $.param.querystring(website.domain + '/', {'fw': website.id});
|
||||
},
|
||||
|
||||
on_save_ga_client_id: function(ga_client_id, ga_analytics_key) {
|
||||
var self = this;
|
||||
return this._rpc({
|
||||
@@ -393,10 +399,14 @@ var Dashboard = AbstractAction.extend({
|
||||
self.on_website_button($(ev.target).data('website-id'));
|
||||
});
|
||||
}
|
||||
|
||||
var $buttons = $(QWeb.render("website.GoToButtons"));
|
||||
$buttons.on('click', this.on_go_to_website.bind(this));
|
||||
|
||||
this.updateControlPanel({
|
||||
cp_content: {
|
||||
$searchview: this.$searchview,
|
||||
$buttons: QWeb.render("website.GoToButtons"),
|
||||
$buttons: $buttons,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -181,21 +181,14 @@ var WebsiteRoot = publicRootData.PublicRoot.extend({
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onWebsiteSwitch: function (ev) {
|
||||
var websiteID = ev.currentTarget.getAttribute('website-id');
|
||||
|
||||
// need to force in each case, even if domain is set
|
||||
// Website 1: localhost; Website 2: 0.0.0.0; website 3: -
|
||||
// when you switch 3 <--> 1, you need to force the website
|
||||
|
||||
var websiteId = ev.currentTarget.getAttribute('website-id');
|
||||
var websiteDomain = ev.currentTarget.getAttribute('domain');
|
||||
var url = $.param.querystring(window.location.href, {fw: websiteID});
|
||||
var url = window.location.href;
|
||||
if (websiteDomain && window.location.hostname !== websiteDomain) {
|
||||
// if domain unchanged, this line will do a nop while we need to refresh
|
||||
// the page to load the new forced website.
|
||||
url = new URL(url);
|
||||
url.hostname = websiteDomain;
|
||||
var path = window.location.pathname + window.location.search + window.location.hash;
|
||||
url = websiteDomain + path;
|
||||
}
|
||||
window.location.href = url;
|
||||
window.location.href = $.param.querystring(url, {'fw': websiteId});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
|
||||
Reference in New Issue
Block a user