[MERGE] forward port branch 10.0 up to c25b68f324
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from datetime import timedelta, datetime
|
||||
import calendar
|
||||
import time
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import fields, models, api, _
|
||||
from odoo.exceptions import ValidationError, UserError
|
||||
from datetime import timedelta
|
||||
from odoo.tools.misc import DEFAULT_SERVER_DATE_FORMAT
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
@@ -45,6 +50,61 @@ If you have any queries regarding your account, Please contact us.
|
||||
Thank you in advance for your cooperation.
|
||||
Best Regards,''')
|
||||
|
||||
@api.multi
|
||||
def _check_lock_dates(self, vals):
|
||||
'''Check the lock dates for the current companies. This can't be done in a api.constrains because we need
|
||||
to perform some comparison between new/old values. This method forces the lock dates to be irreversible.
|
||||
|
||||
* You cannot define stricter conditions on advisors than on users. Then, the lock date on advisor must be set
|
||||
after the lock date for users.
|
||||
* You cannot lock a period that is not finished yet. Then, the lock date for advisors must be set after the
|
||||
last day of the previous month.
|
||||
* The new lock date for advisors must be set after the previous lock date.
|
||||
|
||||
:param vals: The values passed to the write method.
|
||||
'''
|
||||
period_lock_date = vals.get('period_lock_date') and\
|
||||
time.strptime(vals['period_lock_date'], DEFAULT_SERVER_DATE_FORMAT)
|
||||
fiscalyear_lock_date = vals.get('fiscalyear_lock_date') and\
|
||||
time.strptime(vals['fiscalyear_lock_date'], DEFAULT_SERVER_DATE_FORMAT)
|
||||
|
||||
previous_month = datetime.strptime(fields.Date.today(), DEFAULT_SERVER_DATE_FORMAT) + relativedelta(months=-1)
|
||||
days_previous_month = calendar.monthrange(previous_month.year, previous_month.month)
|
||||
previous_month = previous_month.replace(day=days_previous_month[1]).timetuple()
|
||||
for company in self:
|
||||
old_fiscalyear_lock_date = company.fiscalyear_lock_date and\
|
||||
time.strptime(company.fiscalyear_lock_date, DEFAULT_SERVER_DATE_FORMAT)
|
||||
|
||||
# The user attempts to remove the lock date for advisors
|
||||
if old_fiscalyear_lock_date and not fiscalyear_lock_date and 'fiscalyear_lock_date' in vals:
|
||||
raise ValidationError(_('The lock date for advisors is irreversible and can\'t be removed.'))
|
||||
|
||||
# The user attempts to set a lock date for advisors prior to the previous one
|
||||
if old_fiscalyear_lock_date and fiscalyear_lock_date and fiscalyear_lock_date < old_fiscalyear_lock_date:
|
||||
raise ValidationError(_('The new lock date for advisors must be set after the previous lock date.'))
|
||||
|
||||
# In case of no new fiscal year in vals, fallback to the oldest
|
||||
if not fiscalyear_lock_date:
|
||||
if old_fiscalyear_lock_date:
|
||||
fiscalyear_lock_date = old_fiscalyear_lock_date
|
||||
else:
|
||||
continue
|
||||
|
||||
# The user attempts to set a lock date for advisors prior to the last day of previous month
|
||||
if fiscalyear_lock_date > previous_month:
|
||||
raise ValidationError(_('You cannot lock a period that is not finished yet. Please make sure that the lock date for advisors is not set after the last day of the previous month.'))
|
||||
|
||||
# In case of no new period lock date in vals, fallback to the one defined in the company
|
||||
if not period_lock_date:
|
||||
if company.period_lock_date:
|
||||
period_lock_date = time.strptime(company.period_lock_date, DEFAULT_SERVER_DATE_FORMAT)
|
||||
else:
|
||||
continue
|
||||
|
||||
# The user attempts to set a lock date for advisors prior to the lock date for users
|
||||
if period_lock_date < fiscalyear_lock_date:
|
||||
raise ValidationError(_('You cannot define stricter conditions on advisors than on users. Please make sure that the lock date on advisor is set before the lock date for users.'))
|
||||
|
||||
@api.multi
|
||||
def compute_fiscalyear_dates(self, date):
|
||||
""" Computes the start and end dates of the fiscalyear where the given 'date' belongs to
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from odoo.addons.account.tests.account_test_classes import AccountingTestCase
|
||||
from odoo.osv.orm import except_orm
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from calendar import monthrange
|
||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
|
||||
class TestPeriodState(AccountingTestCase):
|
||||
@@ -11,14 +13,16 @@ class TestPeriodState(AccountingTestCase):
|
||||
def setUp(self):
|
||||
super(TestPeriodState, self).setUp()
|
||||
self.user_id = self.env.user
|
||||
self.day_before_yesterday = datetime.now() - timedelta(2)
|
||||
self.yesterday = datetime.now() - timedelta(1)
|
||||
self.yesterday_str = self.yesterday.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
|
||||
last_day_month = datetime.now() - relativedelta(months=1)
|
||||
last_day_month = last_day_month.replace(day=monthrange(last_day_month.year, last_day_month.month)[1])
|
||||
self.last_day_month_str = last_day_month.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
|
||||
#make sure there is no unposted entry
|
||||
draft_entries = self.env['account.move'].search([('date', '<=', self.yesterday_str), ('state', '=', 'draft')])
|
||||
draft_entries = self.env['account.move'].search([('date', '<=', self.last_day_month_str), ('state', '=', 'draft')])
|
||||
if draft_entries:
|
||||
draft_entries.post()
|
||||
self.user_id.company_id.write({'fiscalyear_lock_date': self.yesterday_str})
|
||||
self.user_id.company_id.fiscalyear_lock_date = self.last_day_month_str
|
||||
self.sale_journal_id = self.env['account.journal'].search([('type', '=', 'sale')])[0]
|
||||
self.account_id = self.env['account.account'].search([('internal_type', '=', 'receivable')])[0]
|
||||
|
||||
@@ -27,7 +31,7 @@ class TestPeriodState(AccountingTestCase):
|
||||
move = self.env['account.move'].create({
|
||||
'name': '/',
|
||||
'journal_id': self.sale_journal_id.id,
|
||||
'date': self.day_before_yesterday.strftime(DEFAULT_SERVER_DATE_FORMAT),
|
||||
'date': self.last_day_month_str,
|
||||
'line_ids': [(0, 0, {
|
||||
'name': 'foo',
|
||||
'debit': 10,
|
||||
|
||||
@@ -39,6 +39,12 @@ class TestReconciliation(AccountingTestCase):
|
||||
self.diff_income_account = self.env['res.users'].browse(self.env.uid).company_id.income_currency_exchange_account_id
|
||||
self.diff_expense_account = self.env['res.users'].browse(self.env.uid).company_id.expense_currency_exchange_account_id
|
||||
|
||||
self.inbound_payment_method = self.env['account.payment.method'].create({
|
||||
'name': 'inbound',
|
||||
'code': 'IN',
|
||||
'payment_type': 'inbound',
|
||||
})
|
||||
|
||||
def create_invoice(self, type='out_invoice', invoice_amount=50, currency_id=None):
|
||||
#we create an invoice in given currency
|
||||
invoice = self.account_invoice_model.create({'partner_id': self.partner_agrolait_id,
|
||||
@@ -704,3 +710,46 @@ class TestReconciliation(AccountingTestCase):
|
||||
# Unreconcile invoice and check residual
|
||||
credit_aml.with_context(invoice_id=inv.id).remove_move_reconcile()
|
||||
self.assertAlmostEquals(inv.residual, 111)
|
||||
|
||||
def test_revert_payment_and_reconcile(self):
|
||||
payment = self.env['account.payment'].create({
|
||||
'payment_method_id': self.inbound_payment_method.id,
|
||||
'payment_type': 'inbound',
|
||||
'partner_type': 'customer',
|
||||
'partner_id': self.partner_agrolait_id,
|
||||
'journal_id': self.bank_journal_usd.id,
|
||||
'payment_date': '2018-06-04',
|
||||
'amount': 666,
|
||||
})
|
||||
payment.post()
|
||||
|
||||
self.assertEqual(len(payment.move_line_ids), 2)
|
||||
|
||||
bank_line = payment.move_line_ids.filtered(lambda l: l.account_id.id == self.bank_journal_usd.default_debit_account_id.id)
|
||||
customer_line = payment.move_line_ids - bank_line
|
||||
|
||||
self.assertEqual(len(bank_line), 1)
|
||||
self.assertEqual(len(customer_line), 1)
|
||||
self.assertNotEqual(bank_line.id, customer_line.id)
|
||||
|
||||
self.assertEqual(bank_line.move_id.id, customer_line.move_id.id)
|
||||
move = bank_line.move_id
|
||||
|
||||
# Reversing the payment's move
|
||||
reversed_move_list = move.reverse_moves('2018-06-04')
|
||||
self.assertEqual(len(reversed_move_list), 1)
|
||||
reversed_move = self.env['account.move'].browse(reversed_move_list[0])
|
||||
|
||||
self.assertEqual(len(reversed_move.line_ids), 2)
|
||||
|
||||
# Testing the reconciliation matching between the move lines and their reversed counterparts
|
||||
reversed_bank_line = reversed_move.line_ids.filtered(lambda l: l.account_id.id == self.bank_journal_usd.default_debit_account_id.id)
|
||||
reversed_customer_line = reversed_move.line_ids - reversed_bank_line
|
||||
|
||||
self.assertEqual(len(reversed_bank_line), 1)
|
||||
self.assertEqual(len(reversed_customer_line), 1)
|
||||
self.assertNotEqual(reversed_bank_line.id, reversed_customer_line.id)
|
||||
self.assertEqual(reversed_bank_line.move_id.id, reversed_customer_line.move_id.id)
|
||||
|
||||
self.assertEqual(reversed_bank_line.full_reconcile_id.id, bank_line.full_reconcile_id.id)
|
||||
self.assertEqual(reversed_customer_line.full_reconcile_id.id, customer_line.full_reconcile_id.id)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
@@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
{
|
||||
'name' : 'Irreversible Lock Date',
|
||||
'version' : '1.0',
|
||||
'category': 'Accounting',
|
||||
'description': """
|
||||
Make the lock date irreversible:
|
||||
|
||||
* You cannot define stricter conditions on advisors than on users. Then, the lock date on advisor must be set before the lock date for users.
|
||||
* You cannot lock a period that is not finished yet. Then, the lock date for advisors must be set before the last day of the previous month.
|
||||
* The new lock date for advisors must be set after the previous lock date.
|
||||
""",
|
||||
'depends' : ['account'],
|
||||
'data': [],
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import res_company
|
||||
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, api
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = 'res.company'
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
# fiscalyear_lock_date can't be set to a prior date
|
||||
if 'fiscalyear_lock_date' in vals or 'period_lock_date' in vals:
|
||||
self._check_lock_dates(vals)
|
||||
return super(ResCompany, self).write(vals)
|
||||
@@ -520,6 +520,10 @@ class Escpos:
|
||||
# Print Code
|
||||
if code:
|
||||
self._raw(code)
|
||||
# We are using type A commands
|
||||
# So we need to add the 'NULL' character
|
||||
# https://github.com/python-escpos/python-escpos/pull/98/files#diff-a0b1df12c7c67e38915adbe469051e2dR444
|
||||
self._raw('\x00')
|
||||
else:
|
||||
raise exception.BarcodeCodeError()
|
||||
|
||||
|
||||
@@ -30,6 +30,9 @@ class ResCompany(models.Model):
|
||||
if company._is_accounting_unalterable():
|
||||
sequence_fields = ['l10n_fr_secure_sequence_id']
|
||||
company._create_secure_sequence(sequence_fields)
|
||||
# fiscalyear_lock_date can't be set to a prior date
|
||||
if 'fiscalyear_lock_date' in vals or 'period_lock_date' in vals:
|
||||
self._check_lock_dates(vals)
|
||||
return res
|
||||
|
||||
def _create_secure_sequence(self, sequence_fields):
|
||||
|
||||
@@ -93,7 +93,7 @@ class MailMail(models.Model):
|
||||
def send_get_email_dict(self, partner=None):
|
||||
# TDE: temporary addition (mail was parameter) due to semi-new-API
|
||||
res = super(MailMail, self).send_get_email_dict(partner)
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url').rstrip('/')
|
||||
if self.mailing_id and res.get('body') and res.get('email_to'):
|
||||
emails = tools.email_split(res.get('email_to')[0])
|
||||
email_to = emails and emails[0] or False
|
||||
|
||||
@@ -19,7 +19,7 @@ class MrpProduction(models.Model):
|
||||
@api.multi
|
||||
def _compute_sale_name_sale_ref(self):
|
||||
for production in self:
|
||||
move = production._get_parent_move(production.move_finished_ids[0])
|
||||
move = production._get_parent_move(production.move_finished_ids[:1])
|
||||
production.sale_name = move.procurement_id and move.procurement_id.sale_line_id and move.procurement_id.sale_line_id.order_id.name or False
|
||||
production.sale_ref = move.procurement_id and move.procurement_id.sale_line_id and move.procurement_id.sale_line_id.order_id.client_order_ref or False
|
||||
|
||||
|
||||
@@ -23,16 +23,23 @@ var EventRegistrationForm = Widget.extend({
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
var $form = $(ev.currentTarget).closest('form');
|
||||
var $button = $(ev.currentTarget).closest('[type="submit"]');
|
||||
var post = {};
|
||||
$("#registration_form select").each(function() {
|
||||
post[$(this).attr('name')] = $(this).val();
|
||||
});
|
||||
return ajax.jsonRpc($form.attr('action'), 'call', post).then(function (modal) {
|
||||
// Only needed for 9.0 up to saas-14
|
||||
if (modal === false) {
|
||||
$button.prop('disabled', false);
|
||||
return;
|
||||
}
|
||||
var $modal = $(modal);
|
||||
$modal.find('.modal-body > div').removeClass('container'); // retrocompatibility - REMOVE ME in master / saas-19
|
||||
$modal.after($form).modal();
|
||||
$modal.insertAfter($form).modal();
|
||||
$modal.on('click', '.js_goto_event', function () {
|
||||
$modal.modal('hide');
|
||||
$button.prop('disabled', false);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
+46
-7
@@ -26,7 +26,8 @@ _logger = logging.getLogger(__name__)
|
||||
_test_logger = logging.getLogger('odoo.tests')
|
||||
|
||||
|
||||
def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None):
|
||||
def load_module_graph(cr, graph, status=None, perform_checks=True,
|
||||
skip_modules=None, report=None, models_to_check=None):
|
||||
"""Migrates+Updates or Installs all module nodes from ``graph``
|
||||
:param graph: graph of module nodes to load
|
||||
:param status: deprecated parameter, unused, left to avoid changing signature in 8.0
|
||||
@@ -97,6 +98,9 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
|
||||
if kind in ('demo', 'test'):
|
||||
threading.currentThread().testing = False
|
||||
|
||||
if models_to_check is None:
|
||||
models_to_check = set()
|
||||
|
||||
processed_modules = []
|
||||
loaded_modules = []
|
||||
registry = odoo.registry(cr.dbname)
|
||||
@@ -110,6 +114,8 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
|
||||
t0 = time.time()
|
||||
t0_sql = odoo.sql_db.sql_counter
|
||||
|
||||
models_updated = set()
|
||||
|
||||
for index, package in enumerate(graph, 1):
|
||||
module_name = package.name
|
||||
module_id = package.id
|
||||
@@ -131,9 +137,20 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
|
||||
model_names = registry.load(cr, package)
|
||||
|
||||
loaded_modules.append(package.name)
|
||||
if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
|
||||
if (hasattr(package, 'init') or hasattr(package, 'update')
|
||||
or package.state in ('to install', 'to upgrade')):
|
||||
models_updated |= set(model_names)
|
||||
models_to_check -= set(model_names)
|
||||
registry.setup_models(cr, partial=True)
|
||||
registry.init_models(cr, model_names, {'module': package.name})
|
||||
cr.commit()
|
||||
elif package.state != 'to remove':
|
||||
# The current module has simply been loaded. The models extended by this module
|
||||
# and for which we updated the schema, must have their schema checked again.
|
||||
# This is because the extension may have changed the model,
|
||||
# e.g. adding required=True to an existing field, but the schema has not been
|
||||
# updated by this module because it's not marked as 'to upgrade/to install'.
|
||||
models_to_check |= set(model_names) & models_updated
|
||||
|
||||
idref = {}
|
||||
|
||||
@@ -223,9 +240,14 @@ def _check_module_names(cr, module_names):
|
||||
incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
|
||||
_logger.warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
|
||||
|
||||
def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_modules, perform_checks):
|
||||
def load_marked_modules(cr, graph, states, force, progressdict, report,
|
||||
loaded_modules, perform_checks, models_to_check=None):
|
||||
"""Loads modules marked with ``states``, adding them to ``graph`` and
|
||||
``loaded_modules`` and returns a list of installed/upgraded modules."""
|
||||
|
||||
if models_to_check is None:
|
||||
models_to_check = set()
|
||||
|
||||
processed_modules = []
|
||||
while True:
|
||||
cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),))
|
||||
@@ -234,7 +256,10 @@ def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_m
|
||||
break
|
||||
graph.add_modules(cr, module_list, force)
|
||||
_logger.debug('Updating graph with %d more modules', len(module_list))
|
||||
loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules, perform_checks=perform_checks)
|
||||
loaded, processed = load_module_graph(
|
||||
cr, graph, progressdict, report=report, skip_modules=loaded_modules,
|
||||
perform_checks=perform_checks, models_to_check=models_to_check
|
||||
)
|
||||
processed_modules.extend(processed)
|
||||
loaded_modules.extend(loaded)
|
||||
if not processed:
|
||||
@@ -248,6 +273,8 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
|
||||
if force_demo:
|
||||
force.append('demo')
|
||||
|
||||
models_to_check = set()
|
||||
|
||||
cr = db.cursor()
|
||||
try:
|
||||
if not odoo.modules.db.is_initialized(cr):
|
||||
@@ -277,7 +304,9 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
|
||||
# processed_modules: for cleanup step after install
|
||||
# loaded_modules: to avoid double loading
|
||||
report = registry._assertion_report
|
||||
loaded_modules, processed_modules = load_module_graph(cr, graph, status, perform_checks=update_module, report=report)
|
||||
loaded_modules, processed_modules = load_module_graph(
|
||||
cr, graph, status, perform_checks=update_module,
|
||||
report=report, models_to_check=models_to_check)
|
||||
|
||||
load_lang = tools.config.pop('load_language')
|
||||
if load_lang or update_module:
|
||||
@@ -332,11 +361,11 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
|
||||
previously_processed = len(processed_modules)
|
||||
processed_modules += load_marked_modules(cr, graph,
|
||||
['installed', 'to upgrade', 'to remove'],
|
||||
force, status, report, loaded_modules, update_module)
|
||||
force, status, report, loaded_modules, update_module, models_to_check)
|
||||
if update_module:
|
||||
processed_modules += load_marked_modules(cr, graph,
|
||||
['to install'], force, status, report,
|
||||
loaded_modules, update_module)
|
||||
loaded_modules, update_module, models_to_check)
|
||||
|
||||
registry.setup_models(cr)
|
||||
|
||||
@@ -398,6 +427,16 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
|
||||
api.Environment.reset()
|
||||
return odoo.modules.registry.Registry.new(cr.dbname, force_demo, status, update_module)
|
||||
|
||||
# STEP 5.5: Verify extended fields on every model
|
||||
# This will fix the schema of all models in a situation such as:
|
||||
# - module A is loaded and defines model M;
|
||||
# - module B is installed/upgraded and extends model M;
|
||||
# - module C is loaded and extends model M;
|
||||
# - module B and C depend on A but not on each other;
|
||||
# The changes introduced by module C are not taken into account by the upgrade of B.
|
||||
if models_to_check:
|
||||
registry.init_models(cr, list(models_to_check), {'models_to_check': True})
|
||||
|
||||
# STEP 6: verify custom views on every model
|
||||
if update_module:
|
||||
View = env['ir.ui.view']
|
||||
|
||||
@@ -308,6 +308,8 @@ class Registry(Mapping):
|
||||
"""
|
||||
if 'module' in context:
|
||||
_logger.info('module %s: creating or updating database tables', context['module'])
|
||||
elif context.get('models_to_check', False):
|
||||
_logger.info("verifying fields for every extended model")
|
||||
|
||||
context = dict(context, todo=[])
|
||||
env = odoo.api.Environment(cr, SUPERUSER_ID, context)
|
||||
|
||||
Reference in New Issue
Block a user