[PATCH] Upstream patch - 16032023

This commit is contained in:
Parthiv Patel
2023-03-16 08:34:33 +00:00
parent ab92c945d8
commit d956ab4a70
13 changed files with 201 additions and 32 deletions

View File

@@ -410,18 +410,23 @@ class AccountReconcileModel(models.Model):
:return: A list of dictionary to be passed to the account.bank.statement.line's 'reconcile' method.
'''
self.ensure_one()
journal = st_line.journal_id
company_currency = journal.company_id.currency_id
foreign_currency = st_line.foreign_currency_id or journal.currency_id or company_currency
liquidity_lines, suspense_lines, other_lines = st_line._seek_for_lines()
if st_line.to_check:
st_line_residual = -liquidity_lines.balance
st_line_residual_currency = st_line._prepare_move_line_default_vals()[1]['amount_currency']
elif suspense_lines.account_id.reconcile:
st_line_residual = sum(suspense_lines.mapped('amount_residual'))
st_line_residual_currency = sum(suspense_lines.mapped('amount_residual_currency'))
else:
st_line_residual = sum(suspense_lines.mapped('balance'))
st_line_residual_currency = sum(suspense_lines.mapped('amount_currency'))
partner = partner or st_line.partner_id
has_full_write_off= any(rec_mod_line.amount == 100.0 for rec_mod_line in self.line_ids)
has_full_write_off = any(rec_mod_line.amount == 100.0 for rec_mod_line in self.line_ids)
lines_vals_list = []
amls = self.env['account.move.line'].browse(aml_ids)
@@ -433,18 +438,30 @@ class AccountReconcileModel(models.Model):
if aml.balance * st_line_residual > 0:
# Meaning they have the same signs, so they can't be reconciled together
assigned_balance = -aml.amount_residual
assigned_amount_currency = -aml.amount_residual_currency
elif has_full_write_off:
assigned_balance = -aml.amount_residual
st_line_residual -= min(-aml.amount_residual, st_line_residual, key=abs)
assigned_amount_currency = -aml.amount_residual_currency
st_line_residual -= min(assigned_balance, st_line_residual, key=abs)
st_line_residual_currency -= min(assigned_amount_currency, st_line_residual_currency, key=abs)
else:
assigned_balance = min(-aml.amount_residual, st_line_residual, key=abs)
assigned_amount_currency = min(-aml.amount_residual_currency, st_line_residual_currency, key=abs)
st_line_residual -= assigned_balance
st_line_residual_currency -= assigned_amount_currency
lines_vals_list.append({
'id': aml.id,
'balance': assigned_balance,
'currency_id': st_line.move_id.company_id.currency_id.id,
})
if aml.currency_id == foreign_currency:
lines_vals_list.append({
'id': aml.id,
'balance': assigned_amount_currency,
'currency_id': foreign_currency.id,
})
else:
lines_vals_list.append({
'id': aml.id,
'balance': assigned_balance,
'currency_id': company_currency.id,
})
write_off_amount = max(aml_total_residual, -st_line_residual_before, key=abs) + st_line_residual_before + st_line_residual

View File

@@ -1161,3 +1161,67 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
[(6, 0, tax.refund_repartition_line_ids[1].tag_ids.ids)],
'The tags of the second repartition line are not inverted'
)
def test_matching_multi_currency_auto_validate(self):
currency = self.setup_multi_currency_data(
default_values={
'name': 'Black Chocolate Coin',
'symbol': '🍫',
'currency_unit_label': 'Black Choco',
'currency_subunit_label': 'Black Cacao Powder',
},
rate2016=11.2674,
rate2017=11.2674,
)['currency']
bank_journal = self.company_data['default_journal_bank'].copy()
bank_journal.currency_id = currency
invoice_line = self._create_invoice_line(100.0, self.partner_1, 'out_invoice', currency=currency)
statement = self.env['account.bank.statement'].create({
'name': 'test_matching_multi_currency_auto_validate',
'journal_id': bank_journal.id,
'line_ids': [
(0, 0, {
'journal_id': bank_journal.id,
'partner_id': self.partner_1.id,
'date': invoice_line.date,
'payment_ref': invoice_line.move_name,
'amount': invoice_line.amount_currency,
}),
],
})
statement_line = statement.line_ids
statement.button_post()
matching_rule = self.env['account.reconcile.model'].create({
'name': 'test_matching_multi_currency_auto_validate',
'rule_type': 'invoice_matching',
'match_partner': True,
'match_partner_ids': [(6, 0, self.partner_1.ids)],
'match_total_amount': True,
'match_total_amount_param': 100.0,
'match_same_currency': True,
'past_months_limit': False,
'auto_reconcile': True,
})
self._check_statement_matching(
matching_rule,
{
statement_line.id: {
'aml_ids': invoice_line.ids,
'model': matching_rule,
'status': 'reconciled',
'partner': self.partner_1,
},
},
statements=statement,
)
self.assertRecordValues(statement_line.line_ids, [
# pylint: disable=bad-whitespace
{'amount_currency': 100.0, 'debit': 8.88, 'credit': 0.0},
{'amount_currency': -100.0, 'debit': 0.0, 'credit': 8.88},
])

View File

@@ -333,7 +333,8 @@ class PosSession(models.Model):
# Users without any accounting rights won't be able to create the journal entry. If this
# case, switch to sudo for creation and posting.
try:
self.with_company(self.company_id)._create_account_move()
with self.env.cr.savepoint():
self.with_company(self.company_id)._create_account_move()
except AccessError as e:
if sudo:
self.sudo().with_company(self.company_id)._create_account_move()

View File

@@ -11,8 +11,8 @@ from collections import defaultdict
class StockPicking(models.Model):
_inherit='stock.picking'
pos_session_id = fields.Many2one('pos.session')
pos_order_id = fields.Many2one('pos.order')
pos_session_id = fields.Many2one('pos.session', index=True)
pos_order_id = fields.Many2one('pos.order', index=True)
def _prepare_picking_vals(self, partner, picking_type, location_id, location_dest_id):
return {

View File

@@ -113,12 +113,3 @@ class MercuryTransaction(models.Model):
response = self._do_request('pos_mercury.mercury_return', data)
return response
# One time (the ones we use) Vantiv tokens are required to be
# deleted after 6 months
@api.autovacuum
def _gc_old_tokens(self):
expired_creation_date = (date.today() - timedelta(days=6 * 30)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
for order in self.env['pos.order'].search([('create_date', '<', expired_creation_date)]):
order.ref_no = ""
order.record_no = ""

View File

@@ -169,8 +169,8 @@
<field name="inherit_id" ref="product.product_search_form_view"/>
<field name="arch" type="xml">
<field name="pricelist_id" position="before">
<field name="location_id" options="{'no_create': True}" context="{'location': self}"/>
<field name="warehouse_id" context="{'warehouse': self}"/>
<field name="location_id" options="{'no_create': True}" context="{'location': self}" filter_domain="[]"/>
<field name="warehouse_id" context="{'warehouse': self}" filter_domain="[]"/>
</field>
</field>
</record>

View File

@@ -223,16 +223,16 @@ class ProductProduct(models.Model):
svl_vals_list = []
company_id = self.env.company
price_unit_prec = self.env['decimal.precision'].precision_get('Product Price')
rounded_new_price = float_round(new_price, precision_digits=price_unit_prec)
for product in self:
if product.cost_method not in ('standard', 'average'):
continue
quantity_svl = product.sudo().quantity_svl
if float_compare(quantity_svl, 0.0, precision_rounding=product.uom_id.rounding) <= 0:
continue
digits = self.env['decimal.precision'].precision_get('Product Price')
rounded_new_price = float_round(new_price, precision_digits=digits)
diff = rounded_new_price - product.standard_price
value = company_id.currency_id.round(quantity_svl * diff)
value_svl = product.sudo().value_svl
value = company_id.currency_id.round((rounded_new_price * quantity_svl) - value_svl)
if company_id.currency_id.is_zero(value):
continue

View File

@@ -3864,3 +3864,16 @@ class TestStockValuation(SavepointCase):
self.assertEqual(self.product1.quantity_svl, 12)
move.quantity_done = 2
self.assertEqual(self.product1.quantity_svl, 24)
def test_average_manual_price_change(self):
"""
When doing a Manual Price Change, an SVL is created to update the value_svl.
This test check that the value of this SVL is correct and does result in new_std_price * quantity.
To do so, we create 2 In moves, which result in a standard price rounded at $5.29, the non-rounded value ≃ 5.2857.
Then we update the standard price to $7
"""
self.product1.categ_id.property_cost_method = 'average'
self._make_in_move(self.product1, 5, unit_cost=5)
self._make_in_move(self.product1, 2, unit_cost=6)
self.product1.write({'standard_price': 7})
self.assertEqual(self.product1.value_svl, 49)

View File

@@ -53,19 +53,21 @@ 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
value can be set according to `self.product_id.landed_cost_ok`."""
if self.product_id:
accounts = self.product_id.product_tmpl_id._get_product_accounts()
aml_account = accounts['expense']
if self.product_type != 'service':
self.account_id = accounts['expense']
self.is_landed_costs_line = False
elif self.is_landed_costs_line and self.move_id.company_id.anglo_saxon_accounting:
self.account_id = accounts['stock_input']
else:
self.account_id = accounts['expense']
elif self.is_landed_costs_line:
aml_account = self._get_landed_costs_account(accounts)
self.account_id = aml_account
@api.onchange('product_id')
def _onchange_is_landed_costs_line_product(self):
@@ -73,3 +75,9 @@ class AccountMoveLine(models.Model):
self.is_landed_costs_line = True
else:
self.is_landed_costs_line = False
def create(self, vals_list):
lines = super().create(vals_list)
for line in lines.filtered('is_landed_costs_line'):
line.account_id = line._get_landed_costs_account(line.product_id.product_tmpl_id._get_product_accounts())
return lines

View File

@@ -1058,6 +1058,12 @@ class WebsiteSale(http.Controller):
"""
if sale_order_id is None:
order = request.website.sale_get_order()
if not order and 'sale_last_order_id' in request.session:
# Retrieve the last known order from the session if the session key `sale_order_id`
# was prematurely cleared. This is done to prevent the user from updating their cart
# after payment in case they don't return from payment through this route.
last_order_id = request.session['sale_last_order_id']
order = request.env['sale.order'].sudo().browse(last_order_id).exists()
else:
order = request.env['sale.order'].sudo().browse(sale_order_id)
assert order.id == request.session.get('sale_last_order_id')

View File

@@ -258,6 +258,11 @@ class Website(models.Model):
# Test validity of the sale_order_id
sale_order = self.env['sale.order'].with_company(request.website.company_id.id).sudo().browse(sale_order_id).exists() if sale_order_id else None
# Ignore the current order if a payment has been initiated. We don't want to retrieve the
# cart and allow the user to update it when the payment is about to confirm it.
if sale_order and sale_order.get_portal_last_transaction().state in ('pending', 'authorized', 'done'):
sale_order = None
# Do not reload the cart of this user last visit if the Fiscal Position has changed.
if check_fpos and sale_order:
fpos_id = (

View File

@@ -5,6 +5,7 @@ from . import common
from . import test_customize
from . import test_sale_process
from . import test_sitemap
from . import test_website_sale_cart_payment
from . import test_website_sale_cart_recovery
from . import test_website_sale_cart
from . import test_website_sale_mail

View File

@@ -0,0 +1,63 @@
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from unittest.mock import patch
from flectra.addons.payment.tests.common import PaymentAcquirerCommon
from flectra.addons.website.tools import MockRequest
from flectra.tests.common import tagged
@tagged('post_install', '-at_install')
class WebsiteSaleCartPayment(PaymentAcquirerCommon):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
cls.website = cls.env['website'].get_current_website()
with MockRequest(cls.env, website=cls.website):
cls.order = cls.website.sale_get_order(force_create=True) # Create the cart to retrieve
cls.acquirer = cls.env['payment.acquirer'].create({
'name': "Dummy Acquirer",
'provider': 'manual',
'state': 'test',
'journal_id': cls.company_data['default_journal_bank'].id,
})
cls.tx = cls.env['payment.transaction'].create({
'amount': 1111.11,
'currency_id': cls.currency_euro.id,
'acquirer_id': cls.acquirer.id,
'reference': "Test Transaction",
'partner_id': cls.buyer.id,
})
cls.order.write({'transaction_ids': [(6, 0, [cls.tx.id])]})
def test_unpaid_orders_can_be_retrieved(self):
""" Test that fetching sales orders linked to a payment transaction in the states 'draft',
'cancel', or 'error' returns the orders. """
for unpaid_order_tx_state in ('draft', 'cancel', 'error'):
self.tx.state = unpaid_order_tx_state
with MockRequest(self.env, website=self.website, sale_order_id=self.order.id):
self.assertEqual(
self.website.sale_get_order(),
self.order,
msg=f"The transaction state '{unpaid_order_tx_state}' should not prevent "
f"retrieving the linked order.",
)
def test_paid_orders_cannot_be_retrieved(self):
""" Test that fetching sales orders linked to a payment transaction in the states 'pending',
'authorized', or 'done' returns an empty recordset to prevent updating the paid orders. """
with patch(
'flectra.addons.payment.models.payment_acquirer.PaymentAcquirer._get_feature_support',
return_value={'authorize': ['manual'], 'tokenize': [], 'fees': []},
):
for paid_order_tx_state in ('pending', 'authorized', 'done'):
self.tx.state = paid_order_tx_state
with MockRequest(self.env, website=self.website, sale_order_id=self.order.id):
self.assertFalse(
self.website.sale_get_order(),
msg=f"The transaction state '{paid_order_tx_state}' should prevent retrieving "
f"the linked order.",
)