[PATCH] Upstream patch - 12082022

This commit is contained in:
Parthiv Patel
2022-08-12 08:35:22 +00:00
parent bc6f8c3567
commit a4f88cb3b6
10 changed files with 238 additions and 68 deletions

View File

@@ -910,3 +910,74 @@ class TestAccountPaymentRegister(AccountTestInvoicingCommon):
'partner_bank_id': self.comp_bank_account2.id,
},
])
def test_payment_method_different_type_single_batch_not_grouped(self):
""" Test payment methods when paying a bill and a refund with separated payments (1000 + -2000)."""
in_refund = self.env['account.move'].create({
'move_type': 'in_refund',
'date': '2017-01-01',
'invoice_date': '2017-01-01',
'partner_id': self.partner_a.id,
'invoice_line_ids': [(0, 0, {'product_id': self.product_a.id, 'price_unit': 1600.0})],
})
in_refund.action_post()
active_ids = (self.in_invoice_1 + in_refund).ids
payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({
'group_payment': False,
})._create_payments()
self.assertRecordValues(payments[0], [
{
'ref': 'BILL/2017/01/0001',
'payment_method_id': self.bank_journal_1.outbound_payment_method_ids[0].id,
'payment_type': 'outbound',
}
])
self.assertRecordValues(payments[1], [
{
'ref': 'RBILL/2017/01/0001',
'payment_method_id': self.bank_journal_1.inbound_payment_method_ids[0].id,
'payment_type': 'inbound',
},
])
self.assertRecordValues(payments[0].line_ids.sorted('balance'), [
# == Payment 1: to pay in_invoice_1 ==
# Liquidity line:
{
'debit': 0.0,
'credit': 1000.0,
'currency_id': self.company_data['currency'].id,
'amount_currency': -1000.0,
'reconciled': False,
},
# Payable line:
{
'debit': 1000.0,
'credit': 0.0,
'currency_id': self.company_data['currency'].id,
'amount_currency': 1000.0,
'reconciled': True,
},
])
self.assertRecordValues(payments[1].line_ids.sorted('balance'), [
# == Payment 2: to pay in_refund_1 ==
# Payable line:
{
'debit': 0.0,
'credit': 1600.0,
'currency_id': self.company_data['currency'].id,
'amount_currency': -1600.0,
'reconciled': True,
},
# Liquidity line:
{
'debit': 1600.0,
'credit': 0.0,
'currency_id': self.company_data['currency'].id,
'amount_currency': 1600.0,
'reconciled': False,
},
])

View File

@@ -179,6 +179,13 @@ class AccountPaymentRegister(models.TransientModel):
# Sending money to a bank account owned by a partner.
return batch_result['lines'].partner_id.bank_ids.filtered(lambda x: x.company_id.id in (False, company.id))._origin
@api.model
def _get_batch_available_payment_methods(self, journal, payment_type):
if payment_type == 'inbound':
return journal.inbound_payment_method_ids._origin
else:
return journal.outbound_payment_method_ids._origin
@api.model
def _get_line_batch_key(self, line):
''' Turn the line passed as parameter to a dictionary defining on which way the lines
@@ -353,26 +360,21 @@ class AccountPaymentRegister(models.TransientModel):
'journal_id.outbound_payment_method_ids')
def _compute_payment_method_fields(self):
for wizard in self:
if wizard.payment_type == 'inbound':
wizard.available_payment_method_ids = wizard.journal_id.inbound_payment_method_ids
if wizard.can_edit_wizard:
wizard.available_payment_method_ids = wizard._get_batch_available_payment_methods(wizard.journal_id, wizard.payment_type)
wizard.hide_payment_method = len(wizard.available_payment_method_ids) == 1 and wizard.available_payment_method_ids.code == 'manual'
else:
wizard.available_payment_method_ids = wizard.journal_id.outbound_payment_method_ids
wizard.hide_payment_method = len(wizard.available_payment_method_ids) == 1 and wizard.available_payment_method_ids.code == 'manual'
wizard.available_payment_method_ids = None
wizard.hide_payment_method = False
@api.depends('payment_type',
'journal_id.inbound_payment_method_ids',
'journal_id.outbound_payment_method_ids')
def _compute_payment_method_id(self):
for wizard in self:
if wizard.payment_type == 'inbound':
available_payment_methods = wizard.journal_id.inbound_payment_method_ids
else:
available_payment_methods = wizard.journal_id.outbound_payment_method_ids
# Select the first available one by default.
if available_payment_methods:
wizard.payment_method_id = available_payment_methods[0]._origin
available_payment_methods = wizard._get_batch_available_payment_methods(wizard.journal_id, wizard.payment_type)
if wizard.can_edit_wizard and available_payment_methods:
wizard.payment_method_id = available_payment_methods[:1]
else:
wizard.payment_method_id = False
@@ -523,6 +525,11 @@ class AccountPaymentRegister(models.TransientModel):
else:
partner_bank_id = batch_result['key_values']['partner_bank_id']
payment_method = self.payment_method_id
if batch_values['payment_type'] != payment_method.payment_type:
payment_method = self._get_batch_available_payment_methods(self.journal_id, batch_values['payment_type'])[:1]
return {
'date': self.payment_date,
'amount': batch_values['source_amount_currency'],
@@ -533,7 +540,7 @@ class AccountPaymentRegister(models.TransientModel):
'currency_id': batch_values['source_currency_id'],
'partner_id': batch_values['partner_id'],
'partner_bank_id': partner_bank_id,
'payment_method_id': self.payment_method_id.id,
'payment_method_id': payment_method.id,
'destination_account_id': batch_result['lines'][0].account_id.id
}

View File

@@ -21,18 +21,18 @@ class AccountMove(models.Model):
self.journal_id.l10n_latam_use_documents:
return super()._get_l10n_latam_documents_domain()
if self.journal_id.type == 'sale':
if self.move_type == 'out_refund':
internal_types_domain = ('internal_type', '=', 'credit_note')
else:
internal_types_domain = ('internal_type', 'in', ['invoice', 'debit_note'])
domain = [('country_id.code', '=', 'CL'), internal_types_domain]
domain = [('country_id.code', '=', 'CL')]
if self.move_type in ['in_invoice', 'out_invoice']:
domain += [('internal_type', 'in', ['invoice', 'debit_note', 'invoice_in'])]
elif self.move_type in ['in_refund', 'out_refund']:
domain += [('internal_type', '=', 'credit_note')]
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
if self.move_type == 'in_refund':
internal_types_domain = ('internal_type', '=', 'credit_note')
else:
internal_types_domain = ('internal_type', 'in', ['invoice', 'debit_note', 'credit_note', 'invoice_in'])
internal_types_domain = ('internal_type', 'in', ['invoice', 'debit_note', 'invoice_in'])
domain = [
('country_id.code', '=', 'CL'),
internal_types_domain,
@@ -41,8 +41,6 @@ class AccountMove(models.Model):
domain += [('code', 'not in', ['39', '70', '71', '914', '911'])]
elif self.partner_id.l10n_cl_sii_taxpayer_type == '1' and self.partner_id_vat == '60805000-0':
domain += [('code', 'not in', ['39', '70', '71'])]
if self.move_type == 'in_invoice':
domain += [('internal_type', '!=', 'credit_note')]
elif self.partner_id.l10n_cl_sii_taxpayer_type == '2':
domain += [('code', 'in', ['70', '71', '56', '61'])]
elif self.partner_id.l10n_cl_sii_taxpayer_type == '3':

View File

@@ -42,10 +42,10 @@
<xpath expr="//field[@name='journal_id']/.." position="after">
<field name="l10n_latam_document_type_id"
attrs="{'invisible': [('l10n_latam_use_documents', '=', False)], 'required': [('l10n_latam_use_documents', '=', True)], 'readonly': [('posted_before', '=', True)]}"
attrs="{'invisible': [('l10n_latam_use_documents', '=', False)], 'required': [('partner_id', '!=', False), ('l10n_latam_use_documents', '=', True)], 'readonly': [('posted_before', '=', True)]}"
domain="[('id', 'in', l10n_latam_available_document_type_ids)]" options="{'no_open': True, 'no_create': True}"/>
<field name="l10n_latam_document_number"
attrs="{'invisible': ['|', ('l10n_latam_use_documents', '=', False), ('l10n_latam_manual_document_number', '=', False), '|', '|', ('l10n_latam_use_documents', '=', False), ('highest_name', '!=', False), ('state', '!=', 'draft')], 'required': ['|', ('l10n_latam_manual_document_number', '=', True), ('highest_name', '=', False)], 'readonly': [('posted_before', '=', True), ('state', '!=', 'draft')]}"/>
attrs="{'invisible': ['|', ('l10n_latam_use_documents', '=', False), ('l10n_latam_manual_document_number', '=', False), '|', '|', ('l10n_latam_use_documents', '=', False), ('highest_name', '!=', False), ('state', '!=', 'draft')], 'required': [('partner_id', '!=', False), '|', ('l10n_latam_manual_document_number', '=', True), ('highest_name', '=', False)], 'readonly': [('posted_before', '=', True), ('state', '!=', 'draft')]}"/>
</xpath>
<!-- on latam_documents we use document_number to set name -->

View File

@@ -239,9 +239,12 @@ class SendSMS(models.TransientModel):
subtype_id = self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note')
messages = self.env['mail.message']
all_bodies = self._prepare_body_values(records)
for record in records:
messages |= record._message_sms(
self.body, subtype_id=subtype_id,
messages += record._message_sms(
all_bodies[record.id],
subtype_id=subtype_id,
number_field=self.number_field_name,
sms_numbers=self.sanitized_numbers.split(',') if self.sanitized_numbers else None)
return messages

View File

@@ -207,10 +207,11 @@ class TestSMSComposerComment(TestMailFullCommon, TestRecipients):
class TestSMSComposerBatch(TestMailFullCommon):
@classmethod
def setUpClass(cls):
super(TestSMSComposerBatch, cls).setUpClass()
cls._test_body = 'Zizisse an SMS.'
cls._test_body = 'Hello ${object.name} zizisse an SMS.'
cls._create_records_for_batch('mail.test.sms', 3)
cls.sms_template = cls._create_sms_template('mail.test.sms')
@@ -229,8 +230,12 @@ class TestSMSComposerBatch(TestMailFullCommon):
with self.mockSMSGateway():
messages = composer._action_send_sms()
for record in self.records:
self.assertSMSNotification([{'partner': r.customer_id} for r in self.records], 'Zizisse an SMS.', messages)
for record, message in zip(self.records, messages):
self.assertSMSNotification(
[{'partner': record.customer_id}],
'Hello %s zizisse an SMS.' % record.name,
message
)
def test_composer_batch_active_ids(self):
with self.with_user('employee'):
@@ -245,8 +250,12 @@ class TestSMSComposerBatch(TestMailFullCommon):
with self.mockSMSGateway():
messages = composer._action_send_sms()
for record in self.records:
self.assertSMSNotification([{'partner': r.customer_id} for r in self.records], 'Zizisse an SMS.', messages)
for record, message in zip(self.records, messages):
self.assertSMSNotification(
[{'partner': record.customer_id}],
'Hello %s zizisse an SMS.' % record.name,
message
)
def test_composer_batch_domain(self):
with self.with_user('employee'):
@@ -262,8 +271,12 @@ class TestSMSComposerBatch(TestMailFullCommon):
with self.mockSMSGateway():
messages = composer._action_send_sms()
for record in self.records:
self.assertSMSNotification([{'partner': r.customer_id} for r in self.records], 'Zizisse an SMS.', messages)
for record, message in zip(self.records, messages):
self.assertSMSNotification(
[{'partner': record.customer_id}],
'Hello %s zizisse an SMS.' % record.name,
message
)
def test_composer_batch_res_ids(self):
with self.with_user('employee'):
@@ -278,8 +291,12 @@ class TestSMSComposerBatch(TestMailFullCommon):
with self.mockSMSGateway():
messages = composer._action_send_sms()
for record in self.records:
self.assertSMSNotification([{'partner': r.customer_id} for r in self.records], 'Zizisse an SMS.', messages)
for record, message in zip(self.records, messages):
self.assertSMSNotification(
[{'partner': record.customer_id}],
'Hello %s zizisse an SMS.' % record.name,
message
)
class TestSMSComposerMass(TestMailFullCommon):
@@ -287,9 +304,9 @@ class TestSMSComposerMass(TestMailFullCommon):
@classmethod
def setUpClass(cls):
super(TestSMSComposerMass, cls).setUpClass()
cls._test_body = 'Zizisse an SMS.'
cls._test_body = 'Hello ${object.name} zizisse an SMS.'
cls._create_records_for_batch('mail.test.sms', 3)
cls._create_records_for_batch('mail.test.sms', 10)
cls.sms_template = cls._create_sms_template('mail.test.sms')
def test_composer_mass_active_domain(self):
@@ -308,7 +325,10 @@ class TestSMSComposerMass(TestMailFullCommon):
composer.action_send_sms()
for record in self.records:
self.assertSMSOutgoing(record.customer_id, None, content=self._test_body)
self.assertSMSOutgoing(
record.customer_id, None,
content='Hello %s zizisse an SMS.' % record.name
)
def test_composer_mass_active_domain_w_template(self):
with self.with_user('employee'):
@@ -326,7 +346,10 @@ class TestSMSComposerMass(TestMailFullCommon):
composer.action_send_sms()
for record in self.records:
self.assertSMSOutgoing(record.customer_id, None, content='Dear %s this is an SMS.' % record.display_name)
self.assertSMSOutgoing(
record.customer_id, None,
content='Dear %s this is an SMS.' % record.display_name
)
def test_composer_mass_active_ids(self):
with self.with_user('employee'):
@@ -342,8 +365,11 @@ class TestSMSComposerMass(TestMailFullCommon):
with self.mockSMSGateway():
composer.action_send_sms()
for partner in self.partners:
self.assertSMSOutgoing(partner, None, content=self._test_body)
for partner, record in zip(self.partners, self.records):
self.assertSMSOutgoing(
partner, None,
content='Hello %s zizisse an SMS.' % record.name
)
def test_composer_mass_active_ids_w_blacklist(self):
self.env['phone.blacklist'].create([{
@@ -365,10 +391,17 @@ class TestSMSComposerMass(TestMailFullCommon):
with self.mockSMSGateway():
composer.action_send_sms()
for partner in self.partners[5:]:
self.assertSMSOutgoing(partner, partner.phone_sanitized, content=self._test_body)
for partner in self.partners[:5]:
self.assertSMSCanceled(partner, partner.phone_sanitized, error_code='sms_blacklist', content=self._test_body)
for partner, record in zip(self.partners[5:], self.records[5:]):
self.assertSMSOutgoing(
partner, partner.phone_sanitized,
content='Hello %s zizisse an SMS.' % record.name
)
for partner, record in zip(self.partners[:5], self.records[:5]):
self.assertSMSCanceled(
partner, partner.phone_sanitized,
error_code='sms_blacklist',
content='Hello %s zizisse an SMS.' % record.name
)
def test_composer_mass_active_ids_wo_blacklist(self):
self.env['phone.blacklist'].create([{
@@ -390,17 +423,22 @@ class TestSMSComposerMass(TestMailFullCommon):
with self.mockSMSGateway():
composer.action_send_sms()
for partner in self.partners:
self.assertSMSOutgoing(partner, partner.phone_sanitized, content=self._test_body)
for partner, record in zip(self.partners, self.records):
self.assertSMSOutgoing(
partner, partner.phone_sanitized,
content='Hello %s zizisse an SMS.' % record.name
)
def test_composer_mass_active_ids_w_blacklist_and_done(self):
""" Create some duplicates + blacklist. record[5] will have duplicated
number on 6 and 7. """
self.env['phone.blacklist'].create([{
'number': p.phone_sanitized,
'active': True,
} for p in self.partners[:5]])
for p in self.partners[8:]:
p.mobile = self.partners[8].mobile
self.assertEqual(p.phone_sanitized, self.partners[8].phone_sanitized)
for p in self.partners[5:8]:
p.mobile = self.partners[5].mobile
self.assertEqual(p.phone_sanitized, self.partners[5].phone_sanitized)
with self.with_user('employee'):
composer = self.env['sms.composer'].with_context(
@@ -416,12 +454,29 @@ class TestSMSComposerMass(TestMailFullCommon):
with self.mockSMSGateway():
composer.action_send_sms()
for partner in self.partners[8:]:
self.assertSMSOutgoing(partner, partner.phone_sanitized, content=self._test_body)
for partner in self.partners[5:8]:
self.assertSMSCanceled(partner, partner.phone_sanitized, error_code='sms_duplicate', content=self._test_body)
for partner in self.partners[:5]:
self.assertSMSCanceled(partner, partner.phone_sanitized, error_code='sms_blacklist', content=self._test_body)
self.assertSMSOutgoing(
self.partners[5], self.partners[5].phone_sanitized,
content='Hello %s zizisse an SMS.' % self.records[5].name
)
for partner, record in zip(self.partners[8:], self.records[8:]):
self.assertSMSOutgoing(
partner, partner.phone_sanitized,
content='Hello %s zizisse an SMS.' % record.name
)
# duplicates
for partner, record in zip(self.partners[6:8], self.records[6:8]):
self.assertSMSCanceled(
partner, partner.phone_sanitized,
error_code='sms_duplicate',
content='Hello %s zizisse an SMS.' % record.name
)
# blacklist
for partner, record in zip(self.partners[:5], self.records[:5]):
self.assertSMSCanceled(
partner, partner.phone_sanitized,
error_code='sms_blacklist',
content='Hello %s zizisse an SMS.' % record.name
)
def test_composer_mass_active_ids_w_template(self):
with self.with_user('employee'):
@@ -438,7 +493,10 @@ class TestSMSComposerMass(TestMailFullCommon):
composer.action_send_sms()
for record in self.records:
self.assertSMSOutgoing(record.customer_id, None, content='Dear %s this is an SMS.' % record.display_name)
self.assertSMSOutgoing(
record.customer_id, None,
content='Dear %s this is an SMS.' % record.display_name
)
def test_composer_mass_active_ids_w_template_and_lang(self):
self.env['res.lang']._activate_lang('fr_FR')
@@ -472,9 +530,15 @@ class TestSMSComposerMass(TestMailFullCommon):
for record in self.records:
if record.customer_id == self.partners[2]:
self.assertSMSOutgoing(record.customer_id, None, content='Cher·e· %s ceci est un SMS.' % record.display_name)
self.assertSMSOutgoing(
record.customer_id, None,
content='Cher·e· %s ceci est un SMS.' % record.display_name
)
else:
self.assertSMSOutgoing(record.customer_id, None, content='Dear %s this is an SMS.' % record.display_name)
self.assertSMSOutgoing(
record.customer_id, None,
content='Dear %s this is an SMS.' % record.display_name
)
def test_composer_mass_active_ids_w_template_and_log(self):
with self.with_user('employee'):
@@ -491,7 +555,10 @@ class TestSMSComposerMass(TestMailFullCommon):
composer.action_send_sms()
for record in self.records:
self.assertSMSOutgoing(record.customer_id, None, content='Dear %s this is an SMS.' % record.display_name)
self.assertSMSOutgoing(
record.customer_id, None,
content='Dear %s this is an SMS.' % record.display_name
)
self.assertSMSLogged(record, 'Dear %s this is an SMS.' % record.display_name)
def test_composer_template_context_action(self):
@@ -541,7 +608,10 @@ class TestSMSComposerMass(TestMailFullCommon):
messages = composer._action_send_sms()
number = self.partners[2].phone_get_sanitized_number()
self.assertSMSNotification([{'partner': test_record_2.customer_id, 'number': number}], "Hello %s ceci est en français." % test_record_2.display_name, messages)
self.assertSMSNotification(
[{'partner': test_record_2.customer_id, 'number': number}],
"Hello %s ceci est en français." % test_record_2.display_name, messages
)
# Composer creation with context from a template context action (simulate) - mass (multiple recipient)
with self.with_user('employee'):
@@ -563,5 +633,11 @@ class TestSMSComposerMass(TestMailFullCommon):
with self.mockSMSGateway():
composer.action_send_sms()
self.assertSMSOutgoing(test_record_1.customer_id, None, content='Dear %s this is an SMS.' % test_record_1.display_name)
self.assertSMSOutgoing(test_record_2.customer_id, None, content="Hello %s ceci est en français." % test_record_2.display_name)
self.assertSMSOutgoing(
test_record_1.customer_id, None,
content='Dear %s this is an SMS.' % test_record_1.display_name
)
self.assertSMSOutgoing(
test_record_2.customer_id, None,
content="Hello %s ceci est en français." % test_record_2.display_name
)

View File

@@ -628,6 +628,9 @@ function parseMonetary(value, field, options) {
}
currency = session.get_currency(currency_id);
}
if (!currency) {
return parseFloat(value);
}
if (!value.includes(currency.symbol)) {
throw new Error(_.str.sprintf(core._t("'%s' is not a correct monetary field"), value));
}

View File

@@ -261,7 +261,7 @@ QUnit.test('parse integer', function(assert) {
});
QUnit.test('parse monetary', function(assert) {
assert.expect(13);
assert.expect(15);
var originalCurrencies = session.currencies;
const originalParameters = _.clone(core._t.database.parameters);
session.currencies = {
@@ -293,11 +293,13 @@ QUnit.test('parse monetary', function(assert) {
const nbsp = '\u00a0';
_.extend(core._t.database.parameters, {
grouping: [3, 0],
decimal_point: ',',
decimal_point: '.',
thousands_sep: nbsp,
});
assert.strictEqual(fieldUtils.parse.monetary(`1${nbsp}000.00${nbsp}`, {}, {currency_id: 1}), 1000);
assert.strictEqual(fieldUtils.parse.monetary(`$${nbsp}1${nbsp}000.00`, {}, {currency_id: 3}), 1000);
assert.strictEqual(fieldUtils.parse.monetary(`1${nbsp}000.00`), 1000);
assert.strictEqual(fieldUtils.parse.monetary(`1${nbsp}000${nbsp}000.00`), 1000000);
session.currencies = originalCurrencies;
core._t.database.parameters = originalParameters;

View File

@@ -13,6 +13,7 @@ from werkzeug.datastructures import OrderedMultiDict
from werkzeug.exceptions import NotFound
from flectra import api, fields, models, tools, http
from flectra.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG
from flectra.addons.http_routing.models.ir_http import slugify, _guess_mimetype, url_for
from flectra.addons.website.models.ir_http import sitemap_qs2dom
from flectra.addons.portal.controllers.portal import pager
@@ -238,9 +239,11 @@ class Website(models.Model):
vals['favicon'] = tools.image_process(vals['favicon'], size=(256, 256), crop='center', output_format='ICO')
def unlink(self):
website = self.search([('id', 'not in', self.ids)], limit=1)
if not website:
raise UserError(_('You must keep at least one website.'))
if not self.env.context.get(MODULE_UNINSTALL_FLAG, False):
website = self.search([('id', 'not in', self.ids)], limit=1)
if not website:
raise UserError(_('You must keep at least one website.'))
# Do not delete invoices, delete what's strictly necessary
attachments_to_unlink = self.env['ir.attachment'].search([
('website_id', 'in', self.ids),

View File

@@ -252,3 +252,10 @@
z-index: $zindex-dropdown - 1;
}
}
// Country snippet
.country_events_list .o_wevent_sidebar_title > img {
max-height: 1em;
vertical-align: top;
margin: 2px 0.2em 0;
}