mirror of
https://gitlab.com/flectra-hq/flectra.git
synced 2025-02-25 18:55:21 -06:00
[PATCH] Upstream patch - 16032023
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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},
|
||||
])
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 = ""
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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
|
||||
|
||||
63
addons/website_sale/tests/test_website_sale_cart_payment.py
Normal file
63
addons/website_sale/tests/test_website_sale_cart_payment.py
Normal 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.",
|
||||
)
|
||||
Reference in New Issue
Block a user