[PATCH] Upstream patch - 13012023

This commit is contained in:
Parthiv Patel
2023-01-13 08:35:12 +00:00
parent c66d582112
commit dbf50e7a65
22 changed files with 183 additions and 75 deletions

View File

@@ -1406,6 +1406,30 @@ class AccountMove(models.Model):
'line_ids.payment_id.state',
'line_ids.full_reconcile_id')
def _compute_amount(self):
in_invoices = self.filtered(lambda m: m.move_type == 'in_invoice')
out_invoices = self.filtered(lambda m: m.move_type == 'out_invoice')
others = self.filtered(lambda m: m.move_type not in ('in_invoice', 'out_invoice'))
reversed_mapping = defaultdict(lambda: self.env['account.move'])
for reverse_move in self.env['account.move'].search([
('state', '=', 'posted'),
'|', '|',
'&', ('reversed_entry_id', 'in', in_invoices.ids), ('move_type', '=', 'in_refund'),
'&', ('reversed_entry_id', 'in', out_invoices.ids), ('move_type', '=', 'out_refund'),
'&', ('reversed_entry_id', 'in', others.ids), ('move_type', '=', 'entry'),
]):
reversed_mapping[reverse_move.reversed_entry_id] += reverse_move
caba_mapping = defaultdict(lambda: self.env['account.move'])
caba_company_ids = self.company_id.filtered(lambda c: c.tax_exigibility)
reverse_moves_ids = [move.id for moves in reversed_mapping.values() for move in moves]
for caba_move in self.env['account.move'].search([
('tax_cash_basis_move_id', 'in', self.ids + reverse_moves_ids),
('state', '=', 'posted'),
('move_type', '=', 'entry'),
('company_id', 'in', caba_company_ids.ids)
]):
caba_mapping[caba_move.tax_cash_basis_move_id] += caba_move
for move in self:
if move.payment_state == 'invoicing_legacy':
@@ -1484,17 +1508,10 @@ class AccountMove(models.Model):
new_pmt_state = 'partial'
if new_pmt_state == 'paid' and move.move_type in ('in_invoice', 'out_invoice', 'entry'):
reverse_type = move.move_type == 'in_invoice' and 'in_refund' or move.move_type == 'out_invoice' and 'out_refund' or 'entry'
reverse_moves = self.env['account.move'].search([('reversed_entry_id', '=', move.id), ('state', '=', 'posted'), ('move_type', '=', reverse_type)])
if self.env.company.tax_exigibility:
domain = [
('tax_cash_basis_move_id', 'in', move.ids + reverse_moves.ids),
('state', '=', 'posted'),
('move_type', '=', 'entry')
]
caba_moves = self.env['account.move'].search(domain)
else:
caba_moves = self.env['account.move']
reverse_moves = reversed_mapping[move]
caba_moves = caba_mapping[move]
for reverse_move in reverse_moves:
caba_moves |= caba_mapping[reverse_move]
# We only set 'reversed' state in cas of 1 to 1 full reconciliation with a reverse entry; otherwise, we use the regular 'paid' state
# We ignore potentials cash basis moves reconciled because the transition account of the tax is reconcilable
@@ -4049,6 +4066,18 @@ class AccountMoveLine(models.Model):
if line.reconciled:
raise UserError(_('Lines from "Off-Balance Sheet" accounts cannot be reconciled'))
@api.constrains('product_uom_id')
def _check_product_uom_category_id(self):
for line in self:
if line.product_uom_id and line.product_id and line.product_uom_id.category_id != line.product_id.product_tmpl_id.uom_id.category_id:
raise UserError(_(
"The Unit of Measure (UoM) '%s' you have selected for product '%s', "
"is incompatible with its category : %s.",
line.product_uom_id.name,
line.product_id.name,
line.product_id.product_tmpl_id.uom_id.category_id.name
))
def _affect_tax_report(self):
self.ensure_one()
return self.tax_ids or self.tax_line_id or self.tax_tag_ids.filtered(lambda x: x.applicability == "taxes")

View File

@@ -56,13 +56,26 @@ class ProductTemplate(models.Model):
@api.constrains('uom_id')
def _check_uom_not_in_invoice(self):
for template in self:
aml_domain = [('product_id.product_tmpl_id.id', '=', template.id),
('product_uom_id.category_id.id', '!=', template.uom_id.category_id.id),
]
if self.env['account.move.line'].sudo().search(aml_domain, limit=1):
raise ValidationError(_("This product is already being used in posted Journal Entries.\n"
"If you want to change its Unit of Measure, please archive this product and create a new one."))
self.env['product.template'].flush(['uom_id'])
self._cr.execute("""
SELECT prod_template.id
FROM account_move_line line
JOIN product_product prod_variant ON line.product_id = prod_variant.id
JOIN product_template prod_template ON prod_variant.product_tmpl_id = prod_template.id
JOIN uom_uom template_uom ON prod_template.uom_id = template_uom.id
JOIN uom_category template_uom_cat ON template_uom.category_id = template_uom_cat.id
JOIN uom_uom line_uom ON line.product_uom_id = line_uom.id
JOIN uom_category line_uom_cat ON line_uom.category_id = line_uom_cat.id
WHERE prod_template.id IN %s
AND line.parent_state = 'posted'
AND template_uom_cat.id != line_uom_cat.id
LIMIT 1
""", [tuple(self.ids)])
if self._cr.fetchall():
raise ValidationError(_(
"This product is already being used in posted Journal Entries.\n"
"If you want to change its Unit of Measure, please archive this product and create a new one."
))
class ProductProduct(models.Model):

View File

@@ -677,6 +677,7 @@
<tree decoration-danger="is_refused" editable="bottom">
<field name="date" optional="show"/>
<field name="name"/>
<field name="employee_id" invisible="1"/>
<field name="state" invisible="1"/>
<field name="reference" optional="hide"/>
<field name="analytic_account_id" optional="show" domain="['|', ('company_id', '=', parent.company_id), ('company_id', '=', False)]" groups="analytic.group_analytic_accounting"/>

View File

@@ -43,6 +43,7 @@ Here is how it works:
'report/swissqr_report.xml',
'views/res_bank_view.xml',
'views/account_invoice_view.xml',
'views/account_invoice.xml',
'views/res_config_settings_views.xml',
'views/setup_wizard_views.xml',
],

View File

@@ -304,6 +304,8 @@ class ResPartnerBank(models.Model):
# see https://github.com/arthurdejong/python-stdnum/blob/master/stdnum/iso11649.py
def _eligible_for_qr_code(self, qr_method, debtor_partner, currency):
if qr_method == 'sct_qr' and debtor_partner.country_id.code == 'CH' and self.journal_id.country_code == 'CH':
return False
if qr_method == 'ch_qr':
return self.acc_type == 'iban' and \

View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<flectra>
<template id="l10n_ch_report_invoice_document" inherit_id="account.report_invoice_document">
<xpath expr="//div[@id='qrcode']" position="attributes">
<attribute name="t-if" add="and o.qr_code_method != 'ch_qr'" separator=" "/>
</xpath>
</template>
</flectra>

View File

@@ -119,8 +119,7 @@
<IdPaese t-esc="buyer.country_id.code"/>
<IdCodice t-esc="'0000000'"/>
</IdFiscaleIVA>
<CodiceFiscale t-if="not buyer.vat and buyer.l10n_it_codice_fiscale" t-esc="normalize_codice_fiscale(buyer.l10n_it_codice_fiscale)"/>
<CodiceFiscale t-elif="not buyer.vat and not buyer.l10n_it_codice_fiscale" t-esc="99999999999"/>
<CodiceFiscale t-if="buyer.l10n_it_codice_fiscale" t-esc="normalize_codice_fiscale(buyer.l10n_it_codice_fiscale)"/>
<Anagrafica>
<t t-if="buyer_is_company">
<Denominazione t-esc="format_alphanumeric(buyer.display_name[:80])"/>

View File

@@ -371,6 +371,17 @@ class AccountEdiFormat(models.Model):
return self._import_fattura_pa(decoded_content, invoice)
return super()._update_invoice_from_binary(filename, content, extension, invoice)
def _convert_date_from_xml(self, xsdate_str):
""" Dates in FatturaPA are ISO 8601 date format, pattern '[-]CCYY-MM-DD[Z|(+|-)hh:mm]' """
xsdate_str = xsdate_str.strip()
xsdate_pattern = r"^-?(?P<date>-?\d{4}-\d{2}-\d{2})(?P<tz>[zZ]|[+-]\d{2}:\d{2})?$"
try:
match = re.match(xsdate_pattern, xsdate_str)
converted_date = datetime.strptime(match.group("date"), DEFAULT_FACTUR_ITALIAN_DATE_FORMAT).date()
except Exception:
converted_date = False
return converted_date
def _import_fattura_pa(self, tree, invoice):
""" Decodes a fattura_pa invoice into an invoice.
@@ -473,9 +484,14 @@ class AccountEdiFormat(models.Model):
# Date. <2.1.1.3>
elements = body_tree.xpath('.//DatiGeneraliDocumento/Data')
if elements:
date_str = elements[0].text
date_obj = datetime.strptime(date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
invoice_form.invoice_date = date_obj
document_date = self._convert_date_from_xml(elements[0].text)
if document_date:
invoice_form.invoice_date = document_date
else:
message_to_log.append("%s<br/>%s" % (
_("Document date invalid in XML file:"),
invoice._compose_info_message(elements[0], '.')
))
# Dati Bollo. <2.1.1.6>
elements = body_tree.xpath('.//DatiGeneraliDocumento/DatiBollo/ImportoBollo')
@@ -508,9 +524,16 @@ class AccountEdiFormat(models.Model):
# Due date. <2.4.2.5>
elements = body_tree.xpath('.//DatiPagamento/DettaglioPagamento/DataScadenzaPagamento')
if elements:
date_str = elements[0].text
date_obj = datetime.strptime(date_str, DEFAULT_FACTUR_ITALIAN_DATE_FORMAT)
invoice_form.invoice_date_due = fields.Date.to_string(date_obj)
date_str = elements[0].text.strip()
if date_str:
due_date = self._convert_date_from_xml(date_str)
if due_date:
invoice_form.invoice_date_due = fields.Date.to_string(due_date)
else:
message_to_log.append("%s<br/>%s" % (
_("Payment due date invalid in XML file:"),
invoice._compose_info_message(elements[0], '.')
))
# Total amount. <2.4.2.6>
elements = body_tree.xpath('.//ImportoPagamento')

View File

@@ -36,6 +36,7 @@
<IdPaese>IT</IdPaese>
<IdCodice>00465840031</IdCodice>
</IdFiscaleIVA>
<CodiceFiscale>93026890017</CodiceFiscale>
<Anagrafica>
<Denominazione>Alessi</Denominazione>
</Anagrafica>

View File

@@ -35,6 +35,7 @@
<IdPaese>IT</IdPaese>
<IdCodice>01234560157</IdCodice>
</IdFiscaleIVA>
<CodiceFiscale>01234560157</CodiceFiscale>
<Anagrafica>
<Denominazione>company_2_data</Denominazione>
</Anagrafica>

View File

@@ -80,7 +80,7 @@ class TestItEdi(AccountEdiTestCommon):
cls.italian_partner_a = cls.env['res.partner'].create({
'name': 'Alessi',
'vat': 'IT00465840031',
'l10n_it_codice_fiscale': '00465840031',
'l10n_it_codice_fiscale': '93026890017',
'country_id': cls.env.ref('base.it').id,
'street': 'Via Privata Alessi 6',
'zip': '28887',

View File

@@ -464,11 +464,7 @@ class TestPointOfSaleHttpCommon(flectra.tests.HttpCase):
# Change the default sale pricelist of customers,
# so the js tests can expect deterministically this pricelist when selecting a customer.
env['ir.property']._set_default(
"property_product_pricelist",
"res.partner",
public_pricelist,
)
env['ir.property']._set_default("property_product_pricelist", "res.partner", public_pricelist, main_company)
@flectra.tests.tagged('post_install', '-at_install')

View File

@@ -107,7 +107,8 @@ class SaleReport(models.Model):
if not fields:
fields = {}
res = super()._query(with_clause, fields, groupby, from_clause)
sale_fields = self._select_additional_fields(fields)
current = '(SELECT %s FROM %s GROUP BY %s)' % \
(self._select_pos(fields), self._from_pos(), self._group_by_pos())
(self._select_pos(sale_fields), self._from_pos(), self._group_by_pos())
return '%s UNION ALL %s' % (res, current)

View File

@@ -212,26 +212,25 @@ class Pricelist(models.Model):
continue
if rule.base == 'pricelist' and rule.base_pricelist_id:
price_tmp = rule.base_pricelist_id._compute_price_rule([(product, qty, partner)], date, uom_id)[product.id][0] # TDE: 0 = price, 1 = rule
price = rule.base_pricelist_id.currency_id._convert(price_tmp, self.currency_id, self.env.company, date, round=False)
price = rule.base_pricelist_id._compute_price_rule([(product, qty, partner)], date, uom_id)[product.id][0] # TDE: 0 = price, 1 = rule
src_currency = rule.base_pricelist_id.currency_id
else:
# if base option is public price take sale price else cost price of product
# price_compute returns the price in the context UoM, i.e. qty_uom_id
price = product.price_compute(rule.base)[product.id]
if rule.base == 'standard_price':
src_currency = product.cost_currency_id
else:
src_currency = product.currency_id
if src_currency != self.currency_id:
price = src_currency._convert(
price, self.currency_id, self.env.company, date, round=False)
if price is not False:
# pass the date through the context for further currency conversions
rule_with_date_context = rule.with_context(date=date)
price = rule_with_date_context._compute_price(price, price_uom, product, quantity=qty, partner=partner)
price = rule._compute_price(price, price_uom, product, quantity=qty, partner=partner)
suitable_rule = rule
break
# Final price conversion into pricelist currency
if suitable_rule and suitable_rule.compute_price != 'fixed' and suitable_rule.base != 'pricelist':
if suitable_rule.base == 'standard_price':
cur = product.cost_currency_id
else:
cur = product.currency_id
price = cur._convert(price, self.currency_id, self.env.company, date, round=False)
if not suitable_rule:
cur = product.currency_id
@@ -582,7 +581,6 @@ class PricelistItem(models.Model):
The unused parameters are there to make the full context available for overrides.
"""
self.ensure_one()
date = self.env.context.get('date') or fields.Date.today()
convert_to_price_uom = (lambda price: product.uom_id._compute_price(price, price_uom))
if self.compute_price == 'fixed':
price = convert_to_price_uom(self.fixed_price)
@@ -592,30 +590,19 @@ class PricelistItem(models.Model):
# complete formula
price_limit = price
price = (price - (price * (self.price_discount / 100))) or 0.0
if self.base == 'standard_price':
price_currency = product.cost_currency_id
elif self.base == 'pricelist':
price_currency = self.currency_id # Already converted before to the pricelist currency
else:
price_currency = product.currency_id
if self.price_round:
price = tools.float_round(price, precision_rounding=self.price_round)
def convert_to_base_price_currency(amount):
return self.currency_id._convert(amount, price_currency, self.env.company, date, round=False)
if self.price_surcharge:
price_surcharge = convert_to_base_price_currency(self.price_surcharge)
price_surcharge = convert_to_price_uom(price_surcharge)
price += price_surcharge
price += convert_to_price_uom(self.price_surcharge)
if self.price_min_margin:
price_min_margin = convert_to_base_price_currency(self.price_min_margin)
price_min_margin = convert_to_price_uom(price_min_margin)
price_min_margin = convert_to_price_uom(self.price_min_margin)
price = max(price, price_limit + price_min_margin)
if self.price_max_margin:
price_max_margin = convert_to_base_price_currency(self.price_max_margin)
price_max_margin = convert_to_price_uom(price_max_margin)
price_max_margin = convert_to_price_uom(self.price_max_margin)
price = min(price, price_limit + price_max_margin)
return price

View File

@@ -257,3 +257,24 @@ class TestProductPricelist(TransactionCase):
})
# product price use the currency of the pricelist
self.assertEqual(product.price, 10090)
def test_23_diff_curr_rounding(self):
""" Make sure rounding is applied after the currency conversion"""
pricelist = self.ProductPricelist.create({
'name': 'Currency Pricelist',
'currency_id': self.new_currency.id,
'item_ids': [(0, 0, {
'compute_price': 'formula',
'base': 'list_price',
'price_discount': 42.328745867,
'price_round': 1.00,
})]
})
product = self.computer_SC234
product.lst_price = 450.0
product = product.with_context({
'pricelist': pricelist.id, 'quantity': 1
})
self.assertEqual(product.price, 2595)

View File

@@ -144,12 +144,23 @@ class SaleReport(models.Model):
""" % (groupby)
return groupby_
def _select_additional_fields(self, fields):
"""Hook to return additional fields SQL specification for select part of the table query.
:param dict fields: additional fields info provided by _query overrides (old API), prefer overriding
_select_additional_fields instead.
:returns: mapping field -> SQL computation of the field
:rtype: dict
"""
return fields
def _query(self, with_clause='', fields=None, groupby='', from_clause=''):
if not fields:
fields = {}
sale_report_fields = self._select_additional_fields(fields)
with_ = ("WITH %s" % with_clause) if with_clause else ""
return '%s (SELECT %s FROM %s WHERE l.display_type IS NULL GROUP BY %s)' % \
(with_, self._select_sale(fields), self._from_sale(from_clause), self._group_by_sale(groupby))
(with_, self._select_sale(sale_report_fields), self._from_sale(from_clause), self._group_by_sale(groupby))
def init(self):
# self._table = sale_report

View File

@@ -9,6 +9,6 @@ class SaleReport(models.Model):
margin = fields.Float('Margin')
def _query(self, with_clause='', fields={}, groupby='', from_clause=''):
def _select_additional_fields(self, fields):
fields['margin'] = ", SUM(l.margin / CASE COALESCE(s.currency_rate, 0) WHEN 0 THEN 1.0 ELSE s.currency_rate END) AS margin"
return super(SaleReport, self)._query(with_clause, fields, groupby, from_clause)
return super()._select_additional_fields(fields)

View File

@@ -9,7 +9,11 @@ class SaleReport(models.Model):
warehouse_id = fields.Many2one('stock.warehouse', 'Warehouse', readonly=True)
def _query(self, with_clause='', fields={}, groupby='', from_clause=''):
def _group_by_sale(self, groupby=''):
res = super()._group_by_sale(groupby)
res += """, s.warehouse_id"""
return res
def _select_additional_fields(self, fields):
fields['warehouse_id'] = ", s.warehouse_id as warehouse_id"
groupby += ', s.warehouse_id'
return super(SaleReport, self)._query(with_clause, fields, groupby, from_clause)
return super()._select_additional_fields(fields)

View File

@@ -361,7 +361,9 @@ class Survey(http.Controller):
'page_number': page_ids.index(survey_data['page'].id) + (1 if survey_sudo.progression_mode == 'number' else 0)
})
elif survey_sudo.questions_layout == 'page_per_question':
page_ids = survey_sudo.question_ids.ids
page_ids = (answer_sudo.predefined_question_ids.ids
if not answer_sudo.is_session_answer
else survey_sudo.question_ids.ids)
survey_progress = request.env.ref('survey.survey_progression')._render({
'survey': survey_sudo,
'page_ids': page_ids,

View File

@@ -30,7 +30,12 @@
<t t-set="page_number" t-value="page_ids.index(page.id) + (1 if survey.progression_mode == 'number' else 0)"/>
</t>
<t t-else="">
<t t-set="page_ids" t-value="survey.question_ids.ids"/>
<t t-if="not answer.is_session_answer">
<t t-set="page_ids" t-value="answer.predefined_question_ids.ids"/>
</t>
<t t-else="">
<t t-set="page_ids" t-value="survey.question_ids.ids"/>
</t>
<t t-set="page_number" t-value="page_ids.index(question.id)"/>
</t>
</t>
@@ -53,7 +58,6 @@
<t t-if="answer.test_entry" t-call="survey.survey_button_form_view" />
<div class="wrap o_survey_wrap d-flex">
<div class="container o_survey_form d-flex flex-column mb-5">
<t t-call="survey.survey_fill_header" />
<t t-call="survey.survey_fill_form" />
</div>

View File

@@ -300,7 +300,7 @@ class Post(models.Model):
is_correct = fields.Boolean('Correct', help='Correct answer or answer accepted')
parent_id = fields.Many2one('forum.post', string='Question', ondelete='cascade', readonly=True, index=True)
self_reply = fields.Boolean('Reply to own question', compute='_is_self_reply', store=True)
child_ids = fields.One2many('forum.post', 'parent_id', string='Post Answers', domain=lambda self: [('forum_id', '=', self.forum_id.id)])
child_ids = fields.One2many('forum.post', 'parent_id', string='Post Answers', domain=lambda self: [('forum_id', 'in', self.forum_id.ids)])
child_count = fields.Integer('Answers', compute='_get_child_count', store=True)
uid_has_answered = fields.Boolean('Has Answered', compute='_get_uid_has_answered')
has_validated_answer = fields.Boolean('Is answered', compute='_get_has_validated_answer', store=True)

View File

@@ -8,7 +8,11 @@ class SaleReport(models.Model):
website_id = fields.Many2one('website', readonly=True)
def _query(self, with_clause='', fields={}, groupby='', from_clause=''):
def _group_by_sale(self, groupby=''):
res = super()._group_by_sale(groupby)
res += """,s.website_id"""
return res
def _select_additional_fields(self, fields):
fields['website_id'] = ", s.website_id as website_id"
groupby += ', s.website_id'
return super(SaleReport, self)._query(with_clause, fields, groupby, from_clause)
return super()._select_additional_fields(fields)