[MERGE] Forwardport of saas-12.2 to saas-12.3 up to 01365a3f62
closes odoo/odoo#34747 Signed-off-by: Martin Trigaux (mat) <mat@odoo.com>
This commit is contained in:
@@ -1366,7 +1366,7 @@ class AccountTax(models.Model):
|
||||
base = recompute_base(base, incl_fixed_amount, incl_percent_amount, incl_division_amount)
|
||||
incl_fixed_amount = incl_percent_amount = incl_division_amount = 0
|
||||
store_included_tax_total = True
|
||||
if tax.price_include:
|
||||
if tax.price_include or self._context.get('force_price_include'):
|
||||
if tax.amount_type == 'percent':
|
||||
incl_percent_amount += tax.amount
|
||||
elif tax.amount_type == 'division':
|
||||
@@ -1395,7 +1395,7 @@ class AccountTax(models.Model):
|
||||
cumulated_tax_included_amount = 0
|
||||
for tax in taxes:
|
||||
#compute the tax_amount
|
||||
if tax.price_include and total_included_checkpoints.get(i):
|
||||
if (self._context.get('force_price_include') or tax.price_include) and total_included_checkpoints.get(i):
|
||||
# We know the total to reach for that tax, so we make a substraction to avoid any rounding issues
|
||||
tax_amount = total_included_checkpoints[i] - (base + cumulated_tax_included_amount)
|
||||
cumulated_tax_included_amount = 0
|
||||
@@ -1434,7 +1434,7 @@ class AccountTax(models.Model):
|
||||
'sequence': tax.sequence,
|
||||
'account_id': tax.cash_basis_transition_account_id.id if tax.tax_exigibility == 'on_payment' else repartition_line.account_id.id,
|
||||
'analytic': tax.analytic,
|
||||
'price_include': tax.price_include,
|
||||
'price_include': tax.price_include or self._context.get('force_price_include'),
|
||||
'tax_exigibility': tax.tax_exigibility,
|
||||
'tax_repartition_line_id': repartition_line.id,
|
||||
'tag_ids': [(6, False, (repartition_line.tag_ids + subsequent_tags).ids)],
|
||||
|
||||
@@ -440,7 +440,7 @@ class AccountInvoice(models.Model):
|
||||
vendor_display_name = _('From: ') + invoice.source_email
|
||||
invoice.invoice_icon = '@'
|
||||
else:
|
||||
vendor_display_name = ('Created by: ') + invoice.create_uid.name
|
||||
vendor_display_name = ('Created by: ') + invoice.sudo().create_uid.name
|
||||
invoice.invoice_icon = '#'
|
||||
invoice.vendor_display_name = vendor_display_name
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ class AccountReconcileModel(models.Model):
|
||||
second_analytic_account_id = fields.Many2one('account.analytic.account', string='Second Analytic Account', ondelete='set null')
|
||||
second_analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Second Analytic Tags',
|
||||
relation='account_reconcile_model_second_analytic_tag_rel')
|
||||
|
||||
|
||||
number_entries = fields.Integer(string='Number of entries related to this model', compute='_compute_number_entries')
|
||||
|
||||
@api.multi
|
||||
@@ -113,7 +113,7 @@ class AccountReconcileModel(models.Model):
|
||||
'help': """<p class="o_view_nocontent_empty_folder">{}</p>""".format(_('No move from this reconciliation model')),
|
||||
})
|
||||
return action
|
||||
|
||||
|
||||
@api.multi
|
||||
def _compute_number_entries(self):
|
||||
data = self.env['account.move.line'].read_group([('reconcile_model_id', 'in', self.ids)], ['reconcile_model_ids'], 'reconcile_model_id')
|
||||
@@ -167,19 +167,23 @@ class AccountReconcileModel(models.Model):
|
||||
tax = self.env['account.tax'].browse(tax_res['id'])
|
||||
|
||||
new_aml_dicts.append({
|
||||
'account_id': tax.account_id and tax.account_id.id or base_line_dict['account_id'],
|
||||
'name': tax.name,
|
||||
'account_id': tax_res['account_id'] or base_line_dict['account_id'],
|
||||
'name': tax_res['name'],
|
||||
'partner_id': base_line_dict.get('partner_id'),
|
||||
'debit': tax_res['amount'] > 0 and tax_res['amount'] or 0,
|
||||
'credit': tax_res['amount'] < 0 and -tax_res['amount'] or 0,
|
||||
'analytic_account_id': tax.analytic and base_line_dict['analytic_account_id'],
|
||||
'analytic_tag_ids': tax.analytic and base_line_dict['analytic_tag_ids'],
|
||||
'tax_exigible': tax.tax_exigibility == 'on_payment',
|
||||
'tax_exigible': tax_res['tax_exigibility'],
|
||||
'tax_repartition_line_id': tax_res['tax_repartition_line_id'],
|
||||
'tax_ids': tax_res['tax_ids'],
|
||||
'tag_ids': tax_res['tag_ids']
|
||||
})
|
||||
|
||||
# Handle price included taxes.
|
||||
base_line_dict['debit'] = tax_res['base'] > 0 and tax_res['base'] or base_line_dict['debit']
|
||||
base_line_dict['credit'] = tax_res['base'] < 0 and -tax_res['base'] or base_line_dict['credit']
|
||||
base_line_dict['tag_ids'] = [(6, 0, res['base_tags'])]
|
||||
return new_aml_dicts
|
||||
|
||||
@api.multi
|
||||
@@ -456,7 +460,7 @@ class AccountReconcileModel(models.Model):
|
||||
|
||||
-- if there is a partner, propose all aml of the partner, otherwise propose only the ones
|
||||
-- matching the statement line communication
|
||||
AND
|
||||
AND
|
||||
(
|
||||
(
|
||||
line_partner.partner_id != 0
|
||||
|
||||
@@ -260,7 +260,6 @@ class ResPartner(models.Model):
|
||||
def _invoice_total(self):
|
||||
account_invoice_report = self.env['account.invoice.report']
|
||||
if not self.ids:
|
||||
self.total_invoiced = 0.0
|
||||
return True
|
||||
|
||||
user_currency_id = self.env.user.company_id.currency_id.id
|
||||
|
||||
@@ -98,6 +98,12 @@ class TestReconciliationMatchingRules(AccountingTestCase):
|
||||
'sequence': 1,
|
||||
})
|
||||
|
||||
self.tax21 = self.env['account.tax'].create({
|
||||
'name': '21%',
|
||||
'type_tax_use': 'purchase',
|
||||
'amount': 21,
|
||||
})
|
||||
|
||||
def test_matching_fields(self):
|
||||
''' Test all fields used to restrict the rules's applicability.'''
|
||||
|
||||
@@ -304,6 +310,34 @@ class TestReconciliationMatchingRules(AccountingTestCase):
|
||||
{'partner_id': self.partner_2.id, 'debit': 1000.0, 'credit': 0.0},
|
||||
])
|
||||
|
||||
def test_auto_reconcile_with_tax(self):
|
||||
''' Test auto reconciliation with a tax amount included in the bank statement line'''
|
||||
|
||||
self.rule_1.write({
|
||||
'auto_reconcile': True,
|
||||
'force_tax_included': True,
|
||||
'tax_ids': [(6, 0, self.tax21.ids)],
|
||||
'rule_type': 'writeoff_suggestion',
|
||||
})
|
||||
|
||||
self.bank_line_2.unlink()
|
||||
self.bank_line_1.amount = -121
|
||||
|
||||
self._check_statement_matching(
|
||||
self.rule_1,
|
||||
{
|
||||
self.bank_line_1.id: {'aml_ids': [], 'model': self.rule_1, 'status': 'reconciled'},
|
||||
},
|
||||
self.bank_st
|
||||
)
|
||||
|
||||
# Check first line has been well reconciled.
|
||||
self.assertRecordValues(self.bank_line_1.journal_entry_ids, [
|
||||
{'partner_id': self.partner_1.id, 'debit': 0.0, 'credit': 121.0},
|
||||
{'partner_id': self.partner_1.id, 'debit': 21.0, 'credit': 0.0, 'tax_line_id': self.tax21.id},
|
||||
{'partner_id': self.partner_1.id, 'debit': 100.0, 'credit': 0.0, 'tax_ids': [self.tax21.id]}
|
||||
])
|
||||
|
||||
def test_reverted_move_matching(self):
|
||||
AccountMove = self.env['account.move']
|
||||
move = AccountMove.create({
|
||||
|
||||
@@ -363,6 +363,27 @@
|
||||
<field name="tax_base_amount" invisible="1"/> <!--Not to be removed, we need it so that the call to new() takes it into account when autocompleting the lines of a tax-->
|
||||
<field name="tag_ids" invisible="1"/>
|
||||
</tree>
|
||||
<!-- Form view to cover mobile use -->
|
||||
<form>
|
||||
<group>
|
||||
<field name="tax_line_id" invisible="1"/>
|
||||
<field name="account_id" domain="[('company_id', '=', parent.company_id), ('deprecated', '=', False)]"/>
|
||||
<field name="partner_id" domain="['|', ('parent_id', '=', False), ('is_company', '=', True)]"/>
|
||||
<field name="name"/>
|
||||
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
|
||||
<field name="analytic_tag_ids" groups="analytic.group_analytic_tags" widget="many2many_tags"/>
|
||||
<field name="amount_currency" groups="base.group_multi_currency"/>
|
||||
<field name="company_currency_id" invisible="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="currency_id" options="{'no_create': True}" groups="base.group_multi_currency"/>
|
||||
<field name="debit" sum="Total Debit"/>
|
||||
<field name="credit" sum="Total Credit"/>
|
||||
<field name="tax_ids" string="Taxes Applied" widget="many2many_tags" domain="[('type_tax_use', '=?', parent.tax_type_domain)]" context="{'append_type_to_tax_name': not parent.tax_type_domain}" options="{'no_create': True}"/>
|
||||
<field name="date_maturity" required="0" invisible="context.get('view_no_maturity', False)"/>
|
||||
<field name="recompute_tax_line" invisible="1" readonly="1"/>
|
||||
<field name="tax_line_grouping_key" invisible="1" readonly="1"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
<field name="narration" colspan="4" placeholder="Add an internal note..." nolabel="1" height="50"/>
|
||||
</page>
|
||||
|
||||
@@ -123,14 +123,32 @@ class AccountVoucher(models.Model):
|
||||
@api.multi
|
||||
@api.depends('tax_correction', 'line_ids.price_subtotal')
|
||||
def _compute_total(self):
|
||||
tax_calculation_rounding_method = self.env.user.company_id.tax_calculation_rounding_method
|
||||
for voucher in self:
|
||||
total = 0
|
||||
tax_amount = 0
|
||||
tax_lines_vals_merged = {}
|
||||
for line in voucher.line_ids:
|
||||
tax_info = line.tax_ids.compute_all(line.price_unit, voucher.currency_id, line.quantity, line.product_id, voucher.partner_id)
|
||||
total += tax_info.get('total_included', 0.0)
|
||||
tax_amount += sum([t.get('amount',0.0) for t in tax_info.get('taxes', False)])
|
||||
voucher.amount = total + voucher.tax_correction
|
||||
if tax_calculation_rounding_method == 'round_globally':
|
||||
total += tax_info.get('total_excluded', 0.0)
|
||||
for t in tax_info.get('taxes', False):
|
||||
key = (
|
||||
t['id'],
|
||||
t['account_id'],
|
||||
)
|
||||
if key not in tax_lines_vals_merged:
|
||||
tax_lines_vals_merged[key] = t.get('amount', 0.0)
|
||||
else:
|
||||
tax_lines_vals_merged[key] += t.get('amount', 0.0)
|
||||
else:
|
||||
total += tax_info.get('total_included', 0.0)
|
||||
tax_amount += sum([t.get('amount', 0.0) for t in tax_info.get('taxes', False)])
|
||||
if tax_calculation_rounding_method == 'round_globally':
|
||||
tax_amount = sum([voucher.currency_id.round(t) for t in tax_lines_vals_merged.values()])
|
||||
voucher.amount = total + tax_amount + voucher.tax_correction
|
||||
else:
|
||||
voucher.amount = total + voucher.tax_correction
|
||||
voucher.tax_amount = tax_amount
|
||||
|
||||
@api.onchange('date')
|
||||
|
||||
@@ -50,7 +50,7 @@ class LunchSupplier(models.Model):
|
||||
country_id = fields.Many2one('res.country', related='partner_id.country_id', readonly=False)
|
||||
|
||||
responsible_id = fields.Many2one('res.users', string="Responsible", domain=lambda self: [('groups_id', 'in', self.env.ref('lunch.group_lunch_manager').id)],
|
||||
default=lambda self: self.env.user.partner_id,
|
||||
default=lambda self: self.env.user,
|
||||
help="The responsible is the person that will order lunch for everyone"
|
||||
"It will be used as the 'from' when sending the automatic email")
|
||||
|
||||
|
||||
@@ -438,10 +438,27 @@ var BasicComposer = Widget.extend({
|
||||
* @param {boolean} params.submitForm [optional]
|
||||
*/
|
||||
_processAttachmentChange: function (params) {
|
||||
var self = this,
|
||||
attachments = this.get('attachment_ids'),
|
||||
files = params.files,
|
||||
submitForm = params.submitForm;
|
||||
var self = this;
|
||||
var attachments = this.get('attachment_ids');
|
||||
var files = params.files;
|
||||
var submitForm = params.submitForm;
|
||||
var $form = this.$('form.o_form_binary_form');
|
||||
|
||||
/**
|
||||
* makes a new formData as formData.delete() is not supported by IE or Safari Mobile.
|
||||
*
|
||||
* @return {FormData}
|
||||
*/
|
||||
function makeFormDataWithoutUfile() {
|
||||
var newFormData = new window.FormData();
|
||||
$form.find('input').each(function (index, input) {
|
||||
if (input.name !== 'ufile') {
|
||||
newFormData.append(input.name, input.value);
|
||||
}
|
||||
});
|
||||
return newFormData;
|
||||
}
|
||||
|
||||
_.each(files, function (file) {
|
||||
var attachment = _.findWhere(attachments, {
|
||||
name: file.name,
|
||||
@@ -453,24 +470,20 @@ var BasicComposer = Widget.extend({
|
||||
attachments = _.without(attachments, attachment);
|
||||
}
|
||||
});
|
||||
var $form = this.$('form.o_form_binary_form');
|
||||
if (submitForm) {
|
||||
$form.submit();
|
||||
this._$attachmentButton.prop('disabled', true);
|
||||
} else {
|
||||
var data = new FormData($form[0]);
|
||||
_.each(files, function (file) {
|
||||
// removing existing key with blank data and appending again with file info
|
||||
// In safari, existing key will not be updated when append with new file.
|
||||
data.delete("ufile");
|
||||
data.append("ufile", file, file.name);
|
||||
var formData = makeFormDataWithoutUfile();
|
||||
formData.append("ufile", file, file.name);
|
||||
$.ajax({
|
||||
url: $form.attr("action"),
|
||||
type: "POST",
|
||||
enctype: 'multipart/form-data',
|
||||
processData: false,
|
||||
contentType: false,
|
||||
data: data,
|
||||
data: formData,
|
||||
success: function (result) {
|
||||
var $el = $(result);
|
||||
$.globalEval($el.contents().text());
|
||||
|
||||
@@ -168,9 +168,9 @@ var RenameConversationDialog = Dialog.extend({
|
||||
kwargs: {
|
||||
name: name,
|
||||
}
|
||||
}).then(function (updatedName) {
|
||||
}).then(function () {
|
||||
var channel = self.call('mail_service', 'getThread', self._channelID);
|
||||
channel.setName(updatedName);
|
||||
channel.setName(name);
|
||||
self._callback();
|
||||
});
|
||||
},
|
||||
|
||||
@@ -800,6 +800,7 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
|
||||
_warnMessageModerated: function () {
|
||||
var mailBus = this.call('mail_service', 'getMailBus');
|
||||
if (this.needsModerationByUser()) {
|
||||
this._setModeratedByUser(false);
|
||||
var moderationBox = this.call('mail_service', 'getMailbox', 'moderation');
|
||||
moderationBox.decrementMailboxCounter();
|
||||
moderationBox.removeMessage(this.getID());
|
||||
|
||||
@@ -94,9 +94,7 @@ var MailManager = AbstractService.extend({
|
||||
additionalThreadIDs: data.channel_ids
|
||||
});
|
||||
}
|
||||
if (options.domain && options.domain !== []) {
|
||||
this._addMessageToThreads(message, options);
|
||||
}
|
||||
this._addMessageToThreads(message, options);
|
||||
}
|
||||
return prom;
|
||||
},
|
||||
|
||||
@@ -1426,7 +1426,7 @@ QUnit.test('rename DM conversation', async function (assert) {
|
||||
assert.step(args.method);
|
||||
assert.strictEqual(args.args[0], 1);
|
||||
assert.strictEqual(args.kwargs.name, "Demo");
|
||||
return Promise.resolve("Demo");
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
@@ -2325,9 +2325,9 @@ msgstr ""
|
||||
|
||||
#. module: mass_mailing
|
||||
#. openerp-web
|
||||
#: code:addons/mass_mailing/static/src/js/unsubscribe.js:49
|
||||
#: code:addons/mass_mailing/static/src/js/unsubscribe.js:50
|
||||
#, python-format
|
||||
msgid "You have been <strong>successfully unsubscribed from </strong>."
|
||||
msgid "You have been <strong>successfully unsubscribed from %s</strong>."
|
||||
msgstr ""
|
||||
|
||||
#. module: mass_mailing
|
||||
|
||||
@@ -95,7 +95,7 @@ class MailMail(models.Model):
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def _postprocess_sent_message(self, failure_type=None, **kwargs):
|
||||
def _postprocess_sent_message(self, success_pids, failure_reason=False, failure_type=None):
|
||||
mail_sent = not failure_type # we consider that a recipient error is a failure with mass mailling and show them as failed
|
||||
for mail in self:
|
||||
if mail.mailing_id:
|
||||
@@ -103,5 +103,4 @@ class MailMail(models.Model):
|
||||
mail.statistics_ids.write({'sent': fields.Datetime.now(), 'exception': False})
|
||||
elif mail_sent is False and mail.statistics_ids:
|
||||
mail.statistics_ids.write({'exception': fields.Datetime.now()})
|
||||
return super(MailMail, self)._postprocess_sent_message(failure_type=failure_type, **kwargs)
|
||||
|
||||
return super(MailMail, self)._postprocess_sent_message(success_pids, failure_reason=failure_reason, failure_type=failure_type)
|
||||
|
||||
@@ -46,7 +46,10 @@ odoo.define('mass_mailing.unsubscribe', function (require) {
|
||||
|
||||
var unsubscribed_list = $("input[name='unsubscribed_list']").val();
|
||||
if (unsubscribed_list){
|
||||
$('#subscription_info').html(_t('You have been <strong>successfully unsubscribed from ' + unsubscribed_list + "</strong>."));
|
||||
$('#subscription_info').html(_.str.sprintf(
|
||||
_t("You have been <strong>successfully unsubscribed from %s</strong>."),
|
||||
unsubscribed_list
|
||||
));
|
||||
}
|
||||
else{
|
||||
$('#subscription_info').html(_t('You have been <strong>successfully unsubscribed</strong>.'));
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
<header>
|
||||
<div><title>Odoo</title></div>
|
||||
<div class="text-center">
|
||||
<img t-attf-src="/web/binary/company_logo"/>
|
||||
<img t-attf-src="/web/binary/company_logo?company={{ res_company.id }}"/>
|
||||
</div>
|
||||
</header>
|
||||
<div id="wrap" class="oe_structure oe_empty"/>
|
||||
|
||||
@@ -817,8 +817,6 @@ class MrpProduction(models.Model):
|
||||
moves_to_do = order.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel'))
|
||||
for move in moves_to_do.filtered(lambda m: m.product_qty == 0.0 and m.quantity_done > 0):
|
||||
move.product_uom_qty = move.quantity_done
|
||||
for move in moves_to_do.filtered(lambda m: float_is_zero(m.quantity_done, precision_rounding=m.product_uom.rounding)):
|
||||
move._action_cancel()
|
||||
# MRP do not merge move, catch the result of _action_done in order
|
||||
# to get extra moves.
|
||||
moves_to_do = moves_to_do._action_done()
|
||||
@@ -849,6 +847,11 @@ class MrpProduction(models.Model):
|
||||
if wo.time_ids.filtered(lambda x: (not x.date_end) and (x.loss_type in ('productive', 'performance'))):
|
||||
raise UserError(_('Work order %s is still running') % wo.name)
|
||||
self._check_lots()
|
||||
|
||||
# Cancel unfinished move
|
||||
move_to_cancel = self.move_raw_ids.filtered(lambda m: m.state not in ('done', 'cancel') and float_is_zero(m.quantity_done, precision_rounding=m.product_uom.rounding))
|
||||
move_to_cancel._action_cancel()
|
||||
|
||||
self.post_inventory()
|
||||
# Moves without quantity done are not posted => set them as done instead of canceling. In
|
||||
# case the user edits the MO later on and sets some consumed quantity on those, we do not
|
||||
|
||||
@@ -62,7 +62,8 @@ class PaymentAcquirerAuthorize(models.Model):
|
||||
# The signature key is now '128-character hexadecimal format', while the
|
||||
# transaction key was only 16-character.
|
||||
if len(values['x_trans_key']) == 128:
|
||||
return hmac.new(values['x_trans_key'].decode("hex").encode('utf-8'), data, hashlib.sha512).hexdigest().upper()
|
||||
key = bytes.fromhex(values['x_trans_key'])
|
||||
return hmac.new(key, data, hashlib.sha512).hexdigest().upper()
|
||||
else:
|
||||
return hmac.new(values['x_trans_key'].encode('utf-8'), data, hashlib.md5).hexdigest()
|
||||
|
||||
@@ -315,13 +316,14 @@ class TxAuthorize(models.Model):
|
||||
'acquirer_reference': tree.get('x_trans_id'),
|
||||
'date': fields.Datetime.now(),
|
||||
})
|
||||
if init_state != 'authorized':
|
||||
self.execute_callback()
|
||||
|
||||
if self.payment_token_id:
|
||||
self.payment_token_id.verified = True
|
||||
|
||||
self._set_transaction_done()
|
||||
|
||||
if init_state != 'authorized':
|
||||
self.execute_callback()
|
||||
if tree.get('x_type').lower() == 'auth_only':
|
||||
self.write({'acquirer_reference': tree.get('x_trans_id')})
|
||||
self._set_transaction_authorized()
|
||||
|
||||
@@ -608,12 +608,7 @@ class PurchaseOrderLine(models.Model):
|
||||
if product_lang.description_purchase:
|
||||
self.name += '\n' + product_lang.description_purchase
|
||||
|
||||
fpos = self.order_id.fiscal_position_id
|
||||
if self.env.uid == SUPERUSER_ID:
|
||||
company_id = self.env.user.company_id.id
|
||||
self.taxes_id = fpos.map_tax(self.product_id.supplier_taxes_id.filtered(lambda r: r.company_id.id == company_id))
|
||||
else:
|
||||
self.taxes_id = fpos.map_tax(self.product_id.supplier_taxes_id)
|
||||
self._compute_tax_id()
|
||||
|
||||
self._suggest_quantity()
|
||||
self._onchange_quantity()
|
||||
@@ -685,9 +680,8 @@ class PurchaseOrderLine(models.Model):
|
||||
'''
|
||||
if not self.product_id:
|
||||
return
|
||||
|
||||
seller_min_qty = self.product_id.seller_ids\
|
||||
.filtered(lambda r: r.name == self.order_id.partner_id)\
|
||||
.filtered(lambda r: r.name == self.order_id.partner_id and (not r.product_id or r.product_id == self.product_id))\
|
||||
.sorted(key=lambda r: r.min_qty)
|
||||
if seller_min_qty:
|
||||
self.product_qty = seller_min_qty[0].min_qty or 1.0
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_sale_order_employee,sale.order.employee.expense,sale.model_sale_order,base.group_user,1,0,0,0
|
||||
access_sale_order_employee,sale.order.employee.expense,sale.model_sale_order,base.group_user,0,0,0,0
|
||||
|
@@ -13,6 +13,7 @@
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_unlink" eval="False"/>
|
||||
<field name="active" eval="False"/> <!-- opw-2027005: this rules breaks sale "see own document" -->
|
||||
</record>
|
||||
|
||||
</data>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<field name="inherit_id" ref="hr_expense.hr_expense_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='employee_id']" position="after">
|
||||
<field name="sale_order_id" context="{'sale_show_partner_name': True}"/>
|
||||
<field name="sale_order_id" context="{'sale_show_partner_name': True}" groups="sales_team.group_sale_salesman"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
@@ -17,7 +17,7 @@
|
||||
<field name="inherit_id" ref="hr_expense.view_hr_expense_sheet_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='expense_line_ids']/tree/field[@name='name']" position="after">
|
||||
<field name="sale_order_id" context="{'sale_show_partner_name': True}"/>
|
||||
<field name="sale_order_id" context="{'sale_show_partner_name': True}" groups="sales_team.group_sale_salesman"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -184,7 +184,7 @@ class ProjectTask(models.Model):
|
||||
@api.depends('project_id.sale_line_employee_ids')
|
||||
def _compute_is_project_map_empty(self):
|
||||
for task in self:
|
||||
task.is_project_map_empty = not bool(task.project_id.sale_line_employee_ids)
|
||||
task.is_project_map_empty = not bool(task.sudo().project_id.sale_line_employee_ids)
|
||||
|
||||
@api.onchange('project_id')
|
||||
def _onchange_project(self):
|
||||
|
||||
@@ -760,7 +760,7 @@ function jSignatureClass(parent, options, instanceExtensions) {
|
||||
// these, when enabled, will hover above the sig area. Hence we append them to DOM before canvas.
|
||||
this.$controlbarUpper = (function(){
|
||||
var controlbarstyle = 'padding:0 !important; margin:0 !important;'+
|
||||
'width: 100% !important; height: 0 !important; -ms-touch-action: none;'+
|
||||
'width: 100% !important; height: 0 !important; -ms-touch-action: none; touch-action: none;'+
|
||||
'margin-top:-1em !important; margin-bottom:1em !important;';
|
||||
return $('<div style="'+controlbarstyle+'"></div>').appendTo($parent);
|
||||
})();
|
||||
@@ -771,7 +771,7 @@ function jSignatureClass(parent, options, instanceExtensions) {
|
||||
|
||||
this.$controlbarLower = (function(){
|
||||
var controlbarstyle = 'padding:0 !important; margin:0 !important;'+
|
||||
'width: 100% !important; height: 0 !important; -ms-touch-action: none;'+
|
||||
'width: 100% !important; height: 0 !important; -ms-touch-action: none; touch-action: none;'+
|
||||
'margin-top:-1.5em !important; margin-bottom:1.5em !important; position: relative;';
|
||||
return $('<div style="'+controlbarstyle+'"></div>').appendTo($parent);
|
||||
})();
|
||||
@@ -876,7 +876,7 @@ function jSignatureClass(parent, options, instanceExtensions) {
|
||||
$canvas.bind('mouseup.'+apinamespace, drawEndHandler);
|
||||
$canvas.bind('mousedown.'+apinamespace, drawStartHandler);
|
||||
} else {
|
||||
canvas.ontouchstart = function(e) {
|
||||
canvas.addEventListener('touchstart', function(e) {
|
||||
canvas.onmousedown = canvas.onmouseup = canvas.onmousemove = undef;
|
||||
|
||||
this.fatFingerCompensation = (
|
||||
@@ -886,19 +886,19 @@ function jSignatureClass(parent, options, instanceExtensions) {
|
||||
|
||||
drawStartHandler(e);
|
||||
|
||||
canvas.ontouchend = drawEndHandler;
|
||||
canvas.ontouchstart = drawStartHandler;
|
||||
canvas.ontouchmove = drawMoveHandler;
|
||||
};
|
||||
canvas.onmousedown = function(e) {
|
||||
canvas.addEventListener('touchend', drawEndHandler);
|
||||
canvas.addEventListener('touchstart', drawStartHandler);
|
||||
canvas.addEventListener('touchmove', drawMoveHandler);
|
||||
});
|
||||
canvas.addEventListener('mousedown', function(e) {
|
||||
canvas.ontouchstart = canvas.ontouchend = canvas.ontouchmove = undef;
|
||||
|
||||
drawStartHandler(e);
|
||||
|
||||
canvas.onmousedown = drawStartHandler;
|
||||
canvas.onmouseup = drawEndHandler;
|
||||
canvas.onmousemove = drawMoveHandler;
|
||||
}
|
||||
canvas.addEventListener('mousedown', drawStartHandler);
|
||||
canvas.addEventListener('mouseup', drawEndHandler);
|
||||
canvas.addEventListener('mousemove', drawMoveHandler);
|
||||
});
|
||||
if (window.navigator.msPointerEnabled) {
|
||||
canvas.onmspointerdown = drawStartHandler;
|
||||
canvas.onmspointerup = drawEndHandler;
|
||||
@@ -1084,28 +1084,16 @@ jSignatureClass.prototype.initializeCanvas = function(settings) {
|
||||
settings.width = '100%';
|
||||
}
|
||||
|
||||
$canvas.css(
|
||||
'margin'
|
||||
, 0
|
||||
).css(
|
||||
'padding'
|
||||
, 0
|
||||
).css(
|
||||
'border'
|
||||
, 'none'
|
||||
).css(
|
||||
'height'
|
||||
, settings.height === 'ratio' || !settings.height ? 1 : settings.height.toString(10)
|
||||
).css(
|
||||
'width'
|
||||
, settings.width === 'ratio' || !settings.width ? 1 : settings.width.toString(10)
|
||||
).css(
|
||||
'-ms-touch-action'
|
||||
, 'none'
|
||||
).css(
|
||||
'background-color',
|
||||
settings['background-color']
|
||||
);
|
||||
$canvas.css({
|
||||
'margin': 0,
|
||||
'padding': 0,
|
||||
'border': 'none',
|
||||
'height': settings.height === 'ratio' || !settings.height ? 1 : settings.height.toString(10),
|
||||
'width': settings.width === 'ratio' || !settings.width ? 1 : settings.width.toString(10),
|
||||
'-ms-touch-action': 'none',
|
||||
'touch-action': 'none',
|
||||
'background-color': settings['background-color'],
|
||||
});
|
||||
|
||||
$canvas.appendTo(this.$parent);
|
||||
|
||||
|
||||
@@ -41,6 +41,10 @@
|
||||
});
|
||||
// close modal on submit
|
||||
$('.modal').on('submit', 'form', function (ev) {
|
||||
var form = $(this).closest('form')[0];
|
||||
if (form && form.checkValidity && !form.checkValidity()) {
|
||||
return;
|
||||
}
|
||||
var modal = $(this).parentsUntil('body', '.modal');
|
||||
if (modal.hasClass('o_database_backup')) {
|
||||
$(modal).modal('hide');
|
||||
|
||||
@@ -175,7 +175,7 @@ tour.register('rte_translator', {
|
||||
}, {
|
||||
content: "check bis: content is translated",
|
||||
trigger: '#wrap p font:first:contains(translated french text)',
|
||||
extra_trigger: 'body:not(:has(button[data-action=save]))',
|
||||
extra_trigger: 'html[lang*="fr"] body:not(:has(button[data-action=save]))',
|
||||
}, {
|
||||
content: "check bis: placeholder translation",
|
||||
trigger: 'input[placeholder="test french placeholder"]',
|
||||
|
||||
@@ -327,7 +327,7 @@ odoo.define('website_slides.quiz', function (require) {
|
||||
_extractChannelData: function (slideData){
|
||||
return {
|
||||
id: slideData.channelId,
|
||||
channelEnroll: slideData.enroll,
|
||||
channelEnroll: slideData.channelEnroll,
|
||||
signupAllowed: slideData.signupAllowed
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1531,6 +1531,12 @@ class O2MForm(Form):
|
||||
else:
|
||||
self._values.update(proxy._records[index])
|
||||
|
||||
def _get_modifier(self, field, modifier, default=False, modmap=None, vals=None):
|
||||
if vals is None:
|
||||
vals = {**self._values, '•parent•': self._proxy._parent._values}
|
||||
|
||||
return super()._get_modifier(field, modifier, default=default, modmap=modmap, vals=vals)
|
||||
|
||||
def _onchange_values(self):
|
||||
values = super(O2MForm, self)._onchange_values()
|
||||
# computed o2m may not have a relation_field(?)
|
||||
|
||||
Reference in New Issue
Block a user