[REF] *: use native date/datetime for Date/Datetime fields
From this commit onwards, Date fields will return datetime.date objects and Datetime fields will return datetime.datetime objects, this implies a number of things that are clearly explained both in the ORM API for master. This commit also introduces a number of helper functions for dates and datetimes that are exposed in tools.date_utils and fields.Date[time], explained in the documentation as well. Task-ID: 47189
This commit is contained in:
committed by
Adrian Torres
parent
09ba95b2d0
commit
960360afe4
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
from odoo import api, fields, models, _
|
from odoo import api, fields, models, _
|
||||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
|
|
||||||
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -37,8 +36,8 @@ class AccountFiscalYear(models.Model):
|
|||||||
'''
|
'''
|
||||||
for fy in self:
|
for fy in self:
|
||||||
# Starting date must be prior to the ending date
|
# Starting date must be prior to the ending date
|
||||||
date_from = datetime.strptime(fy.date_from, DEFAULT_SERVER_DATE_FORMAT)
|
date_from = fy.date_from
|
||||||
date_to = datetime.strptime(fy.date_to, DEFAULT_SERVER_DATE_FORMAT)
|
date_to = fy.date_to
|
||||||
if date_to < date_from:
|
if date_to < date_from:
|
||||||
raise ValidationError(_('The ending date must not be prior to the starting date.'))
|
raise ValidationError(_('The ending date must not be prior to the starting date.'))
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ from dateutil.relativedelta import relativedelta
|
|||||||
from werkzeug.urls import url_encode
|
from werkzeug.urls import url_encode
|
||||||
|
|
||||||
from odoo import api, exceptions, fields, models, _
|
from odoo import api, exceptions, fields, models, _
|
||||||
from odoo.tools import email_re, email_split, email_escape_char, float_is_zero, float_compare, pycompat
|
from odoo.tools import email_re, email_split, email_escape_char, float_is_zero, float_compare, \
|
||||||
|
pycompat, date_utils
|
||||||
from odoo.tools.misc import formatLang
|
from odoo.tools.misc import formatLang
|
||||||
|
|
||||||
from odoo.exceptions import AccessError, UserError, RedirectWarning, ValidationError, Warning
|
from odoo.exceptions import AccessError, UserError, RedirectWarning, ValidationError, Warning
|
||||||
@@ -216,7 +217,7 @@ class AccountInvoice(models.Model):
|
|||||||
self.payments_widget = json.dumps(False)
|
self.payments_widget = json.dumps(False)
|
||||||
if self.payment_move_line_ids:
|
if self.payment_move_line_ids:
|
||||||
info = {'title': _('Less Payment'), 'outstanding': False, 'content': self._get_payments_vals()}
|
info = {'title': _('Less Payment'), 'outstanding': False, 'content': self._get_payments_vals()}
|
||||||
self.payments_widget = json.dumps(info)
|
self.payments_widget = json.dumps(info, default=date_utils.json_default)
|
||||||
|
|
||||||
@api.one
|
@api.one
|
||||||
@api.depends('move_id.line_ids.amount_residual')
|
@api.depends('move_id.line_ids.amount_residual')
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class account_journal(models.Model):
|
|||||||
"""
|
"""
|
||||||
self.env.cr.execute(query, (self.id, last_month, today))
|
self.env.cr.execute(query, (self.id, last_month, today))
|
||||||
for val in self.env.cr.dictfetchall():
|
for val in self.env.cr.dictfetchall():
|
||||||
date = datetime.strptime(val['date'], DF)
|
date = val['date']
|
||||||
if val['date'] != today.strftime(DF): # make sure the last point in the graph is today
|
if val['date'] != today.strftime(DF): # make sure the last point in the graph is today
|
||||||
data[:0] = [build_graph_data(date, amount)]
|
data[:0] = [build_graph_data(date, amount)]
|
||||||
amount -= val['amount']
|
amount -= val['amount']
|
||||||
@@ -92,7 +92,7 @@ class account_journal(models.Model):
|
|||||||
@api.multi
|
@api.multi
|
||||||
def get_bar_graph_datas(self):
|
def get_bar_graph_datas(self):
|
||||||
data = []
|
data = []
|
||||||
today = datetime.strptime(fields.Date.context_today(self), DF)
|
today = fields.Date.context_today(self)
|
||||||
data.append({'label': _('Past'), 'value':0.0, 'type': 'past'})
|
data.append({'label': _('Past'), 'value':0.0, 'type': 'past'})
|
||||||
day_of_week = int(format_datetime(today, 'e', locale=self._context.get('lang') or 'en_US'))
|
day_of_week = int(format_datetime(today, 'e', locale=self._context.get('lang') or 'en_US'))
|
||||||
first_day_of_week = today + timedelta(days=-day_of_week+1)
|
first_day_of_week = today + timedelta(days=-day_of_week+1)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
from datetime import date
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from odoo import api, fields, models, _
|
from odoo import api, fields, models, _
|
||||||
from odoo.osv import expression
|
from odoo.osv import expression
|
||||||
@@ -345,10 +346,10 @@ class AccountMove(models.Model):
|
|||||||
@api.multi
|
@api.multi
|
||||||
def _check_lock_date(self):
|
def _check_lock_date(self):
|
||||||
for move in self:
|
for move in self:
|
||||||
lock_date = max(move.company_id.period_lock_date or '0000-00-00', move.company_id.fiscalyear_lock_date or '0000-00-00')
|
lock_date = max(move.company_id.period_lock_date or date.min, move.company_id.fiscalyear_lock_date or date.min)
|
||||||
if self.user_has_groups('account.group_account_manager'):
|
if self.user_has_groups('account.group_account_manager'):
|
||||||
lock_date = move.company_id.fiscalyear_lock_date
|
lock_date = move.company_id.fiscalyear_lock_date
|
||||||
if move.date <= (lock_date or '0000-00-00'):
|
if move.date <= (lock_date or date.min):
|
||||||
if self.user_has_groups('account.group_account_manager'):
|
if self.user_has_groups('account.group_account_manager'):
|
||||||
message = _("You cannot add/modify entries prior to and inclusive of the lock date %s") % (lock_date)
|
message = _("You cannot add/modify entries prior to and inclusive of the lock date %s") % (lock_date)
|
||||||
else:
|
else:
|
||||||
@@ -738,7 +739,7 @@ class AccountMoveLine(models.Model):
|
|||||||
total_debit = 0
|
total_debit = 0
|
||||||
total_credit = 0
|
total_credit = 0
|
||||||
total_amount_currency = 0
|
total_amount_currency = 0
|
||||||
maxdate = '0000-00-00'
|
maxdate = date.min
|
||||||
to_balance = {}
|
to_balance = {}
|
||||||
for aml in amls:
|
for aml in amls:
|
||||||
total_debit += aml.debit
|
total_debit += aml.debit
|
||||||
@@ -947,7 +948,7 @@ class AccountMoveLine(models.Model):
|
|||||||
total = 0
|
total = 0
|
||||||
total_currency = 0
|
total_currency = 0
|
||||||
writeoff_lines = []
|
writeoff_lines = []
|
||||||
date = time.strftime('%Y-%m-%d')
|
date = fields.Date.today()
|
||||||
for vals in lines:
|
for vals in lines:
|
||||||
# Check and complete vals
|
# Check and complete vals
|
||||||
if 'account_id' not in vals or 'journal_id' not in vals:
|
if 'account_id' not in vals or 'journal_id' not in vals:
|
||||||
@@ -955,7 +956,7 @@ class AccountMoveLine(models.Model):
|
|||||||
if ('debit' in vals) ^ ('credit' in vals):
|
if ('debit' in vals) ^ ('credit' in vals):
|
||||||
raise UserError(_("Either pass both debit and credit or none."))
|
raise UserError(_("Either pass both debit and credit or none."))
|
||||||
if 'date' not in vals:
|
if 'date' not in vals:
|
||||||
vals['date'] = self._context.get('date_p') or time.strftime('%Y-%m-%d')
|
vals['date'] = self._context.get('date_p') or fields.Date.today()
|
||||||
if vals['date'] < date:
|
if vals['date'] < date:
|
||||||
date = vals['date']
|
date = vals['date']
|
||||||
if 'name' not in vals:
|
if 'name' not in vals:
|
||||||
@@ -1369,8 +1370,8 @@ class AccountPartialReconcile(models.Model):
|
|||||||
def _compute_max_date(self):
|
def _compute_max_date(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
rec.max_date = max(
|
rec.max_date = max(
|
||||||
fields.Datetime.from_string(rec.debit_move_id.date),
|
rec.debit_move_id.date,
|
||||||
fields.Datetime.from_string(rec.credit_move_id.date)
|
rec.credit_move_id.date
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
@@ -1608,6 +1609,6 @@ class AccountFullReconcile(models.Model):
|
|||||||
# The move date should be the maximum date between payment and invoice
|
# The move date should be the maximum date between payment and invoice
|
||||||
# (in case of payment in advance). However, we should make sure the
|
# (in case of payment in advance). However, we should make sure the
|
||||||
# move date is not recorded after the end of year closing.
|
# move date is not recorded after the end of year closing.
|
||||||
if move_date > (company.fiscalyear_lock_date or '0000-00-00'):
|
if move_date > (company.fiscalyear_lock_date or date.min):
|
||||||
res['date'] = move_date
|
res['date'] = move_date
|
||||||
return res
|
return res
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from odoo import fields, models, api, _
|
|||||||
from odoo.exceptions import ValidationError, UserError, RedirectWarning
|
from odoo.exceptions import ValidationError, UserError, RedirectWarning
|
||||||
from odoo.tools.misc import DEFAULT_SERVER_DATE_FORMAT
|
from odoo.tools.misc import DEFAULT_SERVER_DATE_FORMAT
|
||||||
from odoo.tools.float_utils import float_round, float_is_zero
|
from odoo.tools.float_utils import float_round, float_is_zero
|
||||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, date_utils
|
from odoo.tools import date_utils
|
||||||
|
|
||||||
|
|
||||||
class ResCompany(models.Model):
|
class ResCompany(models.Model):
|
||||||
@@ -110,16 +110,15 @@ Best Regards,'''))
|
|||||||
:param vals: The values passed to the write method.
|
:param vals: The values passed to the write method.
|
||||||
'''
|
'''
|
||||||
period_lock_date = vals.get('period_lock_date') and\
|
period_lock_date = vals.get('period_lock_date') and\
|
||||||
time.strptime(vals['period_lock_date'], DEFAULT_SERVER_DATE_FORMAT)
|
fields.Date.from_string(vals['period_lock_date'])
|
||||||
fiscalyear_lock_date = vals.get('fiscalyear_lock_date') and\
|
fiscalyear_lock_date = vals.get('fiscalyear_lock_date') and\
|
||||||
time.strptime(vals['fiscalyear_lock_date'], DEFAULT_SERVER_DATE_FORMAT)
|
fields.Date.from_string(vals['fiscalyear_lock_date'])
|
||||||
|
|
||||||
previous_month = datetime.strptime(fields.Date.today(), DEFAULT_SERVER_DATE_FORMAT) + relativedelta(months=-1)
|
previous_month = fields.Date.today() + relativedelta(months=-1)
|
||||||
days_previous_month = calendar.monthrange(previous_month.year, previous_month.month)
|
days_previous_month = calendar.monthrange(previous_month.year, previous_month.month)
|
||||||
previous_month = previous_month.replace(day=days_previous_month[1]).timetuple()
|
previous_month = previous_month.replace(day=days_previous_month[1])
|
||||||
for company in self:
|
for company in self:
|
||||||
old_fiscalyear_lock_date = company.fiscalyear_lock_date and\
|
old_fiscalyear_lock_date = company.fiscalyear_lock_date
|
||||||
time.strptime(company.fiscalyear_lock_date, DEFAULT_SERVER_DATE_FORMAT)
|
|
||||||
|
|
||||||
# The user attempts to remove the lock date for advisors
|
# 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:
|
if old_fiscalyear_lock_date and not fiscalyear_lock_date and 'fiscalyear_lock_date' in vals:
|
||||||
@@ -143,7 +142,7 @@ Best Regards,'''))
|
|||||||
# In case of no new period lock date in vals, fallback to the one defined in the company
|
# In case of no new period lock date in vals, fallback to the one defined in the company
|
||||||
if not period_lock_date:
|
if not period_lock_date:
|
||||||
if company.period_lock_date:
|
if company.period_lock_date:
|
||||||
period_lock_date = time.strptime(company.period_lock_date, DEFAULT_SERVER_DATE_FORMAT)
|
period_lock_date = company.period_lock_date
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -182,8 +181,8 @@ Best Regards,'''))
|
|||||||
], limit=1)
|
], limit=1)
|
||||||
if fiscalyear:
|
if fiscalyear:
|
||||||
return {
|
return {
|
||||||
'date_from': datetime.strptime(fiscalyear.date_from, DEFAULT_SERVER_DATE_FORMAT).date(),
|
'date_from': fiscalyear.date_from,
|
||||||
'date_to': datetime.strptime(fiscalyear.date_to, DEFAULT_SERVER_DATE_FORMAT).date(),
|
'date_to': fiscalyear.date_to,
|
||||||
'record': fiscalyear,
|
'record': fiscalyear,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +203,7 @@ Best Regards,'''))
|
|||||||
('date_to', '>=', date_from_str),
|
('date_to', '>=', date_from_str),
|
||||||
], limit=1)
|
], limit=1)
|
||||||
if fiscalyear_from:
|
if fiscalyear_from:
|
||||||
date_from = datetime.strptime(fiscalyear_from.date_to, DEFAULT_SERVER_DATE_FORMAT).date() + timedelta(days=1)
|
date_from = fiscalyear_from.date_to + timedelta(days=1)
|
||||||
|
|
||||||
fiscalyear_to = self.env['account.fiscal.year'].search([
|
fiscalyear_to = self.env['account.fiscal.year'].search([
|
||||||
('company_id', '=', self.id),
|
('company_id', '=', self.id),
|
||||||
@@ -212,7 +211,7 @@ Best Regards,'''))
|
|||||||
('date_to', '>=', date_to_str),
|
('date_to', '>=', date_to_str),
|
||||||
], limit=1)
|
], limit=1)
|
||||||
if fiscalyear_to:
|
if fiscalyear_to:
|
||||||
date_to = datetime.strptime(fiscalyear_to.date_from, DEFAULT_SERVER_DATE_FORMAT).date() - timedelta(days=1)
|
date_to = fiscalyear_to.date_from - timedelta(days=1)
|
||||||
|
|
||||||
return {'date_from': date_from, 'date_to': date_to}
|
return {'date_from': date_from, 'date_to': date_to}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from odoo import api, models, _
|
from odoo import api, fields, models, _
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
from odoo.tools import float_is_zero
|
from odoo.tools import float_is_zero
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -19,7 +19,8 @@ class ReportAgedPartnerBalance(models.AbstractModel):
|
|||||||
# The context key allow it to appear
|
# The context key allow it to appear
|
||||||
ctx = self._context
|
ctx = self._context
|
||||||
periods = {}
|
periods = {}
|
||||||
start = datetime.strptime(date_from, "%Y-%m-%d") - relativedelta(days=1)
|
date_from = fields.Date.from_string(date_from)
|
||||||
|
start = date_from - relativedelta(days=1)
|
||||||
for i in range(5)[::-1]:
|
for i in range(5)[::-1]:
|
||||||
stop = start - relativedelta(days=period_length)
|
stop = start - relativedelta(days=period_length)
|
||||||
periods[str(i)] = {
|
periods[str(i)] = {
|
||||||
@@ -220,7 +221,7 @@ class ReportAgedPartnerBalance(models.AbstractModel):
|
|||||||
docs = self.env[model].browse(self.env.context.get('active_id'))
|
docs = self.env[model].browse(self.env.context.get('active_id'))
|
||||||
|
|
||||||
target_move = data['form'].get('target_move', 'all')
|
target_move = data['form'].get('target_move', 'all')
|
||||||
date_from = data['form'].get('date_from', time.strftime('%Y-%m-%d'))
|
date_from = fields.Date.from_string(data['form'].get('date_from')) or fields.Date.today()
|
||||||
|
|
||||||
if data['form']['result_selection'] == 'customer':
|
if data['form']['result_selection'] == 'customer':
|
||||||
account_type = ['receivable']
|
account_type = ['receivable']
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from datetime import datetime
|
|||||||
import time
|
import time
|
||||||
from odoo import api, models, _
|
from odoo import api, models, _
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
|
|
||||||
|
|
||||||
|
|
||||||
class ReportPartnerLedger(models.AbstractModel):
|
class ReportPartnerLedger(models.AbstractModel):
|
||||||
@@ -35,7 +34,6 @@ class ReportPartnerLedger(models.AbstractModel):
|
|||||||
lang_id = lang._lang_get(lang_code)
|
lang_id = lang._lang_get(lang_code)
|
||||||
date_format = lang_id.date_format
|
date_format = lang_id.date_format
|
||||||
for r in res:
|
for r in res:
|
||||||
r['date'] = datetime.strptime(r['date'], DEFAULT_SERVER_DATE_FORMAT).strftime(date_format)
|
|
||||||
r['displayed_name'] = '-'.join(
|
r['displayed_name'] = '-'.join(
|
||||||
r[field_name] for field_name in ('move_name', 'ref', 'name')
|
r[field_name] for field_name in ('move_name', 'ref', 'name')
|
||||||
if r[field_name] not in (None, '', '/')
|
if r[field_name] not in (None, '', '/')
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from odoo.addons.account.tests.account_test_classes import AccountingTestCase
|
from odoo.addons.account.tests.account_test_classes import AccountingTestCase
|
||||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
|
|
||||||
|
|
||||||
import odoo.tests
|
import odoo.tests
|
||||||
|
from odoo import fields
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
@@ -19,10 +19,10 @@ class TestFiscalPosition(AccountingTestCase):
|
|||||||
:param expected_date_from: The expected date_from after computation.
|
:param expected_date_from: The expected date_from after computation.
|
||||||
:param expected_date_to: The expected date_to after computation.
|
:param expected_date_to: The expected date_to after computation.
|
||||||
'''
|
'''
|
||||||
current_date = datetime.strptime(date, DEFAULT_SERVER_DATE_FORMAT)
|
current_date = fields.Date.from_string(date)
|
||||||
res = company.compute_fiscalyear_dates(current_date)
|
res = company.compute_fiscalyear_dates(current_date)
|
||||||
self.assertEqual(res['date_from'].strftime(DEFAULT_SERVER_DATE_FORMAT), expected_date_from)
|
self.assertEqual(res['date_from'], fields.Date.from_string(expected_date_from))
|
||||||
self.assertEqual(res['date_to'].strftime(DEFAULT_SERVER_DATE_FORMAT), expected_date_to)
|
self.assertEqual(res['date_to'], fields.Date.from_string(expected_date_to))
|
||||||
|
|
||||||
def test_default_fiscal_year(self):
|
def test_default_fiscal_year(self):
|
||||||
'''Basic case with a fiscal year xxxx-01-01 - xxxx-12-31.'''
|
'''Basic case with a fiscal year xxxx-01-01 - xxxx-12-31.'''
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from odoo import api
|
from odoo import api, fields
|
||||||
from odoo.addons.account.tests.account_test_classes import AccountingTestCase
|
from odoo.addons.account.tests.account_test_classes import AccountingTestCase
|
||||||
from odoo.tests import tagged
|
from odoo.tests import tagged
|
||||||
import time
|
import time
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class AccountAgedTrialBalance(models.TransientModel):
|
|||||||
if not data['form']['date_from']:
|
if not data['form']['date_from']:
|
||||||
raise UserError(_('You must set a start date.'))
|
raise UserError(_('You must set a start date.'))
|
||||||
|
|
||||||
start = datetime.strptime(data['form']['date_from'], "%Y-%m-%d")
|
start = fields.Date.from_string(data['form']['date_from'])
|
||||||
|
|
||||||
for i in range(5)[::-1]:
|
for i in range(5)[::-1]:
|
||||||
stop = start - relativedelta(days=period_length - 1)
|
stop = start - relativedelta(days=period_length - 1)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ from dateutil.relativedelta import relativedelta
|
|||||||
|
|
||||||
from odoo import api, fields, models, _
|
from odoo import api, fields, models, _
|
||||||
from odoo.exceptions import UserError, ValidationError
|
from odoo.exceptions import UserError, ValidationError
|
||||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF
|
|
||||||
from odoo.tools import float_compare, float_is_zero
|
from odoo.tools import float_compare, float_is_zero
|
||||||
|
|
||||||
|
|
||||||
@@ -164,7 +163,7 @@ class AccountAssetAsset(models.Model):
|
|||||||
if self.prorata:
|
if self.prorata:
|
||||||
amount = amount_to_depr / self.method_number
|
amount = amount_to_depr / self.method_number
|
||||||
if sequence == 1:
|
if sequence == 1:
|
||||||
date = datetime.strptime(self.date, DF)
|
date = self.date
|
||||||
if self.method_period % 12 != 0:
|
if self.method_period % 12 != 0:
|
||||||
month_days = calendar.monthrange(date.year, date.month)[1]
|
month_days = calendar.monthrange(date.year, date.month)[1]
|
||||||
days = month_days - date.day + 1
|
days = month_days - date.day + 1
|
||||||
@@ -176,7 +175,7 @@ class AccountAssetAsset(models.Model):
|
|||||||
amount = residual_amount * self.method_progress_factor
|
amount = residual_amount * self.method_progress_factor
|
||||||
if self.prorata:
|
if self.prorata:
|
||||||
if sequence == 1:
|
if sequence == 1:
|
||||||
date = datetime.strptime(self.date, DF)
|
date = self.date
|
||||||
if self.method_period % 12 != 0:
|
if self.method_period % 12 != 0:
|
||||||
month_days = calendar.monthrange(date.year, date.month)[1]
|
month_days = calendar.monthrange(date.year, date.month)[1]
|
||||||
days = month_days - date.day + 1
|
days = month_days - date.day + 1
|
||||||
@@ -189,7 +188,7 @@ class AccountAssetAsset(models.Model):
|
|||||||
def _compute_board_undone_dotation_nb(self, depreciation_date, total_days):
|
def _compute_board_undone_dotation_nb(self, depreciation_date, total_days):
|
||||||
undone_dotation_number = self.method_number
|
undone_dotation_number = self.method_number
|
||||||
if self.method_time == 'end':
|
if self.method_time == 'end':
|
||||||
end_date = datetime.strptime(self.method_end, DF).date()
|
end_date = self.method_end
|
||||||
undone_dotation_number = 0
|
undone_dotation_number = 0
|
||||||
while depreciation_date <= end_date:
|
while depreciation_date <= end_date:
|
||||||
depreciation_date = date(depreciation_date.year, depreciation_date.month, depreciation_date.day) + relativedelta(months=+self.method_period)
|
depreciation_date = date(depreciation_date.year, depreciation_date.month, depreciation_date.day) + relativedelta(months=+self.method_period)
|
||||||
@@ -213,12 +212,11 @@ class AccountAssetAsset(models.Model):
|
|||||||
|
|
||||||
# if we already have some previous validated entries, starting date is last entry + method period
|
# if we already have some previous validated entries, starting date is last entry + method period
|
||||||
if posted_depreciation_line_ids and posted_depreciation_line_ids[-1].depreciation_date:
|
if posted_depreciation_line_ids and posted_depreciation_line_ids[-1].depreciation_date:
|
||||||
last_depreciation_date = datetime.strptime(posted_depreciation_line_ids[-1].depreciation_date, DF).date()
|
last_depreciation_date = fields.Date.from_string(posted_depreciation_line_ids[-1].depreciation_date)
|
||||||
depreciation_date = last_depreciation_date + relativedelta(months=+self.method_period)
|
depreciation_date = last_depreciation_date + relativedelta(months=+self.method_period)
|
||||||
else:
|
else:
|
||||||
# depreciation_date computed from the purchase date
|
# depreciation_date computed from the purchase date
|
||||||
depreciation_date = self.date
|
depreciation_date = self.date
|
||||||
depreciation_date = datetime.strptime(depreciation_date, DF)
|
|
||||||
if self.date_first_depreciation == 'last_day_period':
|
if self.date_first_depreciation == 'last_day_period':
|
||||||
# depreciation_date = the last day of the month
|
# depreciation_date = the last day of the month
|
||||||
depreciation_date = depreciation_date + relativedelta(day=31)
|
depreciation_date = depreciation_date + relativedelta(day=31)
|
||||||
@@ -226,14 +224,12 @@ class AccountAssetAsset(models.Model):
|
|||||||
if self.method_period == 12:
|
if self.method_period == 12:
|
||||||
depreciation_date = depreciation_date + relativedelta(month=self.company_id.fiscalyear_last_month)
|
depreciation_date = depreciation_date + relativedelta(month=self.company_id.fiscalyear_last_month)
|
||||||
depreciation_date = depreciation_date + relativedelta(day=self.company_id.fiscalyear_last_day)
|
depreciation_date = depreciation_date + relativedelta(day=self.company_id.fiscalyear_last_day)
|
||||||
if datetime.strftime(depreciation_date, DF) < self.date:
|
if depreciation_date < self.date:
|
||||||
depreciation_date = depreciation_date + relativedelta(years=1)
|
depreciation_date = depreciation_date + relativedelta(years=1)
|
||||||
elif self.first_depreciation_manual_date and self.first_depreciation_manual_date != self.date:
|
elif self.first_depreciation_manual_date and self.first_depreciation_manual_date != self.date:
|
||||||
# depreciation_date set manually from the 'first_depreciation_manual_date' field
|
# depreciation_date set manually from the 'first_depreciation_manual_date' field
|
||||||
depreciation_date = self.first_depreciation_manual_date
|
depreciation_date = self.first_depreciation_manual_date
|
||||||
depreciation_date = datetime.strptime(depreciation_date, DF)
|
|
||||||
|
|
||||||
depreciation_date = depreciation_date.date()
|
|
||||||
total_days = (depreciation_date.year % 4) and 365 or 366
|
total_days = (depreciation_date.year % 4) and 365 or 366
|
||||||
month_day = depreciation_date.day
|
month_day = depreciation_date.day
|
||||||
undone_dotation_number = self._compute_board_undone_dotation_nb(depreciation_date, total_days)
|
undone_dotation_number = self._compute_board_undone_dotation_nb(depreciation_date, total_days)
|
||||||
@@ -252,7 +248,7 @@ class AccountAssetAsset(models.Model):
|
|||||||
'name': (self.code or '') + '/' + str(sequence),
|
'name': (self.code or '') + '/' + str(sequence),
|
||||||
'remaining_value': residual_amount,
|
'remaining_value': residual_amount,
|
||||||
'depreciated_value': self.value - (self.salvage_value + residual_amount),
|
'depreciated_value': self.value - (self.salvage_value + residual_amount),
|
||||||
'depreciation_date': depreciation_date.strftime(DF),
|
'depreciation_date': depreciation_date,
|
||||||
}
|
}
|
||||||
commands.append((0, False, vals))
|
commands.append((0, False, vals))
|
||||||
|
|
||||||
@@ -327,7 +323,7 @@ class AccountAssetAsset(models.Model):
|
|||||||
|
|
||||||
# Create a new depr. line with the residual amount and post it
|
# Create a new depr. line with the residual amount and post it
|
||||||
sequence = len(asset.depreciation_line_ids) - len(unposted_depreciation_line_ids) + 1
|
sequence = len(asset.depreciation_line_ids) - len(unposted_depreciation_line_ids) + 1
|
||||||
today = datetime.today().strftime(DF)
|
today = fields.Datetime.today()
|
||||||
vals = {
|
vals = {
|
||||||
'amount': asset.value_residual,
|
'amount': asset.value_residual,
|
||||||
'asset_id': asset.id,
|
'asset_id': asset.id,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from datetime import datetime
|
|||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
from odoo import api, fields, models, _
|
from odoo import api, fields, models, _
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF
|
|
||||||
|
|
||||||
from odoo.addons import decimal_precision as dp
|
from odoo.addons import decimal_precision as dp
|
||||||
|
|
||||||
@@ -65,10 +64,10 @@ class AccountInvoiceLine(models.Model):
|
|||||||
if self.invoice_id.type in ['out_invoice', 'out_refund']:
|
if self.invoice_id.type in ['out_invoice', 'out_refund']:
|
||||||
self.asset_mrr = self.price_subtotal_signed / months
|
self.asset_mrr = self.price_subtotal_signed / months
|
||||||
if self.invoice_id.date_invoice:
|
if self.invoice_id.date_invoice:
|
||||||
start_date = datetime.strptime(self.invoice_id.date_invoice, DF).replace(day=1)
|
start_date = self.invoice_id.date_invoice.replace(day=1)
|
||||||
end_date = (start_date + relativedelta(months=months, days=-1))
|
end_date = (start_date + relativedelta(months=months, days=-1))
|
||||||
self.asset_start_date = start_date.strftime(DF)
|
self.asset_start_date = start_date
|
||||||
self.asset_end_date = end_date.strftime(DF)
|
self.asset_end_date = end_date
|
||||||
|
|
||||||
@api.one
|
@api.one
|
||||||
def asset_create(self):
|
def asset_create(self):
|
||||||
|
|||||||
@@ -205,22 +205,21 @@ class CrossoveredBudgetLines(models.Model):
|
|||||||
@api.multi
|
@api.multi
|
||||||
def _compute_theoritical_amount(self):
|
def _compute_theoritical_amount(self):
|
||||||
# beware: 'today' variable is mocked in the python tests and thus, its implementation matter
|
# beware: 'today' variable is mocked in the python tests and thus, its implementation matter
|
||||||
today = fields.Datetime.from_string(fields.Date.today())
|
today = fields.Date.today()
|
||||||
for line in self:
|
for line in self:
|
||||||
if line.paid_date:
|
if line.paid_date:
|
||||||
if fields.Datetime.to_string(today) <= line.paid_date:
|
if today <= line.paid_date:
|
||||||
theo_amt = 0.00
|
theo_amt = 0.00
|
||||||
else:
|
else:
|
||||||
theo_amt = line.planned_amount
|
theo_amt = line.planned_amount
|
||||||
else:
|
else:
|
||||||
line_timedelta = fields.Datetime.from_string(line.date_to) - fields.Datetime.from_string(
|
line_timedelta = line.date_to - line.date_from
|
||||||
line.date_from)
|
elapsed_timedelta = today - line.date_from
|
||||||
elapsed_timedelta = today - fields.Datetime.from_string(line.date_from)
|
|
||||||
|
|
||||||
if elapsed_timedelta.days < 0:
|
if elapsed_timedelta.days < 0:
|
||||||
# If the budget line has not started yet, theoretical amount should be zero
|
# If the budget line has not started yet, theoretical amount should be zero
|
||||||
theo_amt = 0.00
|
theo_amt = 0.00
|
||||||
elif line_timedelta.days > 0 and today < fields.Datetime.from_string(line.date_to):
|
elif line_timedelta.days > 0 and today < line.date_to:
|
||||||
# If today is between the budget line date_from and date_to
|
# If today is between the budget line date_from and date_to
|
||||||
theo_amt = (elapsed_timedelta.total_seconds() / line_timedelta.total_seconds()) * line.planned_amount
|
theo_amt = (elapsed_timedelta.total_seconds() / line_timedelta.total_seconds()) * line.planned_amount
|
||||||
else:
|
else:
|
||||||
@@ -265,14 +264,14 @@ class CrossoveredBudgetLines(models.Model):
|
|||||||
@api.one
|
@api.one
|
||||||
@api.constrains('date_from', 'date_to')
|
@api.constrains('date_from', 'date_to')
|
||||||
def _line_dates_between_budget_dates(self):
|
def _line_dates_between_budget_dates(self):
|
||||||
budget_date_from = fields.Datetime.from_string(self.crossovered_budget_id.date_from)
|
budget_date_from = self.crossovered_budget_id.date_from
|
||||||
budget_date_to = fields.Datetime.from_string(self.crossovered_budget_id.date_to)
|
budget_date_to = self.crossovered_budget_id.date_to
|
||||||
if self.date_from:
|
if self.date_from:
|
||||||
date_from = fields.Datetime.from_string(self.date_from)
|
date_from = self.date_from
|
||||||
if date_from < budget_date_from or date_from > budget_date_to:
|
if date_from < budget_date_from or date_from > budget_date_to:
|
||||||
raise ValidationError(_('"Start Date" of the budget line should be included in the Period of the budget'))
|
raise ValidationError(_('"Start Date" of the budget line should be included in the Period of the budget'))
|
||||||
|
|
||||||
if self.date_to:
|
if self.date_to:
|
||||||
date_to = fields.Datetime.from_string(self.date_to)
|
date_to = self.date_to
|
||||||
if date_to < budget_date_from or date_to > budget_date_to:
|
if date_to < budget_date_from or date_to > budget_date_to:
|
||||||
raise ValidationError(_('"End Date" of the budget line should be included in the Period of the budget'))
|
raise ValidationError(_('"End Date" of the budget line should be included in the Period of the budget'))
|
||||||
|
|||||||
@@ -35,29 +35,32 @@ class TestTheoreticalAmount(TestAccountBudgetCommon):
|
|||||||
'account_ids': [(4, account_rev.id, 0)],
|
'account_ids': [(4, account_rev.id, 0)],
|
||||||
})
|
})
|
||||||
#create the budget and budget lines
|
#create the budget and budget lines
|
||||||
first_january = datetime.now().replace(day=1, month=1)
|
first_january = Datetime.now().replace(day=1, month=1)
|
||||||
self.last_day_of_budget = first_january + timedelta(days=364) # will be 30th of December or 31th in case of leap year
|
self.last_day_of_budget = first_january + timedelta(days=364) # will be 30th of December or 31th in case of leap year
|
||||||
|
|
||||||
|
date_from = first_january.date()
|
||||||
|
date_to = self.last_day_of_budget.date()
|
||||||
|
|
||||||
crossovered_budget = self.env['crossovered.budget'].create({
|
crossovered_budget = self.env['crossovered.budget'].create({
|
||||||
'name': 'test budget name',
|
'name': 'test budget name',
|
||||||
'date_from': str(first_january.date()),
|
'date_from': date_from,
|
||||||
'date_to': str(self.last_day_of_budget.date()),
|
'date_to': date_to,
|
||||||
})
|
})
|
||||||
crossovered_budget_line_obj = self.env['crossovered.budget.lines']
|
crossovered_budget_line_obj = self.env['crossovered.budget.lines']
|
||||||
self.line = crossovered_budget_line_obj.create({
|
self.line = crossovered_budget_line_obj.create({
|
||||||
'crossovered_budget_id': crossovered_budget.id,
|
'crossovered_budget_id': crossovered_budget.id,
|
||||||
'general_budget_id': buget_post.id,
|
'general_budget_id': buget_post.id,
|
||||||
'date_from': str(first_january.date()),
|
'date_from': date_from,
|
||||||
'date_to': str(self.last_day_of_budget.date()),
|
'date_to': date_to,
|
||||||
'planned_amount': -364,
|
'planned_amount': -364,
|
||||||
})
|
})
|
||||||
self.paid_date_line = crossovered_budget_line_obj.create({
|
self.paid_date_line = crossovered_budget_line_obj.create({
|
||||||
'crossovered_budget_id': crossovered_budget.id,
|
'crossovered_budget_id': crossovered_budget.id,
|
||||||
'general_budget_id': buget_post.id,
|
'general_budget_id': buget_post.id,
|
||||||
'date_from': str(first_january.date()),
|
'date_from': date_from,
|
||||||
'date_to': str(self.last_day_of_budget.date()),
|
'date_to': date_to,
|
||||||
'planned_amount': -364,
|
'planned_amount': -364,
|
||||||
'paid_date': str(datetime.now().year) + '-09-09',
|
'paid_date': Date.today().replace(day=9, month=9),
|
||||||
})
|
})
|
||||||
|
|
||||||
self.patcher = patch('odoo.addons.account_budget.models.account_budget.fields.Date', wraps=Date)
|
self.patcher = patch('odoo.addons.account_budget.models.account_budget.fields.Date', wraps=Date)
|
||||||
@@ -74,7 +77,7 @@ class TestTheoreticalAmount(TestAccountBudgetCommon):
|
|||||||
]
|
]
|
||||||
for date, expected_amount in test_list:
|
for date, expected_amount in test_list:
|
||||||
_logger.info("Checking theoritical amount for the date: " + date)
|
_logger.info("Checking theoritical amount for the date: " + date)
|
||||||
self.mock_date.today.return_value = date
|
self.mock_date.today.return_value = Date.from_string(date)
|
||||||
self.assertAlmostEqual(self.line.theoritical_amount, expected_amount)
|
self.assertAlmostEqual(self.line.theoritical_amount, expected_amount)
|
||||||
#invalidate the cache of the budget lines to recompute the theoritical amount at next iteration
|
#invalidate the cache of the budget lines to recompute the theoritical amount at next iteration
|
||||||
self.line.invalidate_cache()
|
self.line.invalidate_cache()
|
||||||
@@ -84,13 +87,13 @@ class TestTheoreticalAmount(TestAccountBudgetCommon):
|
|||||||
(str(datetime.now().year) + '-01-01', 0),
|
(str(datetime.now().year) + '-01-01', 0),
|
||||||
(str(datetime.now().year) + '-01-02', 0),
|
(str(datetime.now().year) + '-01-02', 0),
|
||||||
(str(datetime.now().year) + '-09-08', 0),
|
(str(datetime.now().year) + '-09-08', 0),
|
||||||
(str(datetime.now().year) + '-09-09', -364),
|
(str(datetime.now().year) + '-09-09', 0),
|
||||||
(str(datetime.now().year) + '-09-10', -364),
|
(str(datetime.now().year) + '-09-10', -364),
|
||||||
(str(self.last_day_of_budget.date()), -364),
|
(str(self.last_day_of_budget.date()), -364),
|
||||||
]
|
]
|
||||||
for date, expected_amount in test_list:
|
for date, expected_amount in test_list:
|
||||||
_logger.info("Checking theoritical amount for the date: " + date)
|
_logger.info("Checking theoritical amount for the date: " + date)
|
||||||
self.mock_date.today.return_value = date
|
self.mock_date.today.return_value = Date.from_string(date)
|
||||||
self.assertAlmostEqual(self.paid_date_line.theoritical_amount, expected_amount)
|
self.assertAlmostEqual(self.paid_date_line.theoritical_amount, expected_amount)
|
||||||
#invalidate the cache of the budget lines to recompute the theoritical amount at next iteration
|
#invalidate the cache of the budget lines to recompute the theoritical amount at next iteration
|
||||||
self.paid_date_line.invalidate_cache()
|
self.paid_date_line.invalidate_cache()
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import time
|
|||||||
import odoo
|
import odoo
|
||||||
from odoo import api, fields, models, SUPERUSER_ID
|
from odoo import api, fields, models, SUPERUSER_ID
|
||||||
from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
|
from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
|
||||||
|
from odoo.tools import date_utils
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ TIMEOUT = 50
|
|||||||
# Bus
|
# Bus
|
||||||
#----------------------------------------------------------
|
#----------------------------------------------------------
|
||||||
def json_dump(v):
|
def json_dump(v):
|
||||||
return json.dumps(v, separators=(',', ':'))
|
return json.dumps(v, separators=(',', ':'), default=date_utils.json_default)
|
||||||
|
|
||||||
def hashable(key):
|
def hashable(key):
|
||||||
if isinstance(key, list):
|
if isinstance(key, list):
|
||||||
|
|||||||
@@ -43,11 +43,11 @@ class BusPresence(models.Model):
|
|||||||
# update the presence or a create a new one
|
# update the presence or a create a new one
|
||||||
if not presence: # create a new presence for the user
|
if not presence: # create a new presence for the user
|
||||||
values['user_id'] = self._uid
|
values['user_id'] = self._uid
|
||||||
values['last_presence'] = last_presence.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
values['last_presence'] = last_presence
|
||||||
self.create(values)
|
self.create(values)
|
||||||
else: # update the last_presence if necessary, and write values
|
else: # update the last_presence if necessary, and write values
|
||||||
if datetime.datetime.strptime(presence.last_presence, DEFAULT_SERVER_DATETIME_FORMAT) < last_presence:
|
if presence.last_presence < last_presence:
|
||||||
values['last_presence'] = last_presence.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
values['last_presence'] = last_presence
|
||||||
# Hide transaction serialization errors, which can be ignored, the presence update is not essential
|
# Hide transaction serialization errors, which can be ignored, the presence update is not essential
|
||||||
with tools.mute_logger('odoo.sql_db'):
|
with tools.mute_logger('odoo.sql_db'):
|
||||||
presence.write(values)
|
presence.write(values)
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ class AlarmManager(models.AbstractModel):
|
|||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def get_next_mail(self):
|
def get_next_mail(self):
|
||||||
now = fields.Datetime.now()
|
now = fields.Datetime.to_string(fields.Datetime.now())
|
||||||
last_notif_mail = self.env['ir.config_parameter'].sudo().get_param('calendar.last_notif_mail', default=now)
|
last_notif_mail = self.env['ir.config_parameter'].sudo().get_param('calendar.last_notif_mail', default=now)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -391,7 +391,7 @@ class AlarmManager(models.AbstractModel):
|
|||||||
if at_least_one and not last_found: # if the precedent event had an alarm but not this one, we can stop the search for this event
|
if at_least_one and not last_found: # if the precedent event had an alarm but not this one, we can stop the search for this event
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
in_date_format = datetime.strptime(meeting.start, DEFAULT_SERVER_DATETIME_FORMAT)
|
in_date_format = meeting.start
|
||||||
last_found = self.do_check_alarm_for_one_date(in_date_format, meeting, max_delta, 0, 'email', after=last_notif_mail, missing=True)
|
last_found = self.do_check_alarm_for_one_date(in_date_format, meeting, max_delta, 0, 'email', after=last_notif_mail, missing=True)
|
||||||
for alert in last_found:
|
for alert in last_found:
|
||||||
self.do_mail_reminder(alert)
|
self.do_mail_reminder(alert)
|
||||||
@@ -873,9 +873,9 @@ class Meeting(models.Model):
|
|||||||
"""
|
"""
|
||||||
for meeting in self:
|
for meeting in self:
|
||||||
if meeting.allday:
|
if meeting.allday:
|
||||||
meeting.start_date = meeting.start
|
meeting.start_date = meeting.start.date()
|
||||||
meeting.start_datetime = False
|
meeting.start_datetime = False
|
||||||
meeting.stop_date = meeting.stop
|
meeting.stop_date = meeting.stop.date()
|
||||||
meeting.stop_datetime = False
|
meeting.stop_datetime = False
|
||||||
|
|
||||||
meeting.duration = 0.0
|
meeting.duration = 0.0
|
||||||
@@ -897,13 +897,13 @@ class Meeting(models.Model):
|
|||||||
enddate = tz.localize(enddate)
|
enddate = tz.localize(enddate)
|
||||||
enddate = enddate.replace(hour=18)
|
enddate = enddate.replace(hour=18)
|
||||||
enddate = enddate.astimezone(pytz.utc)
|
enddate = enddate.astimezone(pytz.utc)
|
||||||
meeting.stop = fields.Datetime.to_string(enddate)
|
meeting.stop = enddate
|
||||||
|
|
||||||
startdate = fields.Datetime.from_string(meeting.start_date)
|
startdate = fields.Datetime.from_string(meeting.start_date)
|
||||||
startdate = tz.localize(startdate) # Add "+hh:mm" timezone
|
startdate = tz.localize(startdate) # Add "+hh:mm" timezone
|
||||||
startdate = startdate.replace(hour=8) # Set 8 AM in localtime
|
startdate = startdate.replace(hour=8) # Set 8 AM in localtime
|
||||||
startdate = startdate.astimezone(pytz.utc) # Convert to UTC
|
startdate = startdate.astimezone(pytz.utc) # Convert to UTC
|
||||||
meeting.start = fields.Datetime.to_string(startdate)
|
meeting.start = startdate
|
||||||
else:
|
else:
|
||||||
meeting.write({'start': meeting.start_datetime,
|
meeting.write({'start': meeting.start_datetime,
|
||||||
'stop': meeting.stop_datetime})
|
'stop': meeting.stop_datetime})
|
||||||
@@ -945,9 +945,9 @@ class Meeting(models.Model):
|
|||||||
@api.onchange('start_datetime', 'duration')
|
@api.onchange('start_datetime', 'duration')
|
||||||
def _onchange_duration(self):
|
def _onchange_duration(self):
|
||||||
if self.start_datetime:
|
if self.start_datetime:
|
||||||
start = fields.Datetime.from_string(self.start_datetime)
|
start = self.start_datetime
|
||||||
self.start = self.start_datetime
|
self.start = self.start_datetime
|
||||||
self.stop = fields.Datetime.to_string(start + timedelta(hours=self.duration))
|
self.stop = start + timedelta(hours=self.duration)
|
||||||
|
|
||||||
@api.onchange('start_date')
|
@api.onchange('start_date')
|
||||||
def _onchange_start_date(self):
|
def _onchange_start_date(self):
|
||||||
@@ -971,9 +971,9 @@ class Meeting(models.Model):
|
|||||||
def ics_datetime(idate, allday=False):
|
def ics_datetime(idate, allday=False):
|
||||||
if idate:
|
if idate:
|
||||||
if allday:
|
if allday:
|
||||||
return fields.Date.from_string(idate)
|
return idate
|
||||||
else:
|
else:
|
||||||
return fields.Datetime.from_string(idate).replace(tzinfo=pytz.timezone('UTC'))
|
return idate.replace(tzinfo=pytz.timezone('UTC'))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -989,7 +989,7 @@ class Meeting(models.Model):
|
|||||||
|
|
||||||
if not meeting.start or not meeting.stop:
|
if not meeting.start or not meeting.stop:
|
||||||
raise UserError(_("First you have to specify the date of the invitation."))
|
raise UserError(_("First you have to specify the date of the invitation."))
|
||||||
event.add('created').value = ics_datetime(time.strftime(DEFAULT_SERVER_DATETIME_FORMAT))
|
event.add('created').value = ics_datetime(fields.Datetime.now())
|
||||||
event.add('dtstart').value = ics_datetime(meeting.start, meeting.allday)
|
event.add('dtstart').value = ics_datetime(meeting.start, meeting.allday)
|
||||||
event.add('dtend').value = ics_datetime(meeting.stop, meeting.allday)
|
event.add('dtend').value = ics_datetime(meeting.stop, meeting.allday)
|
||||||
event.add('summary').value = meeting.name
|
event.add('summary').value = meeting.name
|
||||||
@@ -1245,7 +1245,8 @@ class Meeting(models.Model):
|
|||||||
return ''
|
return ''
|
||||||
|
|
||||||
def get_end_date():
|
def get_end_date():
|
||||||
end_date_new = ''.join((re.compile('\d')).findall(self.final_date)) + 'T235959Z' if self.final_date else False
|
final_date = fields.Date.to_string(self.final_date)
|
||||||
|
end_date_new = ''.join((re.compile('\d')).findall(final_date)) + 'T235959Z' if final_date else False
|
||||||
return (self.end_type == 'count' and (';COUNT=' + str(self.count)) or '') +\
|
return (self.end_type == 'count' and (';COUNT=' + str(self.count)) or '') +\
|
||||||
((end_date_new and self.end_type == 'end_date' and (';UNTIL=' + end_date_new)) or '')
|
((end_date_new and self.end_type == 'end_date' and (';UNTIL=' + end_date_new)) or '')
|
||||||
|
|
||||||
@@ -1379,6 +1380,7 @@ class Meeting(models.Model):
|
|||||||
meeting_origin = self.browse(real_id)
|
meeting_origin = self.browse(real_id)
|
||||||
|
|
||||||
data = self.read(['allday', 'start', 'stop', 'rrule', 'duration'])[0]
|
data = self.read(['allday', 'start', 'stop', 'rrule', 'duration'])[0]
|
||||||
|
d_class = fields.Date if data['allday'] else fields.Datetime
|
||||||
if data.get('rrule'):
|
if data.get('rrule'):
|
||||||
data.update(
|
data.update(
|
||||||
values,
|
values,
|
||||||
@@ -1387,7 +1389,8 @@ class Meeting(models.Model):
|
|||||||
rrule_type=False,
|
rrule_type=False,
|
||||||
rrule='',
|
rrule='',
|
||||||
recurrency=False,
|
recurrency=False,
|
||||||
final_date=datetime.strptime(data.get('start'), DEFAULT_SERVER_DATETIME_FORMAT if data['allday'] else DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(hours=values.get('duration', False) or data.get('duration'))
|
final_date=d_class.from_string(data.get('start')) \
|
||||||
|
+ timedelta(hours=values.get('duration', False) or data.get('duration'))
|
||||||
)
|
)
|
||||||
|
|
||||||
# do not copy the id
|
# do not copy the id
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, time
|
||||||
|
|
||||||
from odoo import fields
|
from odoo import fields
|
||||||
from odoo.tests.common import TransactionCase
|
from odoo.tests.common import TransactionCase
|
||||||
@@ -34,7 +34,7 @@ class TestCalendar(TransactionCase):
|
|||||||
})
|
})
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
(m.start_datetime, m.stop_datetime),
|
(str(m.start_datetime), str(m.stop_datetime)),
|
||||||
(u'2017-07-12 14:30:00', u'2017-07-12 15:00:00'),
|
(u'2017-07-12 14:30:00', u'2017-07-12 15:00:00'),
|
||||||
"Sanity check"
|
"Sanity check"
|
||||||
)
|
)
|
||||||
@@ -102,8 +102,8 @@ class TestCalendar(TransactionCase):
|
|||||||
# I create a recuring rule for my event
|
# I create a recuring rule for my event
|
||||||
calendar_event_sprint_review = self.CalendarEvent.create({
|
calendar_event_sprint_review = self.CalendarEvent.create({
|
||||||
'name': 'Begin of month meeting',
|
'name': 'Begin of month meeting',
|
||||||
'start': fields.Date.today() + ' 12:00:00',
|
'start': datetime.combine(fields.Date.today(), time(12, 0)),
|
||||||
'stop': fields.Date.today() + ' 18:00:00',
|
'stop': datetime.combine(fields.Date.today(), time(18, 0)),
|
||||||
'recurrency': True,
|
'recurrency': True,
|
||||||
'rrule': 'FREQ=MONTHLY;INTERVAL=1;COUNT=12;BYDAY=1MO'
|
'rrule': 'FREQ=MONTHLY;INTERVAL=1;COUNT=12;BYDAY=1MO'
|
||||||
})
|
})
|
||||||
@@ -130,7 +130,7 @@ class TestCalendar(TransactionCase):
|
|||||||
'stop': '2017-07-12 15:00:00',
|
'stop': '2017-07-12 15:00:00',
|
||||||
})
|
})
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
(m.start_datetime, m.stop_datetime),
|
(str(m.start_datetime), str(m.stop_datetime)),
|
||||||
(u'2017-07-12 14:30:00', u'2017-07-12 15:00:00'),
|
(u'2017-07-12 14:30:00', u'2017-07-12 15:00:00'),
|
||||||
"Sanity check"
|
"Sanity check"
|
||||||
)
|
)
|
||||||
@@ -154,11 +154,11 @@ class TestCalendar(TransactionCase):
|
|||||||
|
|
||||||
records = m.detach_recurring_event(values)
|
records = m.detach_recurring_event(values)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
(m.start_datetime, m.stop_datetime),
|
(str(m.start_datetime), str(m.stop_datetime)),
|
||||||
(u'2017-07-12 14:30:00', u'2017-07-12 15:00:00'),
|
('2017-07-12 14:30:00', u'2017-07-12 15:00:00'),
|
||||||
)
|
)
|
||||||
self.assertEquals(
|
self.assertEquals(
|
||||||
(records.start_datetime, records.stop_datetime),
|
(str(records.start_datetime), str(records.stop_datetime)),
|
||||||
(u'2017-07-10 15:30:00', u'2017-07-10 16:00:00'),
|
(u'2017-07-10 15:30:00', u'2017-07-10 16:00:00'),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -243,7 +243,7 @@ class TestCalendar(TransactionCase):
|
|||||||
self.assertEqual(test_record.activity_ids.summary, test_name)
|
self.assertEqual(test_record.activity_ids.summary, test_name)
|
||||||
self.assertEqual(test_record.activity_ids.note, test_description)
|
self.assertEqual(test_record.activity_ids.note, test_description)
|
||||||
self.assertEqual(test_record.activity_ids.user_id, self.env.user)
|
self.assertEqual(test_record.activity_ids.user_id, self.env.user)
|
||||||
self.assertEqual(test_record.activity_ids.date_deadline, fields.Date.to_string((now + timedelta(days=-1)).date()))
|
self.assertEqual(test_record.activity_ids.date_deadline, (now + timedelta(days=-1)).date())
|
||||||
|
|
||||||
# updating event should update activity
|
# updating event should update activity
|
||||||
test_event.write({
|
test_event.write({
|
||||||
@@ -255,7 +255,7 @@ class TestCalendar(TransactionCase):
|
|||||||
self.assertEqual(test_record.activity_ids.summary, '%s2' % test_name)
|
self.assertEqual(test_record.activity_ids.summary, '%s2' % test_name)
|
||||||
self.assertEqual(test_record.activity_ids.note, test_description2)
|
self.assertEqual(test_record.activity_ids.note, test_description2)
|
||||||
self.assertEqual(test_record.activity_ids.user_id, test_user)
|
self.assertEqual(test_record.activity_ids.user_id, test_user)
|
||||||
self.assertEqual(test_record.activity_ids.date_deadline, fields.Date.to_string((now + timedelta(days=-2)).date()))
|
self.assertEqual(test_record.activity_ids.date_deadline, (now + timedelta(days=-2)).date())
|
||||||
|
|
||||||
# deleting meeting should delete its activity
|
# deleting meeting should delete its activity
|
||||||
test_record.activity_ids.unlink()
|
test_record.activity_ids.unlink()
|
||||||
@@ -268,8 +268,8 @@ class TestCalendar(TransactionCase):
|
|||||||
).create({
|
).create({
|
||||||
'name': test_name,
|
'name': test_name,
|
||||||
'description': test_description,
|
'description': test_description,
|
||||||
'start': fields.Datetime.to_string(now + timedelta(days=-1)),
|
'start': now + timedelta(days=-1),
|
||||||
'stop': fields.Datetime.to_string(now + timedelta(hours=2)),
|
'stop': now + timedelta(hours=2),
|
||||||
'user_id': self.env.user.id,
|
'user_id': self.env.user.id,
|
||||||
})
|
})
|
||||||
self.assertEqual(test_event.res_model, test_record._name)
|
self.assertEqual(test_event.res_model, test_record._name)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class AlarmManager(models.AbstractModel):
|
|||||||
""" Cron method, overriden here to send SMS reminders as well
|
""" Cron method, overriden here to send SMS reminders as well
|
||||||
"""
|
"""
|
||||||
result = super(AlarmManager, self).get_next_mail()
|
result = super(AlarmManager, self).get_next_mail()
|
||||||
now = fields.Datetime.now()
|
now = fields.Datetime.to_string(fields.Datetime.now())
|
||||||
last_sms_cron = self.env['ir.config_parameter'].get_param('calendar_sms.last_sms_cron', default=now)
|
last_sms_cron = self.env['ir.config_parameter'].get_param('calendar_sms.last_sms_cron', default=now)
|
||||||
cron = self.env['ir.model.data'].get_object('calendar', 'ir_cron_scheduler_alarm')
|
cron = self.env['ir.model.data'].get_object('calendar', 'ir_cron_scheduler_alarm')
|
||||||
|
|
||||||
|
|||||||
@@ -1054,7 +1054,7 @@ class Lead(models.Model):
|
|||||||
meetings = self.env['calendar.event'].search(meetings_domain)
|
meetings = self.env['calendar.event'].search(meetings_domain)
|
||||||
for meeting in meetings:
|
for meeting in meetings:
|
||||||
if meeting['start']:
|
if meeting['start']:
|
||||||
start = datetime.strptime(meeting['start'], tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
|
start = meeting['start']
|
||||||
if start == today:
|
if start == today:
|
||||||
result['meeting']['today'] += 1
|
result['meeting']['today'] += 1
|
||||||
if today <= start <= today + timedelta(days=7):
|
if today <= start <= today + timedelta(days=7):
|
||||||
|
|||||||
@@ -347,7 +347,7 @@ class EventRegistration(models.Model):
|
|||||||
partner_id = fields.Many2one(
|
partner_id = fields.Many2one(
|
||||||
'res.partner', string='Contact',
|
'res.partner', string='Contact',
|
||||||
states={'done': [('readonly', True)]})
|
states={'done': [('readonly', True)]})
|
||||||
date_open = fields.Datetime(string='Registration Date', readonly=True, default=lambda self: fields.datetime.now()) # weird crash is directly now
|
date_open = fields.Datetime(string='Registration Date', readonly=True, default=lambda self: fields.Datetime.now()) # weird crash is directly now
|
||||||
date_closed = fields.Datetime(string='Attended Date', readonly=True)
|
date_closed = fields.Datetime(string='Attended Date', readonly=True)
|
||||||
event_begin_date = fields.Datetime(string="Event Start Date", related='event_id.date_begin', readonly=True)
|
event_begin_date = fields.Datetime(string="Event Start Date", related='event_id.date_begin', readonly=True)
|
||||||
event_end_date = fields.Datetime(string="Event End Date", related='event_id.date_end', readonly=True)
|
event_end_date = fields.Datetime(string="Event End Date", related='event_id.date_end', readonly=True)
|
||||||
@@ -515,8 +515,8 @@ class EventRegistration(models.Model):
|
|||||||
@api.multi
|
@api.multi
|
||||||
def get_date_range_str(self):
|
def get_date_range_str(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
today = fields.Datetime.from_string(fields.Datetime.now())
|
today = fields.Datetime.now()
|
||||||
event_date = fields.Datetime.from_string(self.event_begin_date)
|
event_date = self.event_begin_date
|
||||||
diff = (event_date.date() - today.date())
|
diff = (event_date.date() - today.date())
|
||||||
if diff.days <= 0:
|
if diff.days <= 0:
|
||||||
return _('today')
|
return _('today')
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class EventMailScheduler(models.Model):
|
|||||||
else:
|
else:
|
||||||
date, sign = self.event_id.date_end, 1
|
date, sign = self.event_id.date_end, 1
|
||||||
|
|
||||||
self.scheduled_date = datetime.strptime(date, tools.DEFAULT_SERVER_DATETIME_FORMAT) + _INTERVALS[self.interval_unit](sign * self.interval_nbr)
|
self.scheduled_date = date + _INTERVALS[self.interval_unit](sign * self.interval_nbr)
|
||||||
|
|
||||||
@api.one
|
@api.one
|
||||||
def execute(self):
|
def execute(self):
|
||||||
@@ -190,7 +190,7 @@ class EventMailRegistration(models.Model):
|
|||||||
def _compute_scheduled_date(self):
|
def _compute_scheduled_date(self):
|
||||||
if self.registration_id:
|
if self.registration_id:
|
||||||
date_open = self.registration_id.date_open
|
date_open = self.registration_id.date_open
|
||||||
date_open_datetime = date_open and datetime.strptime(date_open, tools.DEFAULT_SERVER_DATETIME_FORMAT) or fields.datetime.now()
|
date_open_datetime = date_open or fields.Datetime.now()
|
||||||
self.scheduled_date = date_open_datetime + _INTERVALS[self.scheduler_id.interval_unit](self.scheduler_id.interval_nbr)
|
self.scheduled_date = date_open_datetime + _INTERVALS[self.scheduler_id.interval_unit](self.scheduler_id.interval_nbr)
|
||||||
else:
|
else:
|
||||||
self.scheduled_date = False
|
self.scheduled_date = False
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ class TestEventFlow(TestEventCommon):
|
|||||||
self.patcher = patch('odoo.addons.event.models.event.fields.Datetime', wraps=Datetime)
|
self.patcher = patch('odoo.addons.event.models.event.fields.Datetime', wraps=Datetime)
|
||||||
self.mock_datetime = self.patcher.start()
|
self.mock_datetime = self.patcher.start()
|
||||||
|
|
||||||
self.mock_datetime.now.return_value = Datetime.to_string(datetime.datetime(2015, 12, 31, 12, 0))
|
self.mock_datetime.now.return_value = datetime.datetime(2015, 12, 31, 12, 0)
|
||||||
|
|
||||||
self.event_0.registration_ids.event_begin_date = datetime.datetime(2015, 12, 31, 18, 0)
|
self.event_0.registration_ids.event_begin_date = datetime.datetime(2015, 12, 31, 18, 0)
|
||||||
self.assertEqual(self.event_0.registration_ids.get_date_range_str(), u'today')
|
self.assertEqual(self.event_0.registration_ids.get_date_range_str(), u'today')
|
||||||
@@ -131,7 +131,7 @@ class TestEventFlow(TestEventCommon):
|
|||||||
self.event_0.registration_ids.event_begin_date = datetime.datetime(2016, 1, 2, 6, 0)
|
self.event_0.registration_ids.event_begin_date = datetime.datetime(2016, 1, 2, 6, 0)
|
||||||
self.assertEqual(self.event_0.registration_ids.get_date_range_str(), u'in 2 days')
|
self.assertEqual(self.event_0.registration_ids.get_date_range_str(), u'in 2 days')
|
||||||
|
|
||||||
self.mock_datetime.now.return_value = Datetime.to_string(datetime.datetime(2015, 12, 10, 12, 0))
|
self.mock_datetime.now.return_value = datetime.datetime(2015, 12, 10, 12, 0)
|
||||||
self.event_0.registration_ids.event_begin_date = datetime.datetime(2016, 1, 25, 6, 0)
|
self.event_0.registration_ids.event_begin_date = datetime.datetime(2016, 1, 25, 6, 0)
|
||||||
self.assertEqual(self.event_0.registration_ids.get_date_range_str(), u'next month')
|
self.assertEqual(self.event_0.registration_ids.get_date_range_str(), u'next month')
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class TestMailSchedule(TestEventCommon):
|
|||||||
@mute_logger('odoo.addons.base.models.ir_model', 'odoo.models')
|
@mute_logger('odoo.addons.base.models.ir_model', 'odoo.models')
|
||||||
def test_00_event_mail_schedule(self):
|
def test_00_event_mail_schedule(self):
|
||||||
""" Test mail scheduling for events """
|
""" Test mail scheduling for events """
|
||||||
now = fields.datetime.now()
|
now = fields.Datetime.now()
|
||||||
event_date_begin = now + relativedelta(days=1)
|
event_date_begin = now + relativedelta(days=1)
|
||||||
event_date_end = now + relativedelta(days=3)
|
event_date_end = now + relativedelta(days=3)
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ class TestMailSchedule(TestEventCommon):
|
|||||||
# verify that subscription scheduler was auto-executed after each registration
|
# verify that subscription scheduler was auto-executed after each registration
|
||||||
self.assertEqual(len(schedulers[0].mail_registration_ids), 2, 'event: incorrect number of mail scheduled date')
|
self.assertEqual(len(schedulers[0].mail_registration_ids), 2, 'event: incorrect number of mail scheduled date')
|
||||||
|
|
||||||
mails = self.env['mail.mail'].search([('subject', 'ilike', 'registration'), ('date', '>=', datetime.datetime.strftime(now, tools.DEFAULT_SERVER_DATETIME_FORMAT))], order='date DESC', limit=3)
|
mails = self.env['mail.mail'].search([('subject', 'ilike', 'registration'), ('date', '>=', now)], order='date DESC', limit=3)
|
||||||
self.assertEqual(len(mails), 2, 'event: wrong number of registration mail sent')
|
self.assertEqual(len(mails), 2, 'event: wrong number of registration mail sent')
|
||||||
|
|
||||||
for registration in schedulers[0].mail_registration_ids:
|
for registration in schedulers[0].mail_registration_ids:
|
||||||
@@ -65,7 +65,7 @@ class TestMailSchedule(TestEventCommon):
|
|||||||
# check before event scheduler
|
# check before event scheduler
|
||||||
schedulers = self.EventMail.search([('event_id', '=', test_event.id), ('interval_type', '=', 'before_event')])
|
schedulers = self.EventMail.search([('event_id', '=', test_event.id), ('interval_type', '=', 'before_event')])
|
||||||
self.assertEqual(len(schedulers), 1, 'event: wrong scheduler creation')
|
self.assertEqual(len(schedulers), 1, 'event: wrong scheduler creation')
|
||||||
self.assertEqual(schedulers[0].scheduled_date, datetime.datetime.strftime(event_date_begin + relativedelta(days=-1), tools.DEFAULT_SERVER_DATETIME_FORMAT), 'event: incorrect scheduled date')
|
self.assertEqual(schedulers[0].scheduled_date, event_date_begin + relativedelta(days=-1), 'event: incorrect scheduled date')
|
||||||
|
|
||||||
# execute event reminder scheduler explicitly
|
# execute event reminder scheduler explicitly
|
||||||
schedulers[0].execute()
|
schedulers[0].execute()
|
||||||
@@ -73,7 +73,7 @@ class TestMailSchedule(TestEventCommon):
|
|||||||
self.assertTrue(schedulers[0].mail_sent, 'event: reminder scheduler should have sent an email')
|
self.assertTrue(schedulers[0].mail_sent, 'event: reminder scheduler should have sent an email')
|
||||||
self.assertTrue(schedulers[0].done, 'event: reminder scheduler should be done')
|
self.assertTrue(schedulers[0].done, 'event: reminder scheduler should be done')
|
||||||
|
|
||||||
mails = self.env['mail.mail'].search([('subject', 'ilike', 'TestEventMail'), ('date', '>=', datetime.datetime.strftime(now, tools.DEFAULT_SERVER_DATETIME_FORMAT))], order='date DESC', limit=3)
|
mails = self.env['mail.mail'].search([('subject', 'ilike', 'TestEventMail'), ('date', '>=', now)], order='date DESC', limit=3)
|
||||||
self.assertEqual(len(mails), 3, 'event: wrong number of reminders in outgoing mail queue')
|
self.assertEqual(len(mails), 3, 'event: wrong number of reminders in outgoing mail queue')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -299,9 +299,9 @@ class FleetVehicleOdometer(models.Model):
|
|||||||
for record in self:
|
for record in self:
|
||||||
name = record.vehicle_id.name
|
name = record.vehicle_id.name
|
||||||
if not name:
|
if not name:
|
||||||
name = record.date
|
name = str(record.date)
|
||||||
elif record.date:
|
elif record.date:
|
||||||
name += ' / ' + record.date
|
name += ' / ' + str(record.date)
|
||||||
record.name = name
|
record.name = name
|
||||||
|
|
||||||
@api.onchange('vehicle_id')
|
@api.onchange('vehicle_id')
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ class FleetVehicleLogContract(models.Model):
|
|||||||
if record.cost_subtype_id.name:
|
if record.cost_subtype_id.name:
|
||||||
name += ' / ' + record.cost_subtype_id.name
|
name += ' / ' + record.cost_subtype_id.name
|
||||||
if record.date:
|
if record.date:
|
||||||
name += ' / ' + record.date
|
name += ' / ' + str(record.date)
|
||||||
record.name = name
|
record.name = name
|
||||||
|
|
||||||
@api.depends('expiration_date', 'state')
|
@api.depends('expiration_date', 'state')
|
||||||
@@ -231,7 +231,7 @@ class FleetVehicleLogContract(models.Model):
|
|||||||
if not contract.start_date or contract.cost_frequency == 'no':
|
if not contract.start_date or contract.cost_frequency == 'no':
|
||||||
continue
|
continue
|
||||||
found = False
|
found = False
|
||||||
last_cost_date = contract.start_date
|
startdate = contract.start_date
|
||||||
if contract.generated_cost_ids:
|
if contract.generated_cost_ids:
|
||||||
last_autogenerated_cost = VehicleCost.search([
|
last_autogenerated_cost = VehicleCost.search([
|
||||||
('contract_id', '=', contract.id),
|
('contract_id', '=', contract.id),
|
||||||
@@ -239,12 +239,11 @@ class FleetVehicleLogContract(models.Model):
|
|||||||
], offset=0, limit=1, order='date desc')
|
], offset=0, limit=1, order='date desc')
|
||||||
if last_autogenerated_cost:
|
if last_autogenerated_cost:
|
||||||
found = True
|
found = True
|
||||||
last_cost_date = last_autogenerated_cost.date
|
startdate = last_autogenerated_cost.date.date()
|
||||||
startdate = fields.Date.from_string(last_cost_date)
|
|
||||||
if found:
|
if found:
|
||||||
startdate += deltas.get(contract.cost_frequency)
|
startdate += deltas.get(contract.cost_frequency)
|
||||||
today = fields.Date.from_string(fields.Date.context_today(self))
|
today = fields.Date.context_today(self)
|
||||||
while (startdate <= today) & (startdate <= fields.Date.from_string(contract.expiration_date)):
|
while (startdate <= today) & (startdate <= contract.expiration_date):
|
||||||
data = {
|
data = {
|
||||||
'amount': contract.cost_generated,
|
'amount': contract.cost_generated,
|
||||||
'date': fields.Date.context_today(self),
|
'date': fields.Date.context_today(self),
|
||||||
|
|||||||
@@ -141,11 +141,11 @@ class Challenge(models.Model):
|
|||||||
report period.
|
report period.
|
||||||
"""
|
"""
|
||||||
for challenge in self:
|
for challenge in self:
|
||||||
last = fields.Datetime.from_string(challenge.last_report_date).date()
|
last = challenge.last_report_date
|
||||||
offset = self.REPORT_OFFSETS.get(challenge.report_message_frequency)
|
offset = self.REPORT_OFFSETS.get(challenge.report_message_frequency)
|
||||||
|
|
||||||
if offset:
|
if offset:
|
||||||
challenge.next_report_date = fields.Date.to_string(last + offset)
|
challenge.next_report_date = last + offset
|
||||||
else:
|
else:
|
||||||
challenge.next_report_date = False
|
challenge.next_report_date = False
|
||||||
|
|
||||||
|
|||||||
@@ -193,12 +193,12 @@ class GoogleCalendar(models.AbstractModel):
|
|||||||
def generate_data(self, event, isCreating=False):
|
def generate_data(self, event, isCreating=False):
|
||||||
if event.allday:
|
if event.allday:
|
||||||
start_date = event.start_date
|
start_date = event.start_date
|
||||||
final_date = (datetime.strptime(event.stop_date, tools.DEFAULT_SERVER_DATE_FORMAT) + timedelta(days=1)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
|
final_date = event.stop_date + timedelta(days=1)
|
||||||
type = 'date'
|
type = 'date'
|
||||||
vstype = 'dateTime'
|
vstype = 'dateTime'
|
||||||
else:
|
else:
|
||||||
start_date = fields.Datetime.context_timestamp(self, fields.Datetime.from_string(event.start)).isoformat('T')
|
start_date = fields.Datetime.context_timestamp(self, event.start).isoformat('T')
|
||||||
final_date = fields.Datetime.context_timestamp(self, fields.Datetime.from_string(event.stop)).isoformat('T')
|
final_date = fields.Datetime.context_timestamp(self, event.stop).isoformat('T')
|
||||||
type = 'dateTime'
|
type = 'dateTime'
|
||||||
vstype = 'date'
|
vstype = 'date'
|
||||||
attendee_list = []
|
attendee_list = []
|
||||||
@@ -852,9 +852,9 @@ class GoogleCalendar(models.AbstractModel):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def check_and_sync(self, oe_event, google_event):
|
def check_and_sync(self, oe_event, google_event):
|
||||||
if datetime.strptime(oe_event.oe_update_date, "%Y-%m-%d %H:%M:%S.%f") > datetime.strptime(google_event['updated'], "%Y-%m-%dT%H:%M:%S.%fz"):
|
if oe_event.oe_update_date > datetime.strptime(google_event['updated'], "%Y-%m-%dT%H:%M:%S.%fz"):
|
||||||
self.update_to_google(oe_event, google_event)
|
self.update_to_google(oe_event, google_event)
|
||||||
elif datetime.strptime(oe_event.oe_update_date, "%Y-%m-%d %H:%M:%S.%f") < datetime.strptime(google_event['updated'], "%Y-%m-%dT%H:%M:%S.%fz"):
|
elif oe_event.oe_update_date < datetime.strptime(google_event['updated'], "%Y-%m-%dT%H:%M:%S.%fz"):
|
||||||
self.update_from_google(oe_event, google_event, 'write')
|
self.update_from_google(oe_event, google_event, 'write')
|
||||||
|
|
||||||
def get_sequence(self, instance_id):
|
def get_sequence(self, instance_id):
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from odoo import models, fields, api, exceptions, _
|
from odoo import models, fields, api, exceptions, _
|
||||||
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
|
||||||
|
|
||||||
|
|
||||||
class HrAttendance(models.Model):
|
class HrAttendance(models.Model):
|
||||||
@@ -43,8 +40,7 @@ class HrAttendance(models.Model):
|
|||||||
def _compute_worked_hours(self):
|
def _compute_worked_hours(self):
|
||||||
for attendance in self:
|
for attendance in self:
|
||||||
if attendance.check_out:
|
if attendance.check_out:
|
||||||
delta = datetime.strptime(attendance.check_out, DEFAULT_SERVER_DATETIME_FORMAT) - datetime.strptime(
|
delta = attendance.check_out - attendance.check_in
|
||||||
attendance.check_in, DEFAULT_SERVER_DATETIME_FORMAT)
|
|
||||||
attendance.worked_hours = delta.total_seconds() / 3600.0
|
attendance.worked_hours = delta.total_seconds() / 3600.0
|
||||||
|
|
||||||
@api.constrains('check_in', 'check_out')
|
@api.constrains('check_in', 'check_out')
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
from datetime import datetime
|
from datetime import date, datetime
|
||||||
from odoo.tests.common import TransactionCase
|
from odoo.tests.common import TransactionCase
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
@@ -37,13 +37,13 @@ class TestHrContracts(TransactionCase):
|
|||||||
self.assertEquals(self.contract.state, 'close')
|
self.assertEquals(self.contract.state, 'close')
|
||||||
|
|
||||||
def test_contract_pending_visa_expire(self):
|
def test_contract_pending_visa_expire(self):
|
||||||
self.employee.visa_expire = datetime.now() + relativedelta(days=30)
|
self.employee.visa_expire = date.today() + relativedelta(days=30)
|
||||||
self.test_contract.update(dict(date_end=False))
|
self.test_contract.update(dict(date_end=False))
|
||||||
self.contract = self.contracts.create(self.test_contract)
|
self.contract = self.contracts.create(self.test_contract)
|
||||||
self.apply_cron()
|
self.apply_cron()
|
||||||
self.assertEquals(self.contract.state, 'pending')
|
self.assertEquals(self.contract.state, 'pending')
|
||||||
|
|
||||||
self.employee.visa_expire = datetime.now() + relativedelta(days=-5)
|
self.employee.visa_expire = date.today() + relativedelta(days=-5)
|
||||||
self.test_contract.update({
|
self.test_contract.update({
|
||||||
'date_start': datetime.now() + relativedelta(days=-50),
|
'date_start': datetime.now() + relativedelta(days=-50),
|
||||||
'state': 'pending',
|
'state': 'pending',
|
||||||
|
|||||||
@@ -376,12 +376,12 @@ class HolidaysRequest(models.Model):
|
|||||||
def _check_leave_type_validity(self):
|
def _check_leave_type_validity(self):
|
||||||
for leave in self:
|
for leave in self:
|
||||||
if leave.holiday_status_id.validity_start and leave.holiday_status_id.validity_stop:
|
if leave.holiday_status_id.validity_start and leave.holiday_status_id.validity_stop:
|
||||||
vstart = fields.Datetime.from_string(leave.holiday_status_id.validity_start)
|
vstart = leave.holiday_status_id.validity_start
|
||||||
vstop = fields.Datetime.from_string(leave.holiday_status_id.validity_stop)
|
vstop = leave.holiday_status_id.validity_stop
|
||||||
dfrom = fields.Datetime.from_string(leave.date_from)
|
dfrom = leave.date_from
|
||||||
dto = fields.Datetime.from_string(leave.date_to)
|
dto = leave.date_to
|
||||||
|
|
||||||
if dfrom and dto and (dfrom < vstart or dto > vstop):
|
if dfrom and dto and (dfrom.date() < vstart or dto.date() > vstop):
|
||||||
raise UserError(_('You can take %s only between %s and %s') % (leave.holiday_status_id.display_name, \
|
raise UserError(_('You can take %s only between %s and %s') % (leave.holiday_status_id.display_name, \
|
||||||
leave.holiday_status_id.validity_start, leave.holiday_status_id.validity_stop))
|
leave.holiday_status_id.validity_start, leave.holiday_status_id.validity_stop))
|
||||||
|
|
||||||
|
|||||||
@@ -288,9 +288,9 @@ class HolidaysAllocation(models.Model):
|
|||||||
def _check_leave_type_validity(self):
|
def _check_leave_type_validity(self):
|
||||||
for allocation in self:
|
for allocation in self:
|
||||||
if allocation.holiday_status_id.validity_start and allocation.holiday_status_id.validity_stop:
|
if allocation.holiday_status_id.validity_start and allocation.holiday_status_id.validity_stop:
|
||||||
vstart = fields.Datetime.from_string(allocation.holiday_status_id.validity_start)
|
vstart = allocation.holiday_status_id.validity_start
|
||||||
vstop = fields.Datetime.from_string(allocation.holiday_status_id.validity_stop)
|
vstop = allocation.holiday_status_id.validity_stop
|
||||||
today = fields.Datetime.from_string(fields.Datetime.now())
|
today = fields.Date.today()
|
||||||
|
|
||||||
if vstart > today or vstop < today:
|
if vstart > today or vstop < today:
|
||||||
raise UserError(_('You can allocate %s only between %s and %s') % (allocation.holiday_status_id.display_name,
|
raise UserError(_('You can allocate %s only between %s and %s') % (allocation.holiday_status_id.display_name,
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class TestAccrualAllocations(TestHrHolidaysBase):
|
|||||||
|
|
||||||
employee = self.env['hr.employee'].browse(self.employee_hruser_id)
|
employee = self.env['hr.employee'].browse(self.employee_hruser_id)
|
||||||
# Getting the previous work date
|
# Getting the previous work date
|
||||||
df = employee.resource_calendar_id.plan_days(-2, fields.Datetime.from_string(fields.Datetime.now())).date()
|
df = employee.resource_calendar_id.plan_days(-2, fields.Datetime.now()).date()
|
||||||
|
|
||||||
leave_0 = Leave.create({
|
leave_0 = Leave.create({
|
||||||
'name': 'Leave for hruser',
|
'name': 'Leave for hruser',
|
||||||
@@ -173,7 +173,7 @@ class TestAccrualAllocations(TestHrHolidaysBase):
|
|||||||
"""
|
"""
|
||||||
Allocation = self.env['hr.leave.allocation'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True)
|
Allocation = self.env['hr.leave.allocation'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True)
|
||||||
|
|
||||||
self.set_employee_create_date(self.employee_emp_id, fields.Datetime.now())
|
self.set_employee_create_date(self.employee_emp_id, fields.Datetime.to_string(fields.Datetime.now()))
|
||||||
|
|
||||||
alloc_0 = Allocation.create({
|
alloc_0 = Allocation.create({
|
||||||
'name': 'one shot one kill',
|
'name': 'one shot one kill',
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from datetime import datetime
|
from odoo import api, fields, models, SUPERUSER_ID
|
||||||
|
|
||||||
from odoo import api, fields, models, tools, SUPERUSER_ID
|
|
||||||
from odoo.tools.translate import _
|
from odoo.tools.translate import _
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
@@ -176,13 +174,13 @@ class Applicant(models.Model):
|
|||||||
@api.one
|
@api.one
|
||||||
def _compute_day(self):
|
def _compute_day(self):
|
||||||
if self.date_open:
|
if self.date_open:
|
||||||
date_create = datetime.strptime(self.create_date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
date_create = self.create_date
|
||||||
date_open = datetime.strptime(self.date_open, tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
date_open = self.date_open
|
||||||
self.day_open = (date_open - date_create).total_seconds() / (24.0 * 3600)
|
self.day_open = (date_open - date_create).total_seconds() / (24.0 * 3600)
|
||||||
|
|
||||||
if self.date_closed:
|
if self.date_closed:
|
||||||
date_create = datetime.strptime(self.create_date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
date_create = self.create_date
|
||||||
date_closed = datetime.strptime(self.date_closed, tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
date_closed = self.date_closed
|
||||||
self.day_close = (date_closed - date_create).total_seconds() / (24.0 * 3600)
|
self.day_close = (date_closed - date_create).total_seconds() / (24.0 * 3600)
|
||||||
self.delay_close = self.day_close - self.day_open
|
self.delay_close = self.day_close - self.day_open
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import babel.dates
|
|||||||
|
|
||||||
from odoo import api, fields, models
|
from odoo import api, fields, models
|
||||||
|
|
||||||
from odoo.fields import Datetime
|
from odoo.fields import Datetime, Date
|
||||||
|
|
||||||
|
|
||||||
class FleetVehicle(models.Model):
|
class FleetVehicle(models.Model):
|
||||||
@@ -80,17 +80,18 @@ class FleetVehicle(models.Model):
|
|||||||
def _get_acquisition_date(self):
|
def _get_acquisition_date(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
return babel.dates.format_date(
|
return babel.dates.format_date(
|
||||||
date=Datetime.from_string(self.acquisition_date),
|
date=self.acquisition_date,
|
||||||
format='MMMM y',
|
format='MMMM y',
|
||||||
locale=self._context.get('lang') or 'en_US'
|
locale=self._context.get('lang') or 'en_US'
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_car_atn(self, acquisition_date, car_value, fuel_type, co2):
|
def _get_car_atn(self, acquisition_date, car_value, fuel_type, co2):
|
||||||
# Compute the correction coefficient from the age of the car
|
# Compute the correction coefficient from the age of the car
|
||||||
now = Datetime.from_string(Datetime.now())
|
now = Date.today()
|
||||||
start = Datetime.from_string(acquisition_date)
|
if acquisition_date:
|
||||||
if start:
|
number_of_month = ((now.year - acquisition_date.year) * 12.0 + now.month -
|
||||||
number_of_month = (now.year - start.year) * 12.0 + now.month - start.month + int(bool(now.day - start.day + 1))
|
acquisition_date.month +
|
||||||
|
int(bool(now.day - acquisition_date.day + 1)))
|
||||||
if number_of_month <= 12:
|
if number_of_month <= 12:
|
||||||
age_coefficient = 1.00
|
age_coefficient = 1.00
|
||||||
elif number_of_month <= 24:
|
elif number_of_month <= 24:
|
||||||
|
|||||||
@@ -5,11 +5,10 @@
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
import io
|
import io
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from odoo import api, fields, models, _
|
from odoo import api, fields, models, _
|
||||||
from odoo.exceptions import Warning
|
from odoo.exceptions import Warning
|
||||||
from odoo.tools import pycompat, DEFAULT_SERVER_DATE_FORMAT
|
from odoo.tools import pycompat
|
||||||
|
|
||||||
|
|
||||||
class AccountFrFec(models.TransientModel):
|
class AccountFrFec(models.TransientModel):
|
||||||
@@ -68,7 +67,7 @@ class AccountFrFec(models.TransientModel):
|
|||||||
'''
|
'''
|
||||||
company = self.env.user.company_id
|
company = self.env.user.company_id
|
||||||
formatted_date_from = self.date_from.replace('-', '')
|
formatted_date_from = self.date_from.replace('-', '')
|
||||||
date_from = datetime.strptime(self.date_from, DEFAULT_SERVER_DATE_FORMAT)
|
date_from = self.date_from
|
||||||
formatted_date_year = date_from.year
|
formatted_date_year = date_from.year
|
||||||
self._cr.execute(
|
self._cr.execute(
|
||||||
sql_query, (formatted_date_year, formatted_date_from, formatted_date_from, formatted_date_from, self.date_from, company.id))
|
sql_query, (formatted_date_year, formatted_date_from, formatted_date_from, formatted_date_from, self.date_from, company.id))
|
||||||
@@ -174,7 +173,7 @@ class AccountFrFec(models.TransientModel):
|
|||||||
AND aat.type not in ('receivable', 'payable')
|
AND aat.type not in ('receivable', 'payable')
|
||||||
'''
|
'''
|
||||||
formatted_date_from = self.date_from.replace('-', '')
|
formatted_date_from = self.date_from.replace('-', '')
|
||||||
date_from = datetime.strptime(self.date_from, DEFAULT_SERVER_DATE_FORMAT)
|
date_from = self.date_from
|
||||||
formatted_date_year = date_from.year
|
formatted_date_year = date_from.year
|
||||||
self._cr.execute(
|
self._cr.execute(
|
||||||
sql_query, (formatted_date_year, formatted_date_from, formatted_date_from, formatted_date_from, self.date_from, company.id))
|
sql_query, (formatted_date_year, formatted_date_from, formatted_date_from, formatted_date_from, self.date_from, company.id))
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from odoo import api, models
|
from odoo import api, models
|
||||||
|
|
||||||
@@ -16,8 +15,8 @@ class payroll_advice_report(models.AbstractModel):
|
|||||||
}
|
}
|
||||||
slip = self.env['hr.payslip'].search([('date_from', '<=', input_date), ('date_to', '>=', input_date)], limit=1)
|
slip = self.env['hr.payslip'].search([('date_from', '<=', input_date), ('date_to', '>=', input_date)], limit=1)
|
||||||
if slip:
|
if slip:
|
||||||
from_date = datetime.strptime(slip.date_from, '%Y-%m-%d')
|
from_date = slip.date_from
|
||||||
to_date = datetime.strptime(slip.date_to, '%Y-%m-%d')
|
to_date = slip.date_to
|
||||||
res['from_name'] = from_date.strftime('%d') + '-' + from_date.strftime('%B') + '-' + from_date.strftime('%Y')
|
res['from_name'] = from_date.strftime('%d') + '-' + from_date.strftime('%B') + '-' + from_date.strftime('%Y')
|
||||||
res['to_name'] = to_date.strftime('%d') + '-' + to_date.strftime('%B') + '-' + to_date.strftime('%Y')
|
res['to_name'] = to_date.strftime('%d') + '-' + to_date.strftime('%B') + '-' + to_date.strftime('%Y')
|
||||||
return res
|
return res
|
||||||
|
|||||||
@@ -123,9 +123,9 @@ class LunchOrder(models.Model):
|
|||||||
"""
|
"""
|
||||||
Prevents the user to create an order in the past
|
Prevents the user to create an order in the past
|
||||||
"""
|
"""
|
||||||
date_order = datetime.datetime.strptime(self.date, '%Y-%m-%d')
|
date_order = self.date
|
||||||
date_today = datetime.datetime.strptime(fields.Date.context_today(self), '%Y-%m-%d')
|
date_today = fields.Date.context_today(self)
|
||||||
if (date_order < date_today):
|
if date_order < date_today:
|
||||||
raise ValidationError(_('The date of your order is in the past.'))
|
raise ValidationError(_('The date of your order is in the past.'))
|
||||||
|
|
||||||
@api.one
|
@api.one
|
||||||
|
|||||||
@@ -172,7 +172,9 @@ class MailActivity(models.Model):
|
|||||||
def _onchange_activity_type_id(self):
|
def _onchange_activity_type_id(self):
|
||||||
if self.activity_type_id:
|
if self.activity_type_id:
|
||||||
self.summary = self.activity_type_id.summary
|
self.summary = self.activity_type_id.summary
|
||||||
self.date_deadline = (datetime.now() + timedelta(days=self.activity_type_id.days))
|
# Date.context_today is correct because date_deadline is a Date and is meant to be
|
||||||
|
# expressed in user TZ
|
||||||
|
self.date_deadline = fields.Date.context_today(self) + timedelta(days=self.activity_type_id.days)
|
||||||
|
|
||||||
@api.onchange('recommended_activity_type_id')
|
@api.onchange('recommended_activity_type_id')
|
||||||
def _onchange_recommended_activity_type_id(self):
|
def _onchange_recommended_activity_type_id(self):
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ def format_date(env, date, pattern=False):
|
|||||||
|
|
||||||
def format_tz(env, dt, tz=False, format=False):
|
def format_tz(env, dt, tz=False, format=False):
|
||||||
record_user_timestamp = env.user.sudo().with_context(tz=tz or env.user.sudo().tz or 'UTC')
|
record_user_timestamp = env.user.sudo().with_context(tz=tz or env.user.sudo().tz or 'UTC')
|
||||||
timestamp = datetime.datetime.strptime(dt, tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
timestamp = fields.Datetime.from_string(dt)
|
||||||
|
|
||||||
ts = fields.Datetime.context_timestamp(record_user_timestamp, timestamp)
|
ts = fields.Datetime.context_timestamp(record_user_timestamp, timestamp)
|
||||||
|
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ class MailTracking(models.Model):
|
|||||||
})
|
})
|
||||||
elif col_info['type'] == 'date':
|
elif col_info['type'] == 'date':
|
||||||
values.update({
|
values.update({
|
||||||
'old_value_datetime': initial_value and datetime.strftime(datetime.combine(datetime.strptime(initial_value, tools.DEFAULT_SERVER_DATE_FORMAT), datetime.min.time()), tools.DEFAULT_SERVER_DATETIME_FORMAT) or False,
|
'old_value_datetime': initial_value and fields.Datetime.to_string(datetime.combine(fields.Date.from_string(initial_value), datetime.min.time())) or False,
|
||||||
'new_value_datetime': new_value and datetime.strftime(datetime.combine(datetime.strptime(new_value, tools.DEFAULT_SERVER_DATE_FORMAT), datetime.min.time()), tools.DEFAULT_SERVER_DATETIME_FORMAT) or False,
|
'new_value_datetime': new_value and fields.Datetime.to_string(datetime.combine(fields.Date.from_string(new_value), datetime.min.time())) or False,
|
||||||
})
|
})
|
||||||
elif col_info['type'] == 'boolean':
|
elif col_info['type'] == 'boolean':
|
||||||
values.update({
|
values.update({
|
||||||
@@ -88,8 +88,8 @@ class MailTracking(models.Model):
|
|||||||
result.append(record['%s_value_datetime' % type])
|
result.append(record['%s_value_datetime' % type])
|
||||||
elif record.field_type == 'date':
|
elif record.field_type == 'date':
|
||||||
if record['%s_value_datetime' % type]:
|
if record['%s_value_datetime' % type]:
|
||||||
new_date = datetime.strptime(record['%s_value_datetime' % type], tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
|
new_date = record['%s_value_datetime' % type]
|
||||||
result.append(new_date.strftime(tools.DEFAULT_SERVER_DATE_FORMAT))
|
result.append(fields.Date.to_string(new_date))
|
||||||
else:
|
else:
|
||||||
result.append(record['%s_value_datetime' % type])
|
result.append(record['%s_value_datetime' % type])
|
||||||
elif record.field_type == 'boolean':
|
elif record.field_type == 'boolean':
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
from odoo import api, fields, models
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
|
||||||
class Invoice(models.Model):
|
class Invoice(models.Model):
|
||||||
_inherit = 'account.invoice'
|
_inherit = 'account.invoice'
|
||||||
@@ -36,7 +38,8 @@ class AccountInvoiceLine(models.Model):
|
|||||||
# Product line has changed to a membership product
|
# Product line has changed to a membership product
|
||||||
date_from = line.product_id.membership_date_from
|
date_from = line.product_id.membership_date_from
|
||||||
date_to = line.product_id.membership_date_to
|
date_to = line.product_id.membership_date_to
|
||||||
if line.invoice_id.date_invoice > (date_from or '0000-00-00') and line.invoice_id.date_invoice < (date_to or '0000-00-00'):
|
if (line.invoice_id.date_invoice > (date_from or date.min) and
|
||||||
|
line.invoice_id.date_invoice < (date_to or date.min)):
|
||||||
date_from = line.invoice_id.date_invoice
|
date_from = line.invoice_id.date_invoice
|
||||||
MemberLine.create({
|
MemberLine.create({
|
||||||
'partner': line.invoice_id.partner_id.id,
|
'partner': line.invoice_id.partner_id.id,
|
||||||
@@ -62,7 +65,10 @@ class AccountInvoiceLine(models.Model):
|
|||||||
# Product line is a membership product
|
# Product line is a membership product
|
||||||
date_from = invoice_line.product_id.membership_date_from
|
date_from = invoice_line.product_id.membership_date_from
|
||||||
date_to = invoice_line.product_id.membership_date_to
|
date_to = invoice_line.product_id.membership_date_to
|
||||||
if date_from and date_from < (invoice_line.invoice_id.date_invoice or '0000-00-00') < (date_to or '0000-00-00'):
|
if (date_from and
|
||||||
|
date_from <
|
||||||
|
(invoice_line.invoice_id.date_invoice or date.min) <
|
||||||
|
(date_to or date.min)):
|
||||||
date_from = invoice_line.invoice_id.date_invoice
|
date_from = invoice_line.invoice_id.date_invoice
|
||||||
MemberLine.create({
|
MemberLine.create({
|
||||||
'partner': invoice_line.invoice_id.partner_id.id,
|
'partner': invoice_line.invoice_id.partner_id.id,
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class TestMrpOrder(TestMrpCommon):
|
|||||||
})
|
})
|
||||||
inventory.action_validate()
|
inventory.action_validate()
|
||||||
|
|
||||||
test_date_planned = datetime.now() - timedelta(days=1)
|
test_date_planned = Dt.now() - timedelta(days=1)
|
||||||
test_quantity = 2.0
|
test_quantity = 2.0
|
||||||
self.bom_1.routing_id = False
|
self.bom_1.routing_id = False
|
||||||
man_order = self.env['mrp.production'].sudo(self.user_mrp_user).create({
|
man_order = self.env['mrp.production'].sudo(self.user_mrp_user).create({
|
||||||
@@ -76,7 +76,7 @@ class TestMrpOrder(TestMrpCommon):
|
|||||||
|
|
||||||
# check production move
|
# check production move
|
||||||
production_move = man_order.move_finished_ids
|
production_move = man_order.move_finished_ids
|
||||||
self.assertEqual(production_move.date, Dt.to_string(test_date_planned))
|
self.assertEqual(production_move.date, test_date_planned)
|
||||||
self.assertEqual(production_move.product_id, self.product_4)
|
self.assertEqual(production_move.product_id, self.product_4)
|
||||||
self.assertEqual(production_move.product_uom, man_order.product_uom_id)
|
self.assertEqual(production_move.product_uom, man_order.product_uom_id)
|
||||||
self.assertEqual(production_move.product_qty, man_order.product_qty)
|
self.assertEqual(production_move.product_qty, man_order.product_qty)
|
||||||
@@ -85,7 +85,7 @@ class TestMrpOrder(TestMrpCommon):
|
|||||||
|
|
||||||
# check consumption moves
|
# check consumption moves
|
||||||
for move in man_order.move_raw_ids:
|
for move in man_order.move_raw_ids:
|
||||||
self.assertEqual(move.date, Dt.to_string(test_date_planned))
|
self.assertEqual(move.date, test_date_planned)
|
||||||
first_move = man_order.move_raw_ids.filtered(lambda move: move.product_id == self.product_2)
|
first_move = man_order.move_raw_ids.filtered(lambda move: move.product_id == self.product_2)
|
||||||
self.assertEqual(first_move.product_qty, test_quantity / self.bom_1.product_qty * self.product_4.uom_id.factor_inv * 2)
|
self.assertEqual(first_move.product_qty, test_quantity / self.bom_1.product_qty * self.product_4.uom_id.factor_inv * 2)
|
||||||
first_move = man_order.move_raw_ids.filtered(lambda move: move.product_id == self.product_1)
|
first_move = man_order.move_raw_ids.filtered(lambda move: move.product_id == self.product_1)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ from uuid import uuid4
|
|||||||
|
|
||||||
from odoo import api, fields, models, _
|
from odoo import api, fields, models, _
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT
|
|
||||||
|
|
||||||
|
|
||||||
class AccountCashboxLine(models.Model):
|
class AccountCashboxLine(models.Model):
|
||||||
@@ -209,7 +208,7 @@ class PosConfig(models.Model):
|
|||||||
pos_config.pos_session_username = session[0].user_id.name
|
pos_config.pos_session_username = session[0].user_id.name
|
||||||
pos_config.pos_session_state = session[0].state
|
pos_config.pos_session_state = session[0].state
|
||||||
pos_config.pos_session_duration = (
|
pos_config.pos_session_duration = (
|
||||||
datetime.now() - datetime.strptime(session[0].start_at, DATETIME_FORMAT)
|
datetime.now() - session[0].start_at
|
||||||
).days if session[0].start_at else 0
|
).days if session[0].start_at else 0
|
||||||
else:
|
else:
|
||||||
pos_config.pos_session_username = False
|
pos_config.pos_session_username = False
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class PosMakePayment(models.TransientModel):
|
|||||||
journal_id = fields.Many2one('account.journal', string='Payment Mode', required=True, default=_default_journal)
|
journal_id = fields.Many2one('account.journal', string='Payment Mode', required=True, default=_default_journal)
|
||||||
amount = fields.Float(digits=(16, 2), required=True, default=_default_amount)
|
amount = fields.Float(digits=(16, 2), required=True, default=_default_amount)
|
||||||
payment_name = fields.Char(string='Payment Reference')
|
payment_name = fields.Char(string='Payment Reference')
|
||||||
payment_date = fields.Date(string='Payment Date', required=True, default=lambda *a: fields.Datetime.now())
|
payment_date = fields.Date(string='Payment Date', required=True, default=lambda *a: fields.Date.today())
|
||||||
|
|
||||||
@api.onchange('session_id')
|
@api.onchange('session_id')
|
||||||
def _on_change_session(self):
|
def _on_change_session(self):
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ class PurchaseOrder(models.Model):
|
|||||||
for line in new_po.order_line:
|
for line in new_po.order_line:
|
||||||
seller = line.product_id._select_seller(
|
seller = line.product_id._select_seller(
|
||||||
partner_id=line.partner_id, quantity=line.product_qty,
|
partner_id=line.partner_id, quantity=line.product_qty,
|
||||||
date=line.order_id.date_order and line.order_id.date_order[:10], uom_id=line.product_uom)
|
date=line.order_id.date_order and line.order_id.date_order.date(), uom_id=line.product_uom)
|
||||||
line.date_planned = line._get_date_planned(seller)
|
line.date_planned = line._get_date_planned(seller)
|
||||||
return new_po
|
return new_po
|
||||||
|
|
||||||
@@ -526,7 +526,7 @@ class PurchaseOrderLine(models.Model):
|
|||||||
"""
|
"""
|
||||||
date_order = po.date_order if po else self.order_id.date_order
|
date_order = po.date_order if po else self.order_id.date_order
|
||||||
if date_order:
|
if date_order:
|
||||||
return datetime.strptime(date_order, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=seller.delay if seller else 0)
|
return date_order + relativedelta(days=seller.delay if seller else 0)
|
||||||
else:
|
else:
|
||||||
return datetime.today() + relativedelta(days=seller.delay if seller else 0)
|
return datetime.today() + relativedelta(days=seller.delay if seller else 0)
|
||||||
|
|
||||||
@@ -590,7 +590,7 @@ class PurchaseOrderLine(models.Model):
|
|||||||
seller = self.product_id._select_seller(
|
seller = self.product_id._select_seller(
|
||||||
partner_id=self.partner_id,
|
partner_id=self.partner_id,
|
||||||
quantity=self.product_qty,
|
quantity=self.product_qty,
|
||||||
date=self.order_id.date_order and self.order_id.date_order[:10],
|
date=self.order_id.date_order and self.order_id.date_order.date(),
|
||||||
uom_id=self.product_uom,
|
uom_id=self.product_uom,
|
||||||
params=params)
|
params=params)
|
||||||
|
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ class PurchaseRequisitionLine(models.Model):
|
|||||||
'product_qty': product_qty,
|
'product_qty': product_qty,
|
||||||
'price_unit': price_unit,
|
'price_unit': price_unit,
|
||||||
'taxes_id': [(6, 0, taxes_ids)],
|
'taxes_id': [(6, 0, taxes_ids)],
|
||||||
'date_planned': requisition.schedule_date or fields.Date.today(),
|
'date_planned': requisition.schedule_date or fields.Datetime.now(),
|
||||||
'account_analytic_id': self.account_analytic_id.id,
|
'account_analytic_id': self.account_analytic_id.id,
|
||||||
'analytic_tag_ids': self.analytic_tag_ids.ids,
|
'analytic_tag_ids': self.analytic_tag_ids.ids,
|
||||||
'move_dest_ids': self.move_dest_id and [(4, self.move_dest_id.id)] or []
|
'move_dest_ids': self.move_dest_id and [(4, self.move_dest_id.id)] or []
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class StockRule(models.Model):
|
|||||||
seller = product_id._select_seller(
|
seller = product_id._select_seller(
|
||||||
partner_id=partner,
|
partner_id=partner,
|
||||||
quantity=line.product_qty + procurement_uom_po_qty,
|
quantity=line.product_qty + procurement_uom_po_qty,
|
||||||
date=line.order_id.date_order and line.order_id.date_order[:10],
|
date=line.order_id.date_order and line.order_id.date_order.date(),
|
||||||
uom_id=product_id.uom_po_id)
|
uom_id=product_id.uom_po_id)
|
||||||
|
|
||||||
price_unit = self.env['account.tax']._fix_tax_included_price_company(seller.price, line.product_id.supplier_taxes_id, line.taxes_id, values['company_id']) if seller else 0.0
|
price_unit = self.env['account.tax']._fix_tax_included_price_company(seller.price, line.product_id.supplier_taxes_id, line.taxes_id, values['company_id']) if seller else 0.0
|
||||||
@@ -118,7 +118,7 @@ class StockRule(models.Model):
|
|||||||
seller = product_id._select_seller(
|
seller = product_id._select_seller(
|
||||||
partner_id=partner,
|
partner_id=partner,
|
||||||
quantity=procurement_uom_po_qty,
|
quantity=procurement_uom_po_qty,
|
||||||
date=po.date_order and po.date_order[:10],
|
date=po.date_order and po.date_order.date(),
|
||||||
uom_id=product_id.uom_po_id)
|
uom_id=product_id.uom_po_id)
|
||||||
|
|
||||||
taxes = product_id.supplier_taxes_id
|
taxes = product_id.supplier_taxes_id
|
||||||
|
|||||||
@@ -29,19 +29,17 @@ class TestPurchaseLeadTime(TestPurchase):
|
|||||||
|
|
||||||
# Check order date of purchase order
|
# Check order date of purchase order
|
||||||
order_date = fields.Datetime.from_string(date_planned) - timedelta(days=company.po_lead) - timedelta(days=self.product_1.seller_ids.delay)
|
order_date = fields.Datetime.from_string(date_planned) - timedelta(days=company.po_lead) - timedelta(days=self.product_1.seller_ids.delay)
|
||||||
po_order_date = fields.Datetime.to_string(order_date)
|
self.assertEqual(purchase.date_order, order_date, 'Order date should be equal to: Date of the procurement order - Purchase Lead Time - Delivery Lead Time.')
|
||||||
self.assertEqual(purchase.date_order, po_order_date, 'Order date should be equal to: Date of the procurement order - Purchase Lead Time - Delivery Lead Time.')
|
|
||||||
|
|
||||||
# Check scheduled date of purchase order
|
# Check scheduled date of purchase order
|
||||||
schedule_date = order_date + timedelta(days=self.product_1.seller_ids.delay)
|
schedule_date = order_date + timedelta(days=self.product_1.seller_ids.delay)
|
||||||
po_schedule_date = fields.Datetime.to_string(schedule_date)
|
self.assertEqual(purchase.date_planned, schedule_date, 'Schedule date should be equal to: Order date of Purchase order + Delivery Lead Time.')
|
||||||
self.assertEqual(purchase.date_planned, po_schedule_date, 'Schedule date should be equal to: Order date of Purchase order + Delivery Lead Time.')
|
|
||||||
|
|
||||||
# check the picking created or not
|
# check the picking created or not
|
||||||
self.assertTrue(purchase.picking_ids, "Picking should be created.")
|
self.assertTrue(purchase.picking_ids, "Picking should be created.")
|
||||||
|
|
||||||
# Check scheduled date of In Type shipment
|
# Check scheduled date of In Type shipment
|
||||||
self.assertEqual(purchase.picking_ids.scheduled_date, po_schedule_date, 'Schedule date of In type shipment should be equal to: schedule date of purchase order.')
|
self.assertEqual(purchase.picking_ids.scheduled_date, schedule_date, 'Schedule date of In type shipment should be equal to: schedule date of purchase order.')
|
||||||
|
|
||||||
def test_01_product_level_delay(self):
|
def test_01_product_level_delay(self):
|
||||||
""" To check schedule dates of multiple purchase order line of the same purchase order,
|
""" To check schedule dates of multiple purchase order line of the same purchase order,
|
||||||
@@ -68,21 +66,18 @@ class TestPurchaseLeadTime(TestPurchase):
|
|||||||
order_line_pro_1 = purchase2.order_line.filtered(lambda r: r.product_id == self.product_1)
|
order_line_pro_1 = purchase2.order_line.filtered(lambda r: r.product_id == self.product_1)
|
||||||
order_line_pro_2 = purchase2.order_line.filtered(lambda r: r.product_id == self.product_2)
|
order_line_pro_2 = purchase2.order_line.filtered(lambda r: r.product_id == self.product_2)
|
||||||
order_date = fields.Datetime.from_string(date_planned1) - timedelta(days=self.product_1.seller_ids.delay)
|
order_date = fields.Datetime.from_string(date_planned1) - timedelta(days=self.product_1.seller_ids.delay)
|
||||||
po_order_date = fields.Datetime.to_string(order_date)
|
self.assertEqual(purchase2.date_order, order_date, 'Order date should be equal to: Date of the procurement order - Delivery Lead Time.')
|
||||||
self.assertEqual(purchase2.date_order, po_order_date, 'Order date should be equal to: Date of the procurement order - Delivery Lead Time.')
|
|
||||||
|
|
||||||
# Check scheduled date of purchase order line for product_1
|
# Check scheduled date of purchase order line for product_1
|
||||||
schedule_date_1 = order_date + timedelta(days=self.product_1.seller_ids.delay)
|
schedule_date_1 = order_date + timedelta(days=self.product_1.seller_ids.delay)
|
||||||
schedule_date_line_1 = fields.Datetime.to_string(schedule_date_1)
|
self.assertEqual(order_line_pro_1.date_planned, schedule_date_1, 'Schedule date of purchase order line for product_1 should be equal to: Order date of purchase order + Delivery Lead Time of product_1.')
|
||||||
self.assertEqual(order_line_pro_1.date_planned, schedule_date_line_1, 'Schedule date of purchase order line for product_1 should be equal to: Order date of purchase order + Delivery Lead Time of product_1.')
|
|
||||||
|
|
||||||
# Check scheduled date of purchase order line for product_2
|
# Check scheduled date of purchase order line for product_2
|
||||||
schedule_date_2 = order_date + timedelta(days=self.product_2.seller_ids.delay)
|
schedule_date_2 = order_date + timedelta(days=self.product_2.seller_ids.delay)
|
||||||
schedule_date_line_2 = fields.Datetime.to_string(schedule_date_2)
|
self.assertEqual(order_line_pro_2.date_planned, schedule_date_2, 'Schedule date of purchase order line for product_2 should be equal to: Order date of purchase order + Delivery Lead Time of product_2.')
|
||||||
self.assertEqual(order_line_pro_2.date_planned, schedule_date_line_2, 'Schedule date of purchase order line for product_2 should be equal to: Order date of purchase order + Delivery Lead Time of product_2.')
|
|
||||||
|
|
||||||
# Check scheduled date of purchase order
|
# Check scheduled date of purchase order
|
||||||
po_schedule_date = min(schedule_date_line_1, schedule_date_line_2)
|
po_schedule_date = min(schedule_date_1, schedule_date_2)
|
||||||
self.assertEqual(purchase2.date_planned, po_schedule_date, 'Schedule date of purchase order should be minimum of schedule dates of purchase order lines.')
|
self.assertEqual(purchase2.date_planned, po_schedule_date, 'Schedule date of purchase order should be minimum of schedule dates of purchase order lines.')
|
||||||
|
|
||||||
# Check the picking created or not
|
# Check the picking created or not
|
||||||
@@ -121,13 +116,11 @@ class TestPurchaseLeadTime(TestPurchase):
|
|||||||
|
|
||||||
# Check order date of purchase order
|
# Check order date of purchase order
|
||||||
order_date = fields.Datetime.from_string(date_planned) - timedelta(days=self.product_1.seller_ids.delay + rule_delay)
|
order_date = fields.Datetime.from_string(date_planned) - timedelta(days=self.product_1.seller_ids.delay + rule_delay)
|
||||||
po_order_date = fields.Datetime.to_string(order_date)
|
self.assertEqual(purchase.date_order, order_date, 'Order date should be equal to: Date of the procurement order - Delivery Lead Time(supplier and pull rules).')
|
||||||
self.assertEqual(purchase.date_order, po_order_date, 'Order date should be equal to: Date of the procurement order - Delivery Lead Time(supplier and pull rules).')
|
|
||||||
|
|
||||||
# Check scheduled date of purchase order
|
# Check scheduled date of purchase order
|
||||||
schedule_date = order_date + timedelta(days=self.product_1.seller_ids.delay + rule_delay)
|
schedule_date = order_date + timedelta(days=self.product_1.seller_ids.delay + rule_delay)
|
||||||
po_schedule_date = fields.Datetime.to_string(schedule_date)
|
self.assertEqual(date_planned, str(schedule_date), 'Schedule date should be equal to: Order date of Purchase order + Delivery Lead Time(supplier and pull rules).')
|
||||||
self.assertEqual(date_planned, po_schedule_date, 'Schedule date should be equal to: Order date of Purchase order + Delivery Lead Time(supplier and pull rules).')
|
|
||||||
|
|
||||||
# Check the picking crated or not
|
# Check the picking crated or not
|
||||||
self.assertTrue(purchase.picking_ids, "Picking should be created.")
|
self.assertTrue(purchase.picking_ids, "Picking should be created.")
|
||||||
@@ -135,10 +128,8 @@ class TestPurchaseLeadTime(TestPurchase):
|
|||||||
# Check scheduled date of Internal Type shipment
|
# Check scheduled date of Internal Type shipment
|
||||||
incoming_shipment1 = self.env['stock.picking'].search([('move_lines.product_id', 'in', (self.product_1.id, self.product_2.id)), ('picking_type_id', '=', self.warehouse_1.int_type_id.id), ('location_id', '=', self.warehouse_1.wh_input_stock_loc_id.id), ('location_dest_id', '=', self.warehouse_1.wh_qc_stock_loc_id.id)])
|
incoming_shipment1 = self.env['stock.picking'].search([('move_lines.product_id', 'in', (self.product_1.id, self.product_2.id)), ('picking_type_id', '=', self.warehouse_1.int_type_id.id), ('location_id', '=', self.warehouse_1.wh_input_stock_loc_id.id), ('location_dest_id', '=', self.warehouse_1.wh_qc_stock_loc_id.id)])
|
||||||
incoming_shipment1_date = order_date + timedelta(days=self.product_1.seller_ids.delay)
|
incoming_shipment1_date = order_date + timedelta(days=self.product_1.seller_ids.delay)
|
||||||
incoming_shipment1_schedule_date = fields.Datetime.to_string(incoming_shipment1_date)
|
self.assertEqual(incoming_shipment1.scheduled_date, incoming_shipment1_date, 'Schedule date of Internal Type shipment for input stock location should be equal to: schedule date of purchase order + push rule delay.')
|
||||||
self.assertEqual(incoming_shipment1.scheduled_date, incoming_shipment1_schedule_date, 'Schedule date of Internal Type shipment for input stock location should be equal to: schedule date of purchase order + push rule delay.')
|
|
||||||
|
|
||||||
incoming_shipment2 = self.env['stock.picking'].search([('picking_type_id', '=', self.warehouse_1.int_type_id.id), ('location_id', '=', self.warehouse_1.wh_qc_stock_loc_id.id), ('location_dest_id', '=', self.warehouse_1.lot_stock_id.id)])
|
incoming_shipment2 = self.env['stock.picking'].search([('picking_type_id', '=', self.warehouse_1.int_type_id.id), ('location_id', '=', self.warehouse_1.wh_qc_stock_loc_id.id), ('location_dest_id', '=', self.warehouse_1.lot_stock_id.id)])
|
||||||
incoming_shipment2_date = schedule_date - timedelta(days=incoming_shipment2.move_lines[0].rule_id.delay)
|
incoming_shipment2_date = schedule_date - timedelta(days=incoming_shipment2.move_lines[0].rule_id.delay)
|
||||||
incoming_shipment2_schedule_date = fields.Datetime.to_string(incoming_shipment2_date)
|
self.assertEqual(incoming_shipment2.scheduled_date, incoming_shipment2_date, 'Schedule date of Internal Type shipment for quality control stock location should be equal to: schedule date of Internal type shipment for input stock location + push rule delay..')
|
||||||
self.assertEqual(incoming_shipment2.scheduled_date, incoming_shipment2_schedule_date, 'Schedule date of Internal Type shipment for quality control stock location should be equal to: schedule date of Internal type shipment for input stock location + push rule delay..')
|
|
||||||
|
|||||||
@@ -163,9 +163,9 @@ class SaleOrder(models.Model):
|
|||||||
order.access_url = '/my/orders/%s' % (order.id)
|
order.access_url = '/my/orders/%s' % (order.id)
|
||||||
|
|
||||||
def _compute_is_expired(self):
|
def _compute_is_expired(self):
|
||||||
now = datetime.now()
|
today = fields.Date.today()
|
||||||
for order in self:
|
for order in self:
|
||||||
if order.validity_date and fields.Datetime.from_string(order.validity_date) < now:
|
if order.validity_date and order.validity_date < today:
|
||||||
order.is_expired = True
|
order.is_expired = True
|
||||||
else:
|
else:
|
||||||
order.is_expired = False
|
order.is_expired = False
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ class SaleOrder(models.Model):
|
|||||||
def _compute_effective_date(self):
|
def _compute_effective_date(self):
|
||||||
for order in self:
|
for order in self:
|
||||||
pickings = order.picking_ids.filtered(lambda x: x.state == 'done' and x.location_dest_id.usage == 'customer')
|
pickings = order.picking_ids.filtered(lambda x: x.state == 'done' and x.location_dest_id.usage == 'customer')
|
||||||
dates_list = pickings.mapped('date_done')
|
dates_list = [date for date in pickings.mapped('date_done') if date]
|
||||||
order.effective_date = dates_list and min(dates_list)
|
order.effective_date = dates_list and min(dates_list).date()
|
||||||
|
|
||||||
@api.onchange('commitment_date')
|
@api.onchange('commitment_date')
|
||||||
def onchange_commitment_date(self):
|
def onchange_commitment_date(self):
|
||||||
|
|||||||
@@ -17,6 +17,6 @@ class TestSaleOrderDates(common.TransactionCase):
|
|||||||
# I verify that the Procurements and Stock Moves have been generated with the correct date
|
# I verify that the Procurements and Stock Moves have been generated with the correct date
|
||||||
security_delay = timedelta(days=new_order.company_id.security_lead)
|
security_delay = timedelta(days=new_order.company_id.security_lead)
|
||||||
commitment_date = fields.Datetime.from_string(new_order.commitment_date)
|
commitment_date = fields.Datetime.from_string(new_order.commitment_date)
|
||||||
right_date = fields.Datetime.to_string(commitment_date - security_delay)
|
right_date = commitment_date - security_delay
|
||||||
for line in new_order.order_line:
|
for line in new_order.order_line:
|
||||||
self.assertEqual(line.move_ids[0].date_expected, right_date, "The expected date for the Stock Move is wrong")
|
self.assertEqual(line.move_ids[0].date_expected, right_date, "The expected date for the Stock Move is wrong")
|
||||||
|
|||||||
@@ -46,32 +46,36 @@ class TestSaleExpectedDate(common.TransactionCase):
|
|||||||
|
|
||||||
# if Shipping Policy is set to `direct`(when SO is in draft state) then expected date should be
|
# if Shipping Policy is set to `direct`(when SO is in draft state) then expected date should be
|
||||||
# current date + shortest lead time from all of it's order lines
|
# current date + shortest lead time from all of it's order lines
|
||||||
expected_date = fields.Datetime.to_string(fields.Datetime.from_string(fields.Datetime.now()) + timedelta(days=5))
|
expected_date = fields.Datetime.now() + timedelta(days=5)
|
||||||
self.assertEquals(expected_date, sale_order.expected_date, "Wrong expected date on sale order!")
|
self.assertAlmostEqual(expected_date, sale_order.expected_date,
|
||||||
|
msg="Wrong expected date on sale order!", delta=timedelta(seconds=1))
|
||||||
|
|
||||||
# if Shipping Policy is set to `one`(when SO is in draft state) then expected date should be
|
# if Shipping Policy is set to `one`(when SO is in draft state) then expected date should be
|
||||||
# current date + longest lead time from all of it's order lines
|
# current date + longest lead time from all of it's order lines
|
||||||
sale_order.write({'picking_policy': 'one'})
|
sale_order.write({'picking_policy': 'one'})
|
||||||
expected_date = fields.Datetime.to_string(fields.Datetime.from_string(fields.Datetime.now()) + timedelta(days=15))
|
expected_date = fields.Datetime.now() + timedelta(days=15)
|
||||||
self.assertEquals(expected_date, sale_order.expected_date, "Wrong expected date on sale order!")
|
self.assertAlmostEquals(expected_date, sale_order.expected_date,
|
||||||
|
msg="Wrong expected date on sale order!", delta=timedelta(seconds=1))
|
||||||
|
|
||||||
sale_order.action_confirm()
|
sale_order.action_confirm()
|
||||||
|
|
||||||
# Setting confirmation date of SO to 5 days from today so that the expected/effective date could be checked
|
# Setting confirmation date of SO to 5 days from today so that the expected/effective date could be checked
|
||||||
# against real confirmation date
|
# against real confirmation date
|
||||||
confirm_date = fields.Datetime.from_string(fields.Datetime.now()) + timedelta(days=5)
|
confirm_date = fields.Datetime.now() + timedelta(days=5)
|
||||||
sale_order.write({'confirmation_date': confirm_date})
|
sale_order.write({'confirmation_date': confirm_date})
|
||||||
|
|
||||||
# if Shipping Policy is set to `one`(when SO is confirmed) then expected date should be
|
# if Shipping Policy is set to `one`(when SO is confirmed) then expected date should be
|
||||||
# SO confirmation date + longest lead time from all of it's order lines
|
# SO confirmation date + longest lead time from all of it's order lines
|
||||||
expected_date = fields.Datetime.to_string(confirm_date + timedelta(days=15))
|
expected_date = confirm_date + timedelta(days=15)
|
||||||
self.assertEquals(expected_date, sale_order.expected_date, "Wrong expected date on sale order!")
|
self.assertAlmostEqual(expected_date, sale_order.expected_date,
|
||||||
|
msg="Wrong expected date on sale order!", delta=timedelta(seconds=1))
|
||||||
|
|
||||||
# if Shipping Policy is set to `direct`(when SO is confirmed) then expected date should be
|
# if Shipping Policy is set to `direct`(when SO is confirmed) then expected date should be
|
||||||
# SO confirmation date + shortest lead time from all of it's order lines
|
# SO confirmation date + shortest lead time from all of it's order lines
|
||||||
sale_order.write({'picking_policy': 'direct'})
|
sale_order.write({'picking_policy': 'direct'})
|
||||||
expected_date = fields.Datetime.to_string(confirm_date + timedelta(days=5))
|
expected_date = confirm_date + timedelta(days=5)
|
||||||
self.assertEquals(expected_date, sale_order.expected_date, "Wrong expected date on sale order!")
|
self.assertAlmostEqual(expected_date, sale_order.expected_date,
|
||||||
|
msg="Wrong expected date on sale order!", delta=timedelta(seconds=1))
|
||||||
|
|
||||||
# Check effective date, it should be date on which the first shipment successfully delivered to customer
|
# Check effective date, it should be date on which the first shipment successfully delivered to customer
|
||||||
picking = sale_order.picking_ids[0]
|
picking = sale_order.picking_ids[0]
|
||||||
|
|||||||
@@ -280,13 +280,13 @@ class SaleOrderLine(models.Model):
|
|||||||
"""
|
"""
|
||||||
values = super(SaleOrderLine, self)._prepare_procurement_values(group_id)
|
values = super(SaleOrderLine, self)._prepare_procurement_values(group_id)
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
date_planned = datetime.strptime(self.order_id.confirmation_date, DEFAULT_SERVER_DATETIME_FORMAT)\
|
date_planned = self.order_id.confirmation_date\
|
||||||
+ timedelta(days=self.customer_lead or 0.0) - timedelta(days=self.order_id.company_id.security_lead)
|
+ timedelta(days=self.customer_lead or 0.0) - timedelta(days=self.order_id.company_id.security_lead)
|
||||||
values.update({
|
values.update({
|
||||||
'company_id': self.order_id.company_id,
|
'company_id': self.order_id.company_id,
|
||||||
'group_id': group_id,
|
'group_id': group_id,
|
||||||
'sale_line_id': self.id,
|
'sale_line_id': self.id,
|
||||||
'date_planned': date_planned.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
'date_planned': date_planned,
|
||||||
'route_ids': self.route_id,
|
'route_ids': self.route_id,
|
||||||
'warehouse_id': self.order_id.warehouse_id or False,
|
'warehouse_id': self.order_id.warehouse_id or False,
|
||||||
'partner_dest_id': self.order_id.partner_shipping_id
|
'partner_dest_id': self.order_id.partner_shipping_id
|
||||||
|
|||||||
@@ -93,7 +93,9 @@ class Product(models.Model):
|
|||||||
domain_quant_loc, domain_move_in_loc, domain_move_out_loc = self._get_domain_locations()
|
domain_quant_loc, domain_move_in_loc, domain_move_out_loc = self._get_domain_locations()
|
||||||
domain_quant = [('product_id', 'in', self.ids)] + domain_quant_loc
|
domain_quant = [('product_id', 'in', self.ids)] + domain_quant_loc
|
||||||
dates_in_the_past = False
|
dates_in_the_past = False
|
||||||
if to_date and to_date < fields.Datetime.now(): #Only to_date as to_date will correspond to qty_available
|
# only to_date as to_date will correspond to qty_available
|
||||||
|
to_date = fields.Datetime.to_datetime(to_date)
|
||||||
|
if to_date and to_date < fields.Datetime.now():
|
||||||
dates_in_the_past = True
|
dates_in_the_past = True
|
||||||
|
|
||||||
domain_move_in = [('product_id', 'in', self.ids)] + domain_move_in_loc
|
domain_move_in = [('product_id', 'in', self.ids)] + domain_move_in_loc
|
||||||
|
|||||||
@@ -426,11 +426,11 @@ class StockMove(models.Model):
|
|||||||
if 'date_expected' in propagated_changes_dict:
|
if 'date_expected' in propagated_changes_dict:
|
||||||
propagated_changes_dict.pop('date_expected')
|
propagated_changes_dict.pop('date_expected')
|
||||||
if propagated_date_field:
|
if propagated_date_field:
|
||||||
current_date = datetime.strptime(move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT)
|
current_date = move.date_expected
|
||||||
new_date = datetime.strptime(vals.get(propagated_date_field), DEFAULT_SERVER_DATETIME_FORMAT)
|
new_date = fields.Datetime.from_string(vals.get(propagated_date_field))
|
||||||
delta_days = (new_date - current_date).total_seconds() / 86400
|
delta_days = (new_date - current_date).total_seconds() / 86400
|
||||||
if abs(delta_days) >= move.company_id.propagation_minimum_delta:
|
if abs(delta_days) >= move.company_id.propagation_minimum_delta:
|
||||||
old_move_date = datetime.strptime(move.move_dest_ids[0].date_expected, DEFAULT_SERVER_DATETIME_FORMAT)
|
old_move_date = move.move_dest_ids[0].date_expected
|
||||||
new_move_date = (old_move_date + relativedelta.relativedelta(days=delta_days or 0)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
new_move_date = (old_move_date + relativedelta.relativedelta(days=delta_days or 0)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||||
propagated_changes_dict['date_expected'] = new_move_date
|
propagated_changes_dict['date_expected'] = new_move_date
|
||||||
#For pushed moves as well as for pulled moves, propagate by recursive call of write().
|
#For pushed moves as well as for pulled moves, propagate by recursive call of write().
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ class StockRule(models.Model):
|
|||||||
Care this function is not call by method run. It is called explicitely
|
Care this function is not call by method run. It is called explicitely
|
||||||
in stock_move.py inside the method _push_apply
|
in stock_move.py inside the method _push_apply
|
||||||
"""
|
"""
|
||||||
new_date = (datetime.strptime(move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=self.delay)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
new_date = fields.Datetime.to_string(move.date_expected + relativedelta(days=self.delay))
|
||||||
if self.auto == 'transparent':
|
if self.auto == 'transparent':
|
||||||
move.write({
|
move.write({
|
||||||
'date': new_date,
|
'date': new_date,
|
||||||
@@ -193,7 +193,9 @@ class StockRule(models.Model):
|
|||||||
:param procurement: browse record
|
:param procurement: browse record
|
||||||
:rtype: dictionary
|
:rtype: dictionary
|
||||||
'''
|
'''
|
||||||
date_expected = (datetime.strptime(values['date_planned'], DEFAULT_SERVER_DATETIME_FORMAT) - relativedelta(days=self.delay or 0)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
date_expected = fields.Datetime.to_string(
|
||||||
|
fields.Datetime.from_string(values['date_planned']) - relativedelta(days=self.delay or 0)
|
||||||
|
)
|
||||||
# it is possible that we've already got some move done, so check for the done qty and create
|
# it is possible that we've already got some move done, so check for the done qty and create
|
||||||
# a new move with the correct qty
|
# a new move with the correct qty
|
||||||
qty_left = product_qty
|
qty_left = product_qty
|
||||||
|
|||||||
@@ -3602,8 +3602,9 @@ class StockMove(TransactionCase):
|
|||||||
('product_id', '=', self.product3.id),
|
('product_id', '=', self.product3.id),
|
||||||
('lot_id', '=', lot1.id),
|
('lot_id', '=', lot1.id),
|
||||||
])
|
])
|
||||||
from datetime import datetime, timedelta
|
from odoo.fields import Datetime
|
||||||
initial_in_date_lot1 = datetime.now() - timedelta(days=5)
|
from datetime import timedelta
|
||||||
|
initial_in_date_lot1 = Datetime.now() - timedelta(days=5)
|
||||||
quant_lot1.in_date = initial_in_date_lot1
|
quant_lot1.in_date = initial_in_date_lot1
|
||||||
|
|
||||||
# Move one quant to pack location
|
# Move one quant to pack location
|
||||||
@@ -3626,8 +3627,7 @@ class StockMove(TransactionCase):
|
|||||||
# As lot1 has an older date and FIFO is set by default, it's the one that should be
|
# As lot1 has an older date and FIFO is set by default, it's the one that should be
|
||||||
# in pack.
|
# in pack.
|
||||||
self.assertEqual(len(quant_in_pack), 1)
|
self.assertEqual(len(quant_in_pack), 1)
|
||||||
from odoo.fields import Datetime
|
self.assertAlmostEqual(quant_in_pack.in_date, initial_in_date_lot1, delta=timedelta(seconds=1))
|
||||||
self.assertEqual(quant_in_pack.in_date, Datetime.to_string(initial_in_date_lot1))
|
|
||||||
self.assertEqual(quant_in_pack.lot_id, lot1)
|
self.assertEqual(quant_in_pack.lot_id, lot1)
|
||||||
|
|
||||||
# Now, edit the move line and actually move the other lot
|
# Now, edit the move line and actually move the other lot
|
||||||
@@ -3640,7 +3640,7 @@ class StockMove(TransactionCase):
|
|||||||
('lot_id', '=', lot1.id),
|
('lot_id', '=', lot1.id),
|
||||||
])
|
])
|
||||||
self.assertEqual(quant_lot1.location_id, self.stock_location)
|
self.assertEqual(quant_lot1.location_id, self.stock_location)
|
||||||
self.assertEqual(quant_lot1.in_date, Datetime.to_string(initial_in_date_lot1))
|
self.assertAlmostEqual(quant_lot1.in_date, initial_in_date_lot1, delta=timedelta(seconds=1))
|
||||||
|
|
||||||
# Check that lo2 is in pack with is right in_date
|
# Check that lo2 is in pack with is right in_date
|
||||||
quant_lot2 = self.env['stock.quant'].search([
|
quant_lot2 = self.env['stock.quant'].search([
|
||||||
@@ -3649,7 +3649,7 @@ class StockMove(TransactionCase):
|
|||||||
('lot_id', '=', lot2.id),
|
('lot_id', '=', lot2.id),
|
||||||
])
|
])
|
||||||
self.assertEqual(quant_lot2.location_id, self.pack_location)
|
self.assertEqual(quant_lot2.location_id, self.pack_location)
|
||||||
self.assertEqual(quant_lot2.in_date, initial_in_date_lot2)
|
self.assertAlmostEqual(quant_lot2.in_date, initial_in_date_lot2, delta=timedelta(seconds=1))
|
||||||
|
|
||||||
def test_in_date_3(self):
|
def test_in_date_3(self):
|
||||||
""" Check that, when creating a move line on a done stock move, the lot and its incoming
|
""" Check that, when creating a move line on a done stock move, the lot and its incoming
|
||||||
@@ -3707,8 +3707,9 @@ class StockMove(TransactionCase):
|
|||||||
('product_id', '=', self.product3.id),
|
('product_id', '=', self.product3.id),
|
||||||
('lot_id', '=', lot1.id),
|
('lot_id', '=', lot1.id),
|
||||||
])
|
])
|
||||||
from datetime import datetime, timedelta
|
from odoo.fields import Datetime
|
||||||
initial_in_date_lot1 = datetime.now() - timedelta(days=5)
|
from datetime import timedelta
|
||||||
|
initial_in_date_lot1 = Datetime.now() - timedelta(days=5)
|
||||||
quant_lot1.in_date = initial_in_date_lot1
|
quant_lot1.in_date = initial_in_date_lot1
|
||||||
|
|
||||||
# Move one quant to pack location
|
# Move one quant to pack location
|
||||||
@@ -3741,12 +3742,11 @@ class StockMove(TransactionCase):
|
|||||||
('product_id', '=', self.product3.id),
|
('product_id', '=', self.product3.id),
|
||||||
])
|
])
|
||||||
self.assertEqual(len(quants), 2)
|
self.assertEqual(len(quants), 2)
|
||||||
from odoo.fields import Datetime
|
|
||||||
for quant in quants:
|
for quant in quants:
|
||||||
if quant.lot_id == lot1:
|
if quant.lot_id == lot1:
|
||||||
self.assertEqual(quant.in_date, Datetime.to_string(initial_in_date_lot1))
|
self.assertAlmostEqual(quant.in_date, initial_in_date_lot1, delta=timedelta(seconds=1))
|
||||||
elif quant.lot_id == lot2:
|
elif quant.lot_id == lot2:
|
||||||
self.assertEqual(quant.in_date, initial_in_date_lot2)
|
self.assertAlmostEqual(quant.in_date, initial_in_date_lot2, delta=timedelta(seconds=1))
|
||||||
|
|
||||||
def test_transit_1(self):
|
def test_transit_1(self):
|
||||||
""" Receive some products, send some to transit, check the product's `available_qty`
|
""" Receive some products, send some to transit, check the product's `available_qty`
|
||||||
|
|||||||
@@ -763,7 +763,8 @@ class StockQuant(TransactionCase):
|
|||||||
'product_id': product1.id,
|
'product_id': product1.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
in_date1 = datetime.now()
|
from odoo.fields import Datetime
|
||||||
|
in_date1 = Datetime.now()
|
||||||
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0, lot_id=lot1, in_date=in_date1)
|
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0, lot_id=lot1, in_date=in_date1)
|
||||||
|
|
||||||
quant = self.env['stock.quant'].search([
|
quant = self.env['stock.quant'].search([
|
||||||
@@ -773,10 +774,9 @@ class StockQuant(TransactionCase):
|
|||||||
self.assertEqual(len(quant), 1)
|
self.assertEqual(len(quant), 1)
|
||||||
self.assertEqual(quant.quantity, 1)
|
self.assertEqual(quant.quantity, 1)
|
||||||
self.assertEqual(quant.lot_id.id, lot1.id)
|
self.assertEqual(quant.lot_id.id, lot1.id)
|
||||||
from odoo.fields import Datetime
|
self.assertEqual(quant.in_date, in_date1)
|
||||||
self.assertEqual(quant.in_date, Datetime.to_string(in_date1))
|
|
||||||
|
|
||||||
in_date2 = datetime.now() - timedelta(days=5)
|
in_date2 = Datetime.now() - timedelta(days=5)
|
||||||
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0, lot_id=lot1, in_date=in_date2)
|
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0, lot_id=lot1, in_date=in_date2)
|
||||||
|
|
||||||
quant = self.env['stock.quant'].search([
|
quant = self.env['stock.quant'].search([
|
||||||
@@ -786,4 +786,4 @@ class StockQuant(TransactionCase):
|
|||||||
self.assertEqual(len(quant), 1)
|
self.assertEqual(len(quant), 1)
|
||||||
self.assertEqual(quant.quantity, 2)
|
self.assertEqual(quant.quantity, 2)
|
||||||
self.assertEqual(quant.lot_id.id, lot1.id)
|
self.assertEqual(quant.lot_id.id, lot1.id)
|
||||||
self.assertEqual(quant.in_date, Datetime.to_string(in_date2))
|
self.assertEqual(quant.in_date, in_date2)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
from odoo.fields import Date
|
from odoo.fields import Date, Datetime
|
||||||
from odoo.tests.common import TransactionCase
|
from odoo.tests.common import TransactionCase
|
||||||
|
|
||||||
|
|
||||||
@@ -3260,7 +3260,7 @@ class TestStockValuation(TransactionCase):
|
|||||||
def test_at_date_standard_1(self):
|
def test_at_date_standard_1(self):
|
||||||
self.product1.product_tmpl_id.cost_method = 'standard'
|
self.product1.product_tmpl_id.cost_method = 'standard'
|
||||||
|
|
||||||
now = Date.from_string(Date.today())
|
now = Datetime.now()
|
||||||
date1 = now - timedelta(days=8)
|
date1 = now - timedelta(days=8)
|
||||||
date2 = now - timedelta(days=7)
|
date2 = now - timedelta(days=7)
|
||||||
date3 = now - timedelta(days=6)
|
date3 = now - timedelta(days=6)
|
||||||
@@ -3387,42 +3387,42 @@ class TestStockValuation(TransactionCase):
|
|||||||
self.assertEqual(self.product1.stock_value, 712.5)
|
self.assertEqual(self.product1.stock_value, 712.5)
|
||||||
|
|
||||||
# Quantity available at date
|
# Quantity available at date
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date1)).qty_available, 0)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).qty_available, 0)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date2)).qty_available, 10)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).qty_available, 10)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date3)).qty_available, 30)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).qty_available, 30)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date4)).qty_available, 15)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).qty_available, 15)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date5)).qty_available, 15)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).qty_available, 15)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date6)).qty_available, -5)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).qty_available, -5)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date7)).qty_available, -5)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date7)).qty_available, -5)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date8)).qty_available, 95)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date8)).qty_available, 95)
|
||||||
|
|
||||||
# Valuation at date
|
# Valuation at date
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date2)).stock_value, 100)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).stock_value, 100)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date3)).stock_value, 300)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).stock_value, 300)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date4)).stock_value, 150)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).stock_value, 150)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date5)).stock_value, 75)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).stock_value, 75)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date6)).stock_value, -25)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).stock_value, -25)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date8)).stock_value, 712.5)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date8)).stock_value, 712.5)
|
||||||
|
|
||||||
# Quantity at date
|
# Quantity at date
|
||||||
self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date1)).qty_at_date, 0.0)
|
self.assertAlmostEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).qty_at_date, 0.0)
|
||||||
self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date2)).qty_at_date, 10.0)
|
self.assertAlmostEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).qty_at_date, 10.0)
|
||||||
self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date3)).qty_at_date, 30.0)
|
self.assertAlmostEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).qty_at_date, 30.0)
|
||||||
self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date4)).qty_at_date, 15.0)
|
self.assertAlmostEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).qty_at_date, 15.0)
|
||||||
self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date5)).qty_at_date, 15.0)
|
self.assertAlmostEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).qty_at_date, 15.0)
|
||||||
self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date6)).qty_at_date, -5.0)
|
self.assertAlmostEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).qty_at_date, -5.0)
|
||||||
self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date7)).qty_at_date, -5.0)
|
self.assertAlmostEqual(self.product1.with_context(to_date=Datetime.to_string(date7)).qty_at_date, -5.0)
|
||||||
self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date8)).qty_at_date, 95.0)
|
self.assertAlmostEqual(self.product1.with_context(to_date=Datetime.to_string(date8)).qty_at_date, 95.0)
|
||||||
|
|
||||||
# edit the done quantity of move1, decrease it
|
# edit the done quantity of move1, decrease it
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date2)).qty_available, 10)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).qty_available, 10)
|
||||||
self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date2)).qty_at_date, 10.0)
|
self.assertAlmostEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).qty_at_date, 10.0)
|
||||||
move1.quantity_done = 5
|
move1.quantity_done = 5
|
||||||
move1.account_move_ids.write({'date': date2})
|
move1.account_move_ids.write({'date': date2})
|
||||||
|
|
||||||
# the quantity at date will reflect the change directly
|
# the quantity at date will reflect the change directly
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date2)).qty_available, 5)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).qty_available, 5)
|
||||||
self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date2)).qty_at_date, 5.0)
|
self.assertAlmostEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).qty_at_date, 5.0)
|
||||||
|
|
||||||
# as when we decrease a quantity on a recreipt, we consider it as a out move with the price
|
# as when we decrease a quantity on a recreipt, we consider it as a out move with the price
|
||||||
# of today, the value will be decrease of 100 - (5*7.5)
|
# of today, the value will be decrease of 100 - (5*7.5)
|
||||||
@@ -3430,22 +3430,22 @@ class TestStockValuation(TransactionCase):
|
|||||||
|
|
||||||
# the valuatin at date will take the qty at date * the standard price at date, that's why
|
# the valuatin at date will take the qty at date * the standard price at date, that's why
|
||||||
# it is different.
|
# it is different.
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date2)).stock_value, 50)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).stock_value, 50)
|
||||||
|
|
||||||
# edit move 4, send 15 instead of 20
|
# edit move 4, send 15 instead of 20
|
||||||
# we now have +5 + 20 - 15 -20 = -10 * a standard price of 5
|
# we now have +5 + 20 - 15 -20 = -10 * a standard price of 5
|
||||||
self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date6)).qty_available, -10.0)
|
self.assertAlmostEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).qty_available, -10.0)
|
||||||
self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date6)).qty_at_date, -10.0)
|
self.assertAlmostEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).qty_at_date, -10.0)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date6)).stock_value, -50)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).stock_value, -50)
|
||||||
move4.quantity_done = 15
|
move4.quantity_done = 15
|
||||||
move4.account_move_ids.write({'date': date6})
|
move4.account_move_ids.write({'date': date6})
|
||||||
|
|
||||||
# -(20*5) + (5*7.5)
|
# -(20*5) + (5*7.5)
|
||||||
self.assertEqual(move4.value, -62.5)
|
self.assertEqual(move4.value, -62.5)
|
||||||
# we now have +5 + 20 - 15 -15 = -5 * a standard price of 5
|
# we now have +5 + 20 - 15 -15 = -5 * a standard price of 5
|
||||||
self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date6)).qty_available, -5.0)
|
self.assertAlmostEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).qty_available, -5.0)
|
||||||
self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date6)).qty_at_date, -5.0)
|
self.assertAlmostEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).qty_at_date, -5.0)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date6)).stock_value, -25)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).stock_value, -25)
|
||||||
|
|
||||||
def test_at_date_fifo_1(self):
|
def test_at_date_fifo_1(self):
|
||||||
""" Make some operations at different dates, check that the results of the valuation at
|
""" Make some operations at different dates, check that the results of the valuation at
|
||||||
@@ -3454,7 +3454,7 @@ class TestStockValuation(TransactionCase):
|
|||||||
"""
|
"""
|
||||||
self.product1.product_tmpl_id.cost_method = 'fifo'
|
self.product1.product_tmpl_id.cost_method = 'fifo'
|
||||||
|
|
||||||
now = Date.from_string(Date.today())
|
now = Datetime.now()
|
||||||
date1 = now - timedelta(days=8)
|
date1 = now - timedelta(days=8)
|
||||||
date2 = now - timedelta(days=7)
|
date2 = now - timedelta(days=7)
|
||||||
date3 = now - timedelta(days=6)
|
date3 = now - timedelta(days=6)
|
||||||
@@ -3580,18 +3580,18 @@ class TestStockValuation(TransactionCase):
|
|||||||
# ending: manual valuation
|
# ending: manual valuation
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
self.product1.product_tmpl_id.valuation = 'manual_periodic'
|
self.product1.product_tmpl_id.valuation = 'manual_periodic'
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date1)).qty_at_date, 20)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).qty_at_date, 20)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date1)).stock_value, 200)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).stock_value, 200)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date2)).qty_at_date, 30)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).qty_at_date, 30)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date2)).stock_value, 320)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).stock_value, 320)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date3)).qty_at_date, 15)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).qty_at_date, 15)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date3)).stock_value, 160)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).stock_value, 160)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date4)).qty_at_date, -5)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).qty_at_date, -5)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date4)).stock_value, -125)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).stock_value, -125)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date5)).qty_at_date, 95)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).qty_at_date, 95)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date5)).stock_value, 1375)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).stock_value, 1375)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date6)).qty_at_date, 95)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).qty_at_date, 95)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date6)).stock_value, 1375)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).stock_value, 1375)
|
||||||
self.assertEqual(self.product1.qty_at_date, 95)
|
self.assertEqual(self.product1.qty_at_date, 95)
|
||||||
self.assertEqual(self.product1.stock_value, 1375)
|
self.assertEqual(self.product1.stock_value, 1375)
|
||||||
|
|
||||||
@@ -3599,25 +3599,25 @@ class TestStockValuation(TransactionCase):
|
|||||||
# ending: perpetual valuation
|
# ending: perpetual valuation
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
self.product1.product_tmpl_id.valuation = 'real_time'
|
self.product1.product_tmpl_id.valuation = 'real_time'
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date1)).qty_at_date, 10)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).qty_at_date, 10)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date1)).stock_value, 100)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).stock_value, 100)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date2)).qty_at_date, 20)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).qty_at_date, 20)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date2)).stock_value, 220)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).stock_value, 220)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date3)).qty_at_date, 5)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).qty_at_date, 5)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date3)).stock_value, 60)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).stock_value, 60)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date4)).qty_at_date, -15)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).qty_at_date, -15)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date4)).stock_value, -180)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).stock_value, -180)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date5)).qty_at_date, 85)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).qty_at_date, 85)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date5)).stock_value, 1320)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).stock_value, 1320)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date6)).qty_at_date, 85)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).qty_at_date, 85)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date6)).stock_value, 1275)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).stock_value, 1275)
|
||||||
self.assertEqual(self.product1.qty_at_date, 95)
|
self.assertEqual(self.product1.qty_at_date, 95)
|
||||||
self.assertEqual(self.product1.stock_value, 1375)
|
self.assertEqual(self.product1.stock_value, 1375)
|
||||||
|
|
||||||
def test_at_date_fifo_2(self):
|
def test_at_date_fifo_2(self):
|
||||||
self.product1.product_tmpl_id.cost_method = 'fifo'
|
self.product1.product_tmpl_id.cost_method = 'fifo'
|
||||||
|
|
||||||
now = Date.from_string(Date.today())
|
now = Datetime.now()
|
||||||
date1 = now - timedelta(days=8)
|
date1 = now - timedelta(days=8)
|
||||||
date2 = now - timedelta(days=7)
|
date2 = now - timedelta(days=7)
|
||||||
date3 = now - timedelta(days=6)
|
date3 = now - timedelta(days=6)
|
||||||
@@ -3741,18 +3741,18 @@ class TestStockValuation(TransactionCase):
|
|||||||
# ending: manual valuation
|
# ending: manual valuation
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
self.product1.product_tmpl_id.valuation = 'manual_periodic'
|
self.product1.product_tmpl_id.valuation = 'manual_periodic'
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date1)).qty_at_date, 10)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).qty_at_date, 10)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date1)).stock_value, 100)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).stock_value, 100)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date2)).qty_at_date, 20)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).qty_at_date, 20)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date2)).stock_value, 250)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).stock_value, 250)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date3)).qty_at_date, -10)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).qty_at_date, -10)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date3)).stock_value, -200)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).stock_value, -200)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date4)).qty_at_date, 0)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).qty_at_date, 0)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date4)).stock_value, 0)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).stock_value, 0)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date5)).qty_at_date, 10)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).qty_at_date, 10)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date5)).stock_value, 100)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).stock_value, 100)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date6)).qty_at_date, 10)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).qty_at_date, 10)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date6)).stock_value, 100)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).stock_value, 100)
|
||||||
self.assertEqual(self.product1.qty_at_date, 10)
|
self.assertEqual(self.product1.qty_at_date, 10)
|
||||||
self.assertEqual(self.product1.stock_value, 100)
|
self.assertEqual(self.product1.stock_value, 100)
|
||||||
|
|
||||||
@@ -3760,17 +3760,17 @@ class TestStockValuation(TransactionCase):
|
|||||||
# ending: perpetual valuation
|
# ending: perpetual valuation
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
self.product1.product_tmpl_id.valuation = 'real_time'
|
self.product1.product_tmpl_id.valuation = 'real_time'
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date1)).qty_at_date, 10)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).qty_at_date, 10)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date1)).stock_value, 100)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).stock_value, 100)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date2)).qty_at_date, 20)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).qty_at_date, 20)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date2)).stock_value, 250)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).stock_value, 250)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date3)).qty_at_date, -10)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).qty_at_date, -10)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date3)).stock_value, -150)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).stock_value, -150)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date4)).qty_at_date, 0)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).qty_at_date, 0)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date4)).stock_value, 50)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).stock_value, 50)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date5)).qty_at_date, 10)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).qty_at_date, 10)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date5)).stock_value, 150)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).stock_value, 150)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date6)).qty_at_date, 10)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).qty_at_date, 10)
|
||||||
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date6)).stock_value, 100)
|
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).stock_value, 100)
|
||||||
self.assertEqual(self.product1.qty_at_date, 10)
|
self.assertEqual(self.product1.qty_at_date, 10)
|
||||||
self.assertEqual(self.product1.stock_value, 100)
|
self.assertEqual(self.product1.stock_value, 100)
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class TestLifoPrice(common.TransactionCase):
|
|||||||
with out_form.move_ids_without_package.new() as move:
|
with out_form.move_ids_without_package.new() as move:
|
||||||
move.product_id = product_lifo_icecream
|
move.product_id = product_lifo_icecream
|
||||||
move.quantity_done = 20.0
|
move.quantity_done = 20.0
|
||||||
move.date_expected = fields.Date.context_today(self.env['stock.move.line'])
|
move.date_expected = fields.Datetime.now()
|
||||||
outgoing_lifo_shipment = out_form.save()
|
outgoing_lifo_shipment = out_form.save()
|
||||||
|
|
||||||
# I assign this outgoing shipment
|
# I assign this outgoing shipment
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class Image(models.AbstractModel):
|
|||||||
if max_width or max_height:
|
if max_width or max_height:
|
||||||
max_size = '%sx%s' % (max_width, max_height)
|
max_size = '%sx%s' % (max_width, max_height)
|
||||||
|
|
||||||
sha = hashlib.sha1(getattr(record, '__last_update').encode('utf-8')).hexdigest()[0:7]
|
sha = hashlib.sha1(str(getattr(record, '__last_update')).encode('utf-8')).hexdigest()[0:7]
|
||||||
max_size = '' if max_size is None else '/%s' % max_size
|
max_size = '' if max_size is None else '/%s' % max_size
|
||||||
avoid_if_small = '&avoid_if_small=true' if options.get('avoid_if_small') else ''
|
avoid_if_small = '&avoid_if_small=true' if options.get('avoid_if_small') else ''
|
||||||
src = '/web/image/%s/%s/%s%s?unique=%s%s' % (record._name, record.id, field_name, max_size, sha, avoid_if_small)
|
src = '/web/image/%s/%s/%s%s?unique=%s%s' % (record._name, record.id, field_name, max_size, sha, avoid_if_small)
|
||||||
|
|||||||
@@ -540,7 +540,7 @@ class Website(models.Model):
|
|||||||
if page.view_id and page.view_id.priority != 16:
|
if page.view_id and page.view_id.priority != 16:
|
||||||
record['__priority'] = min(round(page.view_id.priority / 32.0, 1), 1)
|
record['__priority'] = min(round(page.view_id.priority / 32.0, 1), 1)
|
||||||
if page['write_date']:
|
if page['write_date']:
|
||||||
record['__lastmod'] = page['write_date'][:10]
|
record['__lastmod'] = page['write_date'].date()
|
||||||
yield record
|
yield record
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
@@ -563,7 +563,7 @@ class Website(models.Model):
|
|||||||
def image_url(self, record, field, size=None):
|
def image_url(self, record, field, size=None):
|
||||||
""" Returns a local url that points to the image field of a given browse record. """
|
""" Returns a local url that points to the image field of a given browse record. """
|
||||||
sudo_record = record.sudo()
|
sudo_record = record.sudo()
|
||||||
sha = hashlib.sha1(getattr(sudo_record, '__last_update').encode('utf-8')).hexdigest()[0:7]
|
sha = hashlib.sha1(str(getattr(sudo_record, '__last_update')).encode('utf-8')).hexdigest()[0:7]
|
||||||
size = '' if size is None else '/%s' % size
|
size = '' if size is None else '/%s' % size
|
||||||
return '/web/image/%s/%s/%s%s?unique=%s' % (record._name, record.id, field, size, sha)
|
return '/web/image/%s/%s/%s%s?unique=%s' % (record._name, record.id, field, size, sha)
|
||||||
|
|
||||||
|
|||||||
@@ -221,7 +221,8 @@ class BlogPost(models.Model):
|
|||||||
result = True
|
result = True
|
||||||
for post in self:
|
for post in self:
|
||||||
copy_vals = dict(vals)
|
copy_vals = dict(vals)
|
||||||
if 'website_published' in vals and 'published_date' not in vals and (post.published_date or '') <= fields.Datetime.now():
|
if ('website_published' in vals and 'published_date' not in vals and
|
||||||
|
(not post.published_date or post.published_date <= fields.Datetime.now())):
|
||||||
copy_vals['published_date'] = vals['website_published'] and fields.Datetime.now() or False
|
copy_vals['published_date'] = vals['website_published'] and fields.Datetime.now() or False
|
||||||
result &= super(BlogPost, self).write(copy_vals)
|
result &= super(BlogPost, self).write(copy_vals)
|
||||||
self._check_for_publication(vals)
|
self._check_for_publication(vals)
|
||||||
|
|||||||
@@ -562,14 +562,14 @@
|
|||||||
<title t-esc="blog.name"/>
|
<title t-esc="blog.name"/>
|
||||||
<link t-att-href="'%s/blog/%s' % (base_url ,blog.id)"/>
|
<link t-att-href="'%s/blog/%s' % (base_url ,blog.id)"/>
|
||||||
<id t-esc="'%s/blog/%s' % (base_url, blog.id)"/>
|
<id t-esc="'%s/blog/%s' % (base_url, blog.id)"/>
|
||||||
<updated t-esc="(posts[0] if posts else blog).post_date.replace(' ', 'T') + 'Z'"/>
|
<updated t-esc="str((posts[0] if posts else blog).post_date).replace(' ', 'T') + 'Z'"/>
|
||||||
<entry t-foreach="posts" t-as="post">
|
<entry t-foreach="posts" t-as="post">
|
||||||
<title t-esc="post.name"/>
|
<title t-esc="post.name"/>
|
||||||
<link t-att-href="'%s%s' % (base_url, post.website_url)"/>
|
<link t-att-href="'%s%s' % (base_url, post.website_url)"/>
|
||||||
<id t-esc="'%s%s' % (base_url, post.website_url)"/>
|
<id t-esc="'%s%s' % (base_url, post.website_url)"/>
|
||||||
<author><name t-esc="post.sudo().author_id.name"/></author>
|
<author><name t-esc="post.sudo().author_id.name"/></author>
|
||||||
<summary t-esc="html2plaintext(post.teaser)"/>
|
<summary t-esc="html2plaintext(post.teaser)"/>
|
||||||
<updated t-esc="post.post_date.replace(' ', 'T') + 'Z'"/>
|
<updated t-esc="str(post.post_date).replace(' ', 'T') + 'Z'"/>
|
||||||
</entry>
|
</entry>
|
||||||
</feed>
|
</feed>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ class Event(models.Model):
|
|||||||
|
|
||||||
if not event.date_begin or not event.date_end:
|
if not event.date_begin or not event.date_end:
|
||||||
raise UserError(_("No date has been specified for the event, no file will be generated."))
|
raise UserError(_("No date has been specified for the event, no file will be generated."))
|
||||||
cal_event.add('created').value = fields.Datetime.from_string(fields.Datetime.now()).replace(tzinfo=pytz.timezone('UTC'))
|
cal_event.add('created').value = fields.Datetime.now().replace(tzinfo=pytz.timezone('UTC'))
|
||||||
cal_event.add('dtstart').value = fields.Datetime.from_string(event.date_begin).replace(tzinfo=pytz.timezone('UTC'))
|
cal_event.add('dtstart').value = fields.Datetime.from_string(event.date_begin).replace(tzinfo=pytz.timezone('UTC'))
|
||||||
cal_event.add('dtend').value = fields.Datetime.from_string(event.date_end).replace(tzinfo=pytz.timezone('UTC'))
|
cal_event.add('dtend').value = fields.Datetime.from_string(event.date_end).replace(tzinfo=pytz.timezone('UTC'))
|
||||||
cal_event.add('summary').value = event.name
|
cal_event.add('summary').value = event.name
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ class WebsiteForum(http.Controller):
|
|||||||
values.update({
|
values.update({
|
||||||
'main_object': question,
|
'main_object': question,
|
||||||
'question': question,
|
'question': question,
|
||||||
'can_bump': (question.forum_id.allow_bump and not question.child_ids and (datetime.today() - datetime.strptime(question.write_date, tools.DEFAULT_SERVER_DATETIME_FORMAT)).days > 9),
|
'can_bump': (question.forum_id.allow_bump and not question.child_ids and (datetime.today() - question.write_date).days > 9),
|
||||||
'header': {'question_data': True},
|
'header': {'question_data': True},
|
||||||
'filters': filters,
|
'filters': filters,
|
||||||
'reversed': reversed,
|
'reversed': reversed,
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ class Post(models.Model):
|
|||||||
@api.depends('vote_count', 'forum_id.relevancy_post_vote', 'forum_id.relevancy_time_decay')
|
@api.depends('vote_count', 'forum_id.relevancy_post_vote', 'forum_id.relevancy_time_decay')
|
||||||
def _compute_relevancy(self):
|
def _compute_relevancy(self):
|
||||||
if self.create_date:
|
if self.create_date:
|
||||||
days = (datetime.today() - datetime.strptime(self.create_date, tools.DEFAULT_SERVER_DATETIME_FORMAT)).days
|
days = (datetime.today() - self.create_date).days
|
||||||
self.relevancy = math.copysign(1, self.vote_count) * (abs(self.vote_count - 1) ** self.forum_id.relevancy_post_vote / (days + 2) ** self.forum_id.relevancy_time_decay)
|
self.relevancy = math.copysign(1, self.vote_count) * (abs(self.vote_count - 1) ** self.forum_id.relevancy_post_vote / (days + 2) ** self.forum_id.relevancy_time_decay)
|
||||||
else:
|
else:
|
||||||
self.relevancy = 0
|
self.relevancy = 0
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class SaleOrder(models.Model):
|
|||||||
@api.depends('team_id.team_type', 'date_order', 'order_line', 'state', 'partner_id')
|
@api.depends('team_id.team_type', 'date_order', 'order_line', 'state', 'partner_id')
|
||||||
def _compute_abandoned_cart(self):
|
def _compute_abandoned_cart(self):
|
||||||
abandoned_delay = float(self.env['ir.config_parameter'].sudo().get_param('website_sale.cart_abandoned_delay', '1.0'))
|
abandoned_delay = float(self.env['ir.config_parameter'].sudo().get_param('website_sale.cart_abandoned_delay', '1.0'))
|
||||||
abandoned_datetime = fields.Datetime.to_string(datetime.utcnow() - relativedelta(hours=abandoned_delay))
|
abandoned_datetime = datetime.utcnow() - relativedelta(hours=abandoned_delay)
|
||||||
for order in self:
|
for order in self:
|
||||||
domain = order.date_order <= abandoned_datetime and order.team_id.team_type == 'website' and order.state == 'draft' and order.partner_id.id != self.env.ref('base.public_partner').id and order.order_line
|
domain = order.date_order <= abandoned_datetime and order.team_id.team_type == 'website' and order.state == 'draft' and order.partner_id.id != self.env.ref('base.public_partner').id and order.order_line
|
||||||
order.is_abandoned_cart = bool(domain)
|
order.is_abandoned_cart = bool(domain)
|
||||||
|
|||||||
@@ -429,7 +429,7 @@
|
|||||||
<div id="o-carousel-product" class="carousel slide" data-ride="carousel" data-interval="0">
|
<div id="o-carousel-product" class="carousel slide" data-ride="carousel" data-interval="0">
|
||||||
<div class="carousel-outer">
|
<div class="carousel-outer">
|
||||||
<div class="carousel-inner">
|
<div class="carousel-inner">
|
||||||
<div t-if="variant_img" class="carousel-item active" itemprop="image" t-field="product[:1].product_variant_id.image" t-options="{'widget': 'image', 'class': 'product_detail_img js_variant_img', 'alt-field': 'name', 'zoom': 'image', 'unique': product['__last_update'] + (product.product_variant_id['__last_update'] or '')}"/>
|
<div t-if="variant_img" class="carousel-item active" itemprop="image" t-field="product[:1].product_variant_id.image" t-options="{'widget': 'image', 'class': 'product_detail_img js_variant_img', 'alt-field': 'name', 'zoom': 'image', 'unique': str(product['__last_update']) + (str(product.product_variant_id['__last_update']) or '')}"/>
|
||||||
<div t-attf-class="carousel-item#{'' if variant_img else ' active'}" itemprop="image" t-field="product.image" t-options="{'widget': 'image', 'class': 'product_detail_img', 'alt-field': 'name', 'zoom': 'image', 'unique': product['__last_update']}"/>
|
<div t-attf-class="carousel-item#{'' if variant_img else ' active'}" itemprop="image" t-field="product.image" t-options="{'widget': 'image', 'class': 'product_detail_img', 'alt-field': 'name', 'zoom': 'image', 'unique': product['__last_update']}"/>
|
||||||
<t t-if="len(image_ids)" t-foreach="image_ids" t-as="pimg">
|
<t t-if="len(image_ids)" t-foreach="image_ids" t-as="pimg">
|
||||||
<div class="carousel-item" t-field="pimg.image" t-options='{"widget": "image", "class": "product_detail_img", "alt-field": "name", "zoom": "image" }'/>
|
<div class="carousel-item" t-field="pimg.image" t-options='{"widget": "image", "class": "product_detail_img", "alt-field": "name", "zoom": "image" }'/>
|
||||||
|
|||||||
+46
-2
@@ -866,13 +866,57 @@ Basic fields
|
|||||||
.. autoclass:: odoo.fields.Html
|
.. autoclass:: odoo.fields.Html
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. _reference/orm/fields/date_datetime:
|
||||||
|
|
||||||
|
Date and Datetime fields
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Dates and Datetimes are very important fields in any kind of business
|
||||||
|
application, they are heavily used in many popular Odoo applications such as
|
||||||
|
logistics or accounting and their misuse can create invisible yet painful
|
||||||
|
bugs, this excerpt aims to provide Odoo developers with the knowledge required
|
||||||
|
to avoid misusing these fields.
|
||||||
|
|
||||||
|
When assigning a value to a Date/Datetime field, the following options are valid:
|
||||||
|
* A string in the proper server format **(YYYY-MM-DD)** for Date fields,
|
||||||
|
**(YYYY-MM-DD HH:MM:SS)** for Datetime fields.
|
||||||
|
* A `date` or `datetime` object.
|
||||||
|
* `False` or `None`.
|
||||||
|
|
||||||
|
If not sure of the type of the value being assigned to a Date/Datetime object,
|
||||||
|
the best course of action is to pass the value to
|
||||||
|
:func:`~odoo.fields.Date.to_date` or :func:`~odoo.fields.Datetime.to_datetime`
|
||||||
|
which will attempt to convert the value to a date or datetime object
|
||||||
|
respectively, which can then be assigned to the field in question.
|
||||||
|
|
||||||
|
.. admonition:: Example
|
||||||
|
|
||||||
|
To parse date/datetimes coming from external sources::
|
||||||
|
|
||||||
|
fields.Date.to_date(self._context.get('date_from'))
|
||||||
|
|
||||||
|
Date / Datetime comparison best practices:
|
||||||
|
* Date fields can **only** be compared to date objects.
|
||||||
|
* Datetime fields can **only** be compared to datetime objects.
|
||||||
|
|
||||||
|
.. warning:: Strings representing dates and datetimes can be compared
|
||||||
|
between each other, however the result may not be the expected
|
||||||
|
result, as a datetime string will always be greater than a
|
||||||
|
date string, therefore this practice is **heavily**
|
||||||
|
discouraged.
|
||||||
|
|
||||||
|
Common operations with dates and datetimes such as addition, substraction or
|
||||||
|
fetching the start/end of a period are exposed through both
|
||||||
|
:class:`~odoo.fields.Date` and :class:`~odoo.fields.Datetime`.
|
||||||
|
These helpers are also available by importing `odoo.tools.date_utils`.
|
||||||
|
|
||||||
.. autoclass:: odoo.fields.Date
|
.. autoclass:: odoo.fields.Date
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
:members: today, context_today, from_string, to_string
|
:members: today, context_today, to_date, to_string, start_of, end_of, add, subtract
|
||||||
|
|
||||||
.. autoclass:: odoo.fields.Datetime
|
.. autoclass:: odoo.fields.Datetime
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
:members: now, context_timestamp, from_string, to_string
|
:members: now, today, context_timestamp, to_datetime, to_string, start_of, end_of, add, subtract
|
||||||
|
|
||||||
.. _reference/orm/fields/relational:
|
.. _reference/orm/fields/relational:
|
||||||
|
|
||||||
|
|||||||
@@ -358,7 +358,7 @@ class AssetsBundle(object):
|
|||||||
attachments = self.env['ir.attachment'].sudo().search(assets_domain)
|
attachments = self.env['ir.attachment'].sudo().search(assets_domain)
|
||||||
for attachment in attachments:
|
for attachment in attachments:
|
||||||
asset = assets[attachment.url]
|
asset = assets[attachment.url]
|
||||||
if asset.last_modified > fields.Datetime.from_string(attachment['__last_update']):
|
if asset.last_modified > attachment['__last_update']:
|
||||||
outdated = True
|
outdated = True
|
||||||
break
|
break
|
||||||
if asset._content is None:
|
if asset._content is None:
|
||||||
@@ -539,12 +539,7 @@ class WebAsset(object):
|
|||||||
if self._filename:
|
if self._filename:
|
||||||
return datetime.fromtimestamp(os.path.getmtime(self._filename))
|
return datetime.fromtimestamp(os.path.getmtime(self._filename))
|
||||||
elif self._ir_attach:
|
elif self._ir_attach:
|
||||||
server_format = tools.DEFAULT_SERVER_DATETIME_FORMAT
|
return self._ir_attach['__last_update']
|
||||||
last_update = self._ir_attach['__last_update']
|
|
||||||
try:
|
|
||||||
return datetime.strptime(last_update, server_format + '.%f')
|
|
||||||
except ValueError:
|
|
||||||
return datetime.strptime(last_update, server_format)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return datetime(1970, 1, 1)
|
return datetime(1970, 1, 1)
|
||||||
|
|||||||
@@ -139,12 +139,7 @@ class IrHttp(models.AbstractModel):
|
|||||||
return werkzeug.utils.redirect(name, 301)
|
return werkzeug.utils.redirect(name, 301)
|
||||||
|
|
||||||
response = werkzeug.wrappers.Response()
|
response = werkzeug.wrappers.Response()
|
||||||
server_format = tools.DEFAULT_SERVER_DATETIME_FORMAT
|
response.last_modified = wdate
|
||||||
try:
|
|
||||||
response.last_modified = datetime.datetime.strptime(wdate, server_format + '.%f')
|
|
||||||
except ValueError:
|
|
||||||
# just in case we have a timestamp without microseconds
|
|
||||||
response.last_modified = datetime.datetime.strptime(wdate, server_format)
|
|
||||||
|
|
||||||
response.set_etag(checksum)
|
response.set_etag(checksum)
|
||||||
response.make_conditional(request.httprequest)
|
response.make_conditional(request.httprequest)
|
||||||
|
|||||||
@@ -196,9 +196,9 @@ class IrSequence(models.Model):
|
|||||||
def _interpolation_dict():
|
def _interpolation_dict():
|
||||||
now = range_date = effective_date = datetime.now(pytz.timezone(self._context.get('tz') or 'UTC'))
|
now = range_date = effective_date = datetime.now(pytz.timezone(self._context.get('tz') or 'UTC'))
|
||||||
if self._context.get('ir_sequence_date'):
|
if self._context.get('ir_sequence_date'):
|
||||||
effective_date = datetime.strptime(self._context.get('ir_sequence_date'), '%Y-%m-%d')
|
effective_date = fields.Datetime.from_string(self._context.get('ir_sequence_date'))
|
||||||
if self._context.get('ir_sequence_date_range'):
|
if self._context.get('ir_sequence_date_range'):
|
||||||
range_date = datetime.strptime(self._context.get('ir_sequence_date_range'), '%Y-%m-%d')
|
range_date = fields.Datetime.from_string(self._context.get('ir_sequence_date_range'))
|
||||||
|
|
||||||
sequences = {
|
sequences = {
|
||||||
'year': '%Y', 'month': '%m', 'day': '%d', 'y': '%y', 'doy': '%j', 'woy': '%W',
|
'year': '%Y', 'month': '%m', 'day': '%d', 'y': '%y', 'doy': '%j', 'woy': '%W',
|
||||||
@@ -230,12 +230,10 @@ class IrSequence(models.Model):
|
|||||||
date_to = '{}-12-31'.format(year)
|
date_to = '{}-12-31'.format(year)
|
||||||
date_range = self.env['ir.sequence.date_range'].search([('sequence_id', '=', self.id), ('date_from', '>=', date), ('date_from', '<=', date_to)], order='date_from desc', limit=1)
|
date_range = self.env['ir.sequence.date_range'].search([('sequence_id', '=', self.id), ('date_from', '>=', date), ('date_from', '<=', date_to)], order='date_from desc', limit=1)
|
||||||
if date_range:
|
if date_range:
|
||||||
date_to = datetime.strptime(date_range.date_from, '%Y-%m-%d') + timedelta(days=-1)
|
date_to = date_range.date_from + timedelta(days=-1)
|
||||||
date_to = date_to.strftime('%Y-%m-%d')
|
|
||||||
date_range = self.env['ir.sequence.date_range'].search([('sequence_id', '=', self.id), ('date_to', '>=', date_from), ('date_to', '<=', date)], order='date_to desc', limit=1)
|
date_range = self.env['ir.sequence.date_range'].search([('sequence_id', '=', self.id), ('date_to', '>=', date_from), ('date_to', '<=', date)], order='date_to desc', limit=1)
|
||||||
if date_range:
|
if date_range:
|
||||||
date_from = datetime.strptime(date_range.date_to, '%Y-%m-%d') + timedelta(days=1)
|
date_from = date_range.date_to + timedelta(days=1)
|
||||||
date_from = date_from.strftime('%Y-%m-%d')
|
|
||||||
seq_date_range = self.env['ir.sequence.date_range'].sudo().create({
|
seq_date_range = self.env['ir.sequence.date_range'].sudo().create({
|
||||||
'date_from': date_from,
|
'date_from': date_from,
|
||||||
'date_to': date_to,
|
'date_to': date_to,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class TestIrSequenceDateRangeStandard(SingleTransactionCase):
|
|||||||
def test_ir_sequence_date_range_2_change_dates(self):
|
def test_ir_sequence_date_range_2_change_dates(self):
|
||||||
""" Draw numbers to create a first subsequence then change its date range. Then, try to draw a new number adn check a new subsequence was correctly created. """
|
""" Draw numbers to create a first subsequence then change its date range. Then, try to draw a new number adn check a new subsequence was correctly created. """
|
||||||
year = date.today().year - 1
|
year = date.today().year - 1
|
||||||
january = lambda d: date(year, 1, d).strftime(DATE_FORMAT)
|
january = lambda d: date(year, 1, d)
|
||||||
|
|
||||||
seq16 = self.env['ir.sequence'].with_context({'ir_sequence_date': january(16)})
|
seq16 = self.env['ir.sequence'].with_context({'ir_sequence_date': january(16)})
|
||||||
n = seq16.next_by_code('test_sequence_date_range')
|
n = seq16.next_by_code('test_sequence_date_range')
|
||||||
@@ -63,7 +63,7 @@ class TestIrSequenceDateRangeNoGap(SingleTransactionCase):
|
|||||||
def test_ir_sequence_date_range_2_change_dates(self):
|
def test_ir_sequence_date_range_2_change_dates(self):
|
||||||
""" Draw numbers to create a first subsequence then change its date range. Then, try to draw a new number adn check a new subsequence was correctly created. """
|
""" Draw numbers to create a first subsequence then change its date range. Then, try to draw a new number adn check a new subsequence was correctly created. """
|
||||||
year = date.today().year - 1
|
year = date.today().year - 1
|
||||||
january = lambda d: date(year, 1, d).strftime(DATE_FORMAT)
|
january = lambda d: date(year, 1, d)
|
||||||
|
|
||||||
seq16 = self.env['ir.sequence'].with_context({'ir_sequence_date': january(16)})
|
seq16 = self.env['ir.sequence'].with_context({'ir_sequence_date': january(16)})
|
||||||
n = seq16.next_by_code('test_sequence_date_range_2')
|
n = seq16.next_by_code('test_sequence_date_range_2')
|
||||||
@@ -111,7 +111,7 @@ class TestIrSequenceDateRangeChangeImplementation(SingleTransactionCase):
|
|||||||
def test_ir_sequence_date_range_2_use(self):
|
def test_ir_sequence_date_range_2_use(self):
|
||||||
""" Make some use of the sequences to create some subsequences """
|
""" Make some use of the sequences to create some subsequences """
|
||||||
year = date.today().year - 1
|
year = date.today().year - 1
|
||||||
january = lambda d: date(year, 1, d).strftime(DATE_FORMAT)
|
january = lambda d: date(year, 1, d)
|
||||||
|
|
||||||
seq = self.env['ir.sequence']
|
seq = self.env['ir.sequence']
|
||||||
seq16 = self.env['ir.sequence'].with_context({'ir_sequence_date': january(16)})
|
seq16 = self.env['ir.sequence'].with_context({'ir_sequence_date': january(16)})
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import json
|
|||||||
import pkgutil
|
import pkgutil
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from odoo import fields
|
||||||
from odoo.tests import common
|
from odoo.tests import common
|
||||||
from odoo.tools.misc import mute_logger
|
from odoo.tools.misc import mute_logger
|
||||||
|
|
||||||
@@ -1065,7 +1066,7 @@ class test_datetime(ImporterCase):
|
|||||||
['value'], [['2012-02-03 11:11:11']], {'tz': 'Pacific/Kiritimati'})
|
['value'], [['2012-02-03 11:11:11']], {'tz': 'Pacific/Kiritimati'})
|
||||||
self.assertFalse(result['messages'])
|
self.assertFalse(result['messages'])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
values(self.read(domain=[('id', 'in', result['ids'])])),
|
[fields.Datetime.to_string(value['value']) for value in self.read(domain=[('id', 'in', result['ids'])])],
|
||||||
['2012-02-02 21:11:11'])
|
['2012-02-02 21:11:11'])
|
||||||
|
|
||||||
# UTC-0930
|
# UTC-0930
|
||||||
@@ -1073,7 +1074,7 @@ class test_datetime(ImporterCase):
|
|||||||
['value'], [['2012-02-03 11:11:11']], {'tz': 'Pacific/Marquesas'})
|
['value'], [['2012-02-03 11:11:11']], {'tz': 'Pacific/Marquesas'})
|
||||||
self.assertFalse(result['messages'])
|
self.assertFalse(result['messages'])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
values(self.read(domain=[('id', 'in', result['ids'])])),
|
[fields.Datetime.to_string(value['value']) for value in self.read(domain=[('id', 'in', result['ids'])])],
|
||||||
['2012-02-03 20:41:11'])
|
['2012-02-03 20:41:11'])
|
||||||
|
|
||||||
def test_usertz(self):
|
def test_usertz(self):
|
||||||
@@ -1087,7 +1088,7 @@ class test_datetime(ImporterCase):
|
|||||||
['value'], [['2012-02-03 11:11:11']])
|
['value'], [['2012-02-03 11:11:11']])
|
||||||
self.assertFalse(result['messages'])
|
self.assertFalse(result['messages'])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
values(self.read(domain=[('id', 'in', result['ids'])])),
|
[fields.Datetime.to_string(value['value']) for value in self.read(domain=[('id', 'in', result['ids'])])],
|
||||||
['2012-02-03 01:11:11'])
|
['2012-02-03 01:11:11'])
|
||||||
|
|
||||||
def test_notz(self):
|
def test_notz(self):
|
||||||
@@ -1099,7 +1100,7 @@ class test_datetime(ImporterCase):
|
|||||||
result = self.import_(['value'], [['2012-02-03 11:11:11']])
|
result = self.import_(['value'], [['2012-02-03 11:11:11']])
|
||||||
self.assertFalse(result['messages'])
|
self.assertFalse(result['messages'])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
values(self.read(domain=[('id', 'in', result['ids'])])),
|
[fields.Datetime.to_string(value['value']) for value in self.read(domain=[('id', 'in', result['ids'])])],
|
||||||
['2012-02-03 11:11:11'])
|
['2012-02-03 11:11:11'])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
#
|
#
|
||||||
# test cases for new-style fields
|
# test cases for new-style fields
|
||||||
#
|
#
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime, time
|
||||||
|
|
||||||
from odoo.exceptions import AccessError, UserError, except_orm
|
from odoo import fields
|
||||||
|
from odoo.exceptions import AccessError, UserError
|
||||||
from odoo.tests import common
|
from odoo.tests import common
|
||||||
from odoo.tools import mute_logger, float_repr, pycompat
|
from odoo.tools import mute_logger, float_repr, pycompat
|
||||||
|
from odoo.tools.date_utils import add, subtract, start_of, end_of
|
||||||
|
|
||||||
|
|
||||||
class TestFields(common.TransactionCase):
|
class TestFields(common.TransactionCase):
|
||||||
@@ -484,20 +486,117 @@ class TestFields(common.TransactionCase):
|
|||||||
record.date = None
|
record.date = None
|
||||||
self.assertFalse(record.date)
|
self.assertFalse(record.date)
|
||||||
|
|
||||||
# one may assign date and datetime objects
|
# one may assign date but not datetime objects
|
||||||
record.date = date(2012, 5, 1)
|
record.date = date(2012, 5, 1)
|
||||||
self.assertEqual(record.date, '2012-05-01')
|
self.assertEqual(record.date, date(2012, 5, 1))
|
||||||
|
|
||||||
record.date = datetime(2012, 5, 1, 10, 45, 00)
|
with self.assertRaises(TypeError):
|
||||||
self.assertEqual(record.date, '2012-05-01')
|
record.date = datetime(2012, 5, 1, 10, 45, 0)
|
||||||
|
|
||||||
# one may assign dates in the default format, and it must be checked
|
# one may assign dates and datetime in the default format, and it must be checked
|
||||||
record.date = '2012-05-01'
|
record.date = '2012-05-01'
|
||||||
self.assertEqual(record.date, '2012-05-01')
|
self.assertEqual(record.date, date(2012, 5, 1))
|
||||||
|
|
||||||
|
record.date = "2012-05-01 10:45:00"
|
||||||
|
self.assertEqual(record.date, date(2012, 5, 1))
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
record.date = '12-5-1'
|
record.date = '12-5-1'
|
||||||
|
|
||||||
|
for i in range(0, 10):
|
||||||
|
self.assertEqual(fields.Datetime.now().microsecond, 0)
|
||||||
|
|
||||||
|
def test_21_date_datetime_helpers(self):
|
||||||
|
""" test date/datetime fields helpers """
|
||||||
|
_date = fields.Date.from_string("2077-10-23")
|
||||||
|
_datetime = fields.Datetime.from_string("2077-10-23 09:42:00")
|
||||||
|
|
||||||
|
# addition
|
||||||
|
self.assertEqual(add(_date, days=5), date(2077, 10, 28))
|
||||||
|
self.assertEqual(add(_datetime, seconds=10), datetime(2077, 10, 23, 9, 42, 10))
|
||||||
|
|
||||||
|
# subtraction
|
||||||
|
self.assertEqual(subtract(_date, months=1), date(2077, 9, 23))
|
||||||
|
self.assertEqual(subtract(_datetime, hours=2), datetime(2077, 10, 23, 7, 42, 0))
|
||||||
|
|
||||||
|
# start_of
|
||||||
|
# year
|
||||||
|
self.assertEqual(start_of(_date, 'year'), date(2077, 1, 1))
|
||||||
|
self.assertEqual(start_of(_datetime, 'year'), datetime(2077, 1, 1))
|
||||||
|
|
||||||
|
# quarter
|
||||||
|
q1 = date(2077, 1, 1)
|
||||||
|
q2 = date(2077, 4, 1)
|
||||||
|
q3 = date(2077, 7, 1)
|
||||||
|
q4 = date(2077, 10, 1)
|
||||||
|
self.assertEqual(start_of(_date.replace(month=3), 'quarter'), q1)
|
||||||
|
self.assertEqual(start_of(_date.replace(month=5), 'quarter'), q2)
|
||||||
|
self.assertEqual(start_of(_date.replace(month=7), 'quarter'), q3)
|
||||||
|
self.assertEqual(start_of(_date, 'quarter'), q4)
|
||||||
|
self.assertEqual(start_of(_datetime, 'quarter'), datetime.combine(q4, time.min))
|
||||||
|
|
||||||
|
# month
|
||||||
|
self.assertEqual(start_of(_date, 'month'), date(2077, 10, 1))
|
||||||
|
self.assertEqual(start_of(_datetime, 'month'), datetime(2077, 10, 1))
|
||||||
|
|
||||||
|
# week
|
||||||
|
self.assertEqual(start_of(_date, 'week'), date(2077, 10, 18))
|
||||||
|
self.assertEqual(start_of(_datetime, 'week'), datetime(2077, 10, 18))
|
||||||
|
|
||||||
|
# day
|
||||||
|
self.assertEqual(start_of(_date, 'day'), _date)
|
||||||
|
self.assertEqual(start_of(_datetime, 'day'), _datetime.replace(hour=0, minute=0, second=0))
|
||||||
|
|
||||||
|
# hour
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
start_of(_date, 'hour')
|
||||||
|
self.assertEqual(start_of(_datetime, 'hour'), _datetime.replace(minute=0, second=0))
|
||||||
|
|
||||||
|
# invalid
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
start_of(_datetime, 'poop')
|
||||||
|
|
||||||
|
# end_of
|
||||||
|
# year
|
||||||
|
self.assertEqual(end_of(_date, 'year'), _date.replace(month=12, day=31))
|
||||||
|
self.assertEqual(end_of(_datetime, 'year'),
|
||||||
|
datetime.combine(_date.replace(month=12, day=31), time.max))
|
||||||
|
|
||||||
|
# quarter
|
||||||
|
q1 = date(2077, 3, 31)
|
||||||
|
q2 = date(2077, 6, 30)
|
||||||
|
q3 = date(2077, 9, 30)
|
||||||
|
q4 = date(2077, 12, 31)
|
||||||
|
self.assertEqual(end_of(_date.replace(month=2), 'quarter'), q1)
|
||||||
|
self.assertEqual(end_of(_date.replace(month=4), 'quarter'), q2)
|
||||||
|
self.assertEqual(end_of(_date.replace(month=9), 'quarter'), q3)
|
||||||
|
self.assertEqual(end_of(_date, 'quarter'), q4)
|
||||||
|
self.assertEqual(end_of(_datetime, 'quarter'), datetime.combine(q4, time.max))
|
||||||
|
|
||||||
|
# month
|
||||||
|
self.assertEqual(end_of(_date, 'month'), _date.replace(day=31))
|
||||||
|
self.assertEqual(end_of(_datetime, 'month'),
|
||||||
|
datetime.combine(date(2077, 10, 31), time.max))
|
||||||
|
|
||||||
|
# week
|
||||||
|
self.assertEqual(end_of(_date, 'week'), date(2077, 10, 24))
|
||||||
|
self.assertEqual(end_of(_datetime, 'week'),
|
||||||
|
datetime.combine(datetime(2077, 10, 24), time.max))
|
||||||
|
|
||||||
|
# day
|
||||||
|
self.assertEqual(end_of(_date, 'day'), _date)
|
||||||
|
self.assertEqual(end_of(_datetime, 'day'), datetime.combine(_datetime, time.max))
|
||||||
|
|
||||||
|
# hour
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
end_of(_date, 'hour')
|
||||||
|
self.assertEqual(end_of(_datetime, 'hour'),
|
||||||
|
datetime.combine(_datetime, time.max).replace(hour=_datetime.hour))
|
||||||
|
|
||||||
|
# invalid
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
end_of(_datetime, 'crap')
|
||||||
|
|
||||||
def test_22_selection(self):
|
def test_22_selection(self):
|
||||||
""" test selection fields """
|
""" test selection fields """
|
||||||
record = self.env['test_new_api.mixed'].create({})
|
record = self.env['test_new_api.mixed'].create({})
|
||||||
|
|||||||
+116
-53
@@ -4,7 +4,8 @@
|
|||||||
""" High-level objects for fields. """
|
""" High-level objects for fields. """
|
||||||
|
|
||||||
from collections import OrderedDict, defaultdict
|
from collections import OrderedDict, defaultdict
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime, time
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
import itertools
|
import itertools
|
||||||
@@ -21,7 +22,8 @@ except ImportError:
|
|||||||
import psycopg2
|
import psycopg2
|
||||||
|
|
||||||
from .sql_db import LazyCursor
|
from .sql_db import LazyCursor
|
||||||
from .tools import float_repr, float_round, frozendict, html_sanitize, human_size, pg_varchar, ustr, OrderedSet, pycompat, sql
|
from .tools import float_repr, float_round, frozendict, html_sanitize, human_size, pg_varchar,\
|
||||||
|
ustr, OrderedSet, pycompat, sql, date_utils
|
||||||
from .tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT
|
from .tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT
|
||||||
from .tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT
|
from .tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT
|
||||||
from .tools.translate import html_translate, _
|
from .tools.translate import html_translate, _
|
||||||
@@ -1516,23 +1518,30 @@ class Date(Field):
|
|||||||
column_type = ('date', 'date')
|
column_type = ('date', 'date')
|
||||||
column_cast_from = ('timestamp',)
|
column_cast_from = ('timestamp',)
|
||||||
|
|
||||||
|
start_of = staticmethod(date_utils.start_of)
|
||||||
|
end_of = staticmethod(date_utils.end_of)
|
||||||
|
add = staticmethod(date_utils.add)
|
||||||
|
subtract = staticmethod(date_utils.subtract)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def today(*args):
|
def today(*args):
|
||||||
""" Return the current day in the format expected by the ORM.
|
""" Return the current day in the format expected by the ORM.
|
||||||
This function may be used to compute default values.
|
This function may be used to compute default values.
|
||||||
"""
|
"""
|
||||||
return date.today().strftime(DATE_FORMAT)
|
return date.today()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def context_today(record, timestamp=None):
|
def context_today(record, timestamp=None):
|
||||||
""" Return the current date as seen in the client's timezone in a format
|
"""
|
||||||
fit for date fields. This method may be used to compute default
|
Return the current date as seen in the client's timezone in a format
|
||||||
values.
|
fit for date fields. This method may be used to compute default
|
||||||
|
values.
|
||||||
|
|
||||||
:param datetime timestamp: optional datetime value to use instead of
|
:param record: recordset from which the timezone will be obtained.
|
||||||
the current date and time (must be a datetime, regular dates
|
:param datetime timestamp: optional datetime value to use instead of
|
||||||
can't be converted between timezones.)
|
the current date and time (must be a datetime, regular dates
|
||||||
:rtype: str
|
can't be converted between timezones).
|
||||||
|
:rtype: date
|
||||||
"""
|
"""
|
||||||
today = timestamp or datetime.now()
|
today = timestamp or datetime.now()
|
||||||
context_today = None
|
context_today = None
|
||||||
@@ -1544,33 +1553,55 @@ class Date(Field):
|
|||||||
except Exception:
|
except Exception:
|
||||||
_logger.debug("failed to compute context/client-specific today date, using UTC value for `today`",
|
_logger.debug("failed to compute context/client-specific today date, using UTC value for `today`",
|
||||||
exc_info=True)
|
exc_info=True)
|
||||||
return (context_today or today).strftime(DATE_FORMAT)
|
return (context_today or today).date()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_string(value):
|
def to_date(value):
|
||||||
""" Convert an ORM ``value`` into a :class:`date` value. """
|
"""
|
||||||
|
Attempt to convert ``value`` to a :class:`date` object.
|
||||||
|
|
||||||
|
This function can take as input different kinds of types:
|
||||||
|
* A falsy object, in which case None will be returned.
|
||||||
|
* A string representing a date or datetime.
|
||||||
|
* A date object, in which case the object will be returned as-is.
|
||||||
|
* A datetime object, in which case it will be converted to a date object and all\
|
||||||
|
datetime-specific information will be lost (HMS, TZ, ...).
|
||||||
|
|
||||||
|
:param value: value to convert.
|
||||||
|
:return: an object representing ``value``.
|
||||||
|
:rtype: date
|
||||||
|
"""
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
|
if isinstance(value, date):
|
||||||
|
if isinstance(value, datetime):
|
||||||
|
return value.date()
|
||||||
|
return value
|
||||||
value = value[:DATE_LENGTH]
|
value = value[:DATE_LENGTH]
|
||||||
return datetime.strptime(value, DATE_FORMAT).date()
|
return datetime.strptime(value, DATE_FORMAT).date()
|
||||||
|
|
||||||
|
# kept for backwards compatibility, but consider `from_string` as deprecated, will probably
|
||||||
|
# be removed after V12
|
||||||
|
from_string = to_date
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def to_string(value):
|
def to_string(value):
|
||||||
""" Convert a :class:`date` value into the format expected by the ORM. """
|
"""
|
||||||
return value.strftime(DATE_FORMAT) if value else False
|
Convert a :class:`date` or :class:`datetime` object to a string.
|
||||||
|
|
||||||
def convert_to_column(self, value, record, values=None, validate=True):
|
:param value: value to convert.
|
||||||
return super(Date, self).convert_to_column(value or None, record, values, validate)
|
:return: a string representing ``value`` in the server's date format, if ``value`` is of
|
||||||
|
type :class:`datetime`, the hours, minute, seconds, tzinfo will be truncated.
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
return value.strftime(DATE_FORMAT) if value else False
|
||||||
|
|
||||||
def convert_to_cache(self, value, record, validate=True):
|
def convert_to_cache(self, value, record, validate=True):
|
||||||
if not value:
|
if not value:
|
||||||
return False
|
return False
|
||||||
if isinstance(value, pycompat.string_types):
|
if isinstance(value, datetime):
|
||||||
if validate:
|
raise TypeError("%s (field %s) must be string or date, not datetime." % (value, self))
|
||||||
# force parsing for validation
|
return self.from_string(value)
|
||||||
self.from_string(value)
|
|
||||||
return value[:DATE_LENGTH]
|
|
||||||
return self.to_string(value)
|
|
||||||
|
|
||||||
def convert_to_export(self, value, record):
|
def convert_to_export(self, value, record):
|
||||||
if not value:
|
if not value:
|
||||||
@@ -1583,26 +1614,40 @@ class Datetime(Field):
|
|||||||
column_type = ('timestamp', 'timestamp')
|
column_type = ('timestamp', 'timestamp')
|
||||||
column_cast_from = ('date',)
|
column_cast_from = ('date',)
|
||||||
|
|
||||||
|
start_of = staticmethod(date_utils.start_of)
|
||||||
|
end_of = staticmethod(date_utils.end_of)
|
||||||
|
add = staticmethod(date_utils.add)
|
||||||
|
subtract = staticmethod(date_utils.subtract)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def now(*args):
|
def now(*args):
|
||||||
""" Return the current day and time in the format expected by the ORM.
|
""" Return the current day and time in the format expected by the ORM.
|
||||||
This function may be used to compute default values.
|
This function may be used to compute default values.
|
||||||
"""
|
"""
|
||||||
return datetime.now().strftime(DATETIME_FORMAT)
|
# microseconds must be annihilated as they don't comply with the server datetime format
|
||||||
|
return datetime.now().replace(microsecond=0)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def today(*args):
|
||||||
|
"""
|
||||||
|
Return the current day, at midnight (00:00:00).
|
||||||
|
"""
|
||||||
|
return Datetime.now().replace(hour=0, minute=0, second=0)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def context_timestamp(record, timestamp):
|
def context_timestamp(record, timestamp):
|
||||||
"""Returns the given timestamp converted to the client's timezone.
|
"""
|
||||||
This method is *not* meant for use as a default initializer,
|
Returns the given timestamp converted to the client's timezone.
|
||||||
because datetime fields are automatically converted upon
|
This method is *not* meant for use as a default initializer,
|
||||||
display on client side. For default values :meth:`fields.datetime.now`
|
because datetime fields are automatically converted upon
|
||||||
should be used instead.
|
display on client side. For default values, :meth:`fields.Datetime.now`
|
||||||
|
should be used instead.
|
||||||
|
|
||||||
:param datetime timestamp: naive datetime value (expressed in UTC)
|
:param record: recordset from which the timezone will be obtained.
|
||||||
to be converted to the client timezone
|
:param datetime timestamp: naive datetime value (expressed in UTC)
|
||||||
:rtype: datetime
|
to be converted to the client timezone.
|
||||||
:return: timestamp converted to timezone-aware datetime in context
|
:rtype: datetime
|
||||||
timezone
|
:return: timestamp converted to timezone-aware datetime in context timezone.
|
||||||
"""
|
"""
|
||||||
assert isinstance(timestamp, datetime), 'Datetime instance expected'
|
assert isinstance(timestamp, datetime), 'Datetime instance expected'
|
||||||
tz_name = record._context.get('tz') or record.env.user.tz
|
tz_name = record._context.get('tz') or record.env.user.tz
|
||||||
@@ -1618,35 +1663,53 @@ class Datetime(Field):
|
|||||||
return utc_timestamp
|
return utc_timestamp
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_string(value):
|
def to_datetime(value):
|
||||||
""" Convert an ORM ``value`` into a :class:`datetime` value. """
|
"""
|
||||||
|
Convert an ORM ``value`` into a :class:`datetime` value.
|
||||||
|
|
||||||
|
This function can take as input different kinds of types:
|
||||||
|
* A falsy object, in which case None will be returned.
|
||||||
|
* A string representing a date or datetime.
|
||||||
|
* A datetime object, in which case the object will be returned as-is.
|
||||||
|
* A date object, in which case it will be converted to a datetime object.
|
||||||
|
|
||||||
|
:param value: value to convert.
|
||||||
|
:return: an object representing ``value``.
|
||||||
|
:rtype: datetime
|
||||||
|
"""
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
value = value[:DATETIME_LENGTH]
|
if isinstance(value, date):
|
||||||
if len(value) == DATE_LENGTH:
|
if isinstance(value, datetime):
|
||||||
value += " 00:00:00"
|
return value
|
||||||
return datetime.strptime(value, DATETIME_FORMAT)
|
return datetime.combine(value, time.min)
|
||||||
|
try:
|
||||||
|
return datetime.strptime(value[:DATETIME_LENGTH], DATETIME_FORMAT)
|
||||||
|
except ValueError:
|
||||||
|
return datetime.strptime(value, DATE_FORMAT)
|
||||||
|
|
||||||
|
# kept for backwards compatibility, but consider `from_string` as deprecated, will probably
|
||||||
|
# be removed after V12
|
||||||
|
from_string = to_datetime
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def to_string(value):
|
def to_string(value):
|
||||||
""" Convert a :class:`datetime` value into the format expected by the ORM. """
|
"""
|
||||||
return value.strftime(DATETIME_FORMAT) if value else False
|
Convert a :class:`datetime` or :class:`date` object to a string.
|
||||||
|
|
||||||
def convert_to_column(self, value, record, values=None, validate=True):
|
:param value: value to convert.
|
||||||
return super(Datetime, self).convert_to_column(value or None, record, values, validate)
|
:return: a string representing ``value`` in the server's datetime format, if ``value`` is
|
||||||
|
of type :class:`date`, the time portion will be midnight (00:00:00).
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
return value.strftime(DATETIME_FORMAT) if value else False
|
||||||
|
|
||||||
def convert_to_cache(self, value, record, validate=True):
|
def convert_to_cache(self, value, record, validate=True):
|
||||||
if not value:
|
if not value:
|
||||||
return False
|
return False
|
||||||
if isinstance(value, pycompat.string_types):
|
if isinstance(value, date) and not isinstance(value, datetime):
|
||||||
if validate:
|
raise TypeError("%s (field %s) must be string or datetime, not date." % (value, self))
|
||||||
# force parsing for validation
|
return self.from_string(value)
|
||||||
self.from_string(value)
|
|
||||||
value = value[:DATETIME_LENGTH]
|
|
||||||
if len(value) == DATE_LENGTH:
|
|
||||||
value += " 00:00:00"
|
|
||||||
return value
|
|
||||||
return self.to_string(value)
|
|
||||||
|
|
||||||
def convert_to_export(self, value, record):
|
def convert_to_export(self, value, record):
|
||||||
if not value:
|
if not value:
|
||||||
|
|||||||
+6
-3
@@ -25,6 +25,7 @@ from os.path import join as opj
|
|||||||
from zlib import adler32
|
from zlib import adler32
|
||||||
|
|
||||||
import babel.core
|
import babel.core
|
||||||
|
from datetime import datetime, date
|
||||||
import passlib.utils
|
import passlib.utils
|
||||||
import psycopg2
|
import psycopg2
|
||||||
import json
|
import json
|
||||||
@@ -44,10 +45,11 @@ except ImportError:
|
|||||||
psutil = None
|
psutil = None
|
||||||
|
|
||||||
import odoo
|
import odoo
|
||||||
|
from odoo import fields
|
||||||
from .service.server import memory_info
|
from .service.server import memory_info
|
||||||
from .service import security, model as service_model
|
from .service import security, model as service_model
|
||||||
from .tools.func import lazy_property
|
from .tools.func import lazy_property
|
||||||
from .tools import ustr, consteq, frozendict, pycompat, unique
|
from .tools import ustr, consteq, frozendict, pycompat, unique, date_utils
|
||||||
|
|
||||||
from .modules.module import module_manifest
|
from .modules.module import module_manifest
|
||||||
|
|
||||||
@@ -617,6 +619,7 @@ class JsonRequest(WebRequest):
|
|||||||
self.context = self.params.pop('context', dict(self.session.context))
|
self.context = self.params.pop('context', dict(self.session.context))
|
||||||
|
|
||||||
def _json_response(self, result=None, error=None):
|
def _json_response(self, result=None, error=None):
|
||||||
|
|
||||||
response = {
|
response = {
|
||||||
'jsonrpc': '2.0',
|
'jsonrpc': '2.0',
|
||||||
'id': self.jsonrequest.get('id')
|
'id': self.jsonrequest.get('id')
|
||||||
@@ -632,10 +635,10 @@ class JsonRequest(WebRequest):
|
|||||||
# We need then to manage http sessions manually.
|
# We need then to manage http sessions manually.
|
||||||
response['session_id'] = self.session.sid
|
response['session_id'] = self.session.sid
|
||||||
mime = 'application/javascript'
|
mime = 'application/javascript'
|
||||||
body = "%s(%s);" % (self.jsonp, json.dumps(response, default=ustr),)
|
body = "%s(%s);" % (self.jsonp, json.dumps(response, default=date_utils.json_default))
|
||||||
else:
|
else:
|
||||||
mime = 'application/json'
|
mime = 'application/json'
|
||||||
body = json.dumps(response, default=ustr)
|
body = json.dumps(response, default=date_utils.json_default)
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
body, status=error and error.pop('http_status', 200) or 200,
|
body, status=error and error.pop('http_status', 200) or 200,
|
||||||
|
|||||||
+16
-5
@@ -120,6 +120,7 @@ import traceback
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
from zlib import crc32
|
from zlib import crc32
|
||||||
|
|
||||||
|
from datetime import date, datetime, time
|
||||||
import odoo.modules
|
import odoo.modules
|
||||||
from odoo.tools import pycompat
|
from odoo.tools import pycompat
|
||||||
from ..models import MAGIC_COLUMNS, BaseModel
|
from ..models import MAGIC_COLUMNS, BaseModel
|
||||||
@@ -1095,12 +1096,22 @@ class expression(object):
|
|||||||
# -------------------------------------------------
|
# -------------------------------------------------
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if field.type == 'datetime' and right and len(right) == 10:
|
if field.type == 'datetime' and right:
|
||||||
if operator in ('>', '<='):
|
if isinstance(right, pycompat.string_types) and len(right) == 10:
|
||||||
right += ' 23:59:59'
|
if operator in ('>', '<='):
|
||||||
|
right += ' 23:59:59'
|
||||||
|
else:
|
||||||
|
right += ' 00:00:00'
|
||||||
|
push(create_substitution_leaf(leaf, (left, operator, right), model))
|
||||||
|
elif isinstance(right, date) and not isinstance(right, datetime):
|
||||||
|
if operator in ('>', '<='):
|
||||||
|
right = datetime.combine(right, time.max)
|
||||||
|
else:
|
||||||
|
right = datetime.combine(right, time.min)
|
||||||
|
push(create_substitution_leaf(leaf, (left, operator, right), model))
|
||||||
else:
|
else:
|
||||||
right += ' 00:00:00'
|
push_result(leaf)
|
||||||
push(create_substitution_leaf(leaf, (left, operator, right), model))
|
|
||||||
|
|
||||||
elif field.translate is True and right:
|
elif field.translate is True and right:
|
||||||
need_wildcard = operator in ('like', 'ilike', 'not like', 'not ilike')
|
need_wildcard = operator in ('like', 'ilike', 'not like', 'not ilike')
|
||||||
|
|||||||
@@ -26,12 +26,6 @@ psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
|
|||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
types_mapping = {
|
|
||||||
'date': (1082,),
|
|
||||||
'time': (1083,),
|
|
||||||
'datetime': (1114,),
|
|
||||||
}
|
|
||||||
|
|
||||||
def unbuffer(symb, cr):
|
def unbuffer(symb, cr):
|
||||||
if symb is None:
|
if symb is None:
|
||||||
return None
|
return None
|
||||||
@@ -42,8 +36,6 @@ def undecimalize(symb, cr):
|
|||||||
return None
|
return None
|
||||||
return float(symb)
|
return float(symb)
|
||||||
|
|
||||||
for name, typeoid in types_mapping.items():
|
|
||||||
psycopg2.extensions.register_type(psycopg2.extensions.new_type(typeoid, name, lambda x, cr: x))
|
|
||||||
psycopg2.extensions.register_type(psycopg2.extensions.new_type((700, 701, 1700,), 'float', undecimalize))
|
psycopg2.extensions.register_type(psycopg2.extensions.new_type((700, 701, 1700,), 'float', undecimalize))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
import math
|
import math
|
||||||
import calendar
|
import calendar
|
||||||
|
from datetime import date, datetime, time
|
||||||
|
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
|
from . import ustr
|
||||||
|
|
||||||
|
|
||||||
def get_month(date):
|
def get_month(date):
|
||||||
@@ -62,3 +64,117 @@ def get_fiscal_year(date, day=31, month=12):
|
|||||||
max_day = calendar.monthrange(date_to.year + 1, date_to.month)[1]
|
max_day = calendar.monthrange(date_to.year + 1, date_to.month)[1]
|
||||||
date_to = type(date)(date.year + 1, month, min(day, max_day))
|
date_to = type(date)(date.year + 1, month, min(day, max_day))
|
||||||
return date_from, date_to
|
return date_from, date_to
|
||||||
|
|
||||||
|
|
||||||
|
def start_of(value, granularity):
|
||||||
|
"""
|
||||||
|
Get start of a time period from a date or a datetime.
|
||||||
|
|
||||||
|
:param value: initial date or datetime.
|
||||||
|
:param granularity: type of period in string, can be year, quarter, month, week, day or hour.
|
||||||
|
:return: a date/datetime object corresponding to the start of the specified period.
|
||||||
|
"""
|
||||||
|
is_datetime = isinstance(value, datetime)
|
||||||
|
if granularity == "year":
|
||||||
|
result = value.replace(month=1, day=1)
|
||||||
|
elif granularity == "quarter":
|
||||||
|
# Q1 = Jan 1st
|
||||||
|
# Q2 = Apr 1st
|
||||||
|
# Q3 = Jul 1st
|
||||||
|
# Q4 = Oct 1st
|
||||||
|
result = get_quarter(value)[0]
|
||||||
|
elif granularity == "month":
|
||||||
|
result = value.replace(day=1)
|
||||||
|
elif granularity == 'week':
|
||||||
|
# `calendar.weekday` uses ISO8601 for start of week reference, this means that
|
||||||
|
# by default MONDAY is the first day of the week and SUNDAY is the last.
|
||||||
|
result = value - relativedelta(days=calendar.weekday(value.year, value.month, value.day))
|
||||||
|
elif granularity == "day":
|
||||||
|
result = value
|
||||||
|
elif granularity == "hour" and is_datetime:
|
||||||
|
return datetime.combine(value, time.min).replace(hour=value.hour)
|
||||||
|
elif is_datetime:
|
||||||
|
raise ValueError(
|
||||||
|
"Granularity must be year, quarter, month, week, day or hour for value %s" % value
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
"Granularity must be year, quarter, month, week or day for value %s" % value
|
||||||
|
)
|
||||||
|
|
||||||
|
return datetime.combine(result, time.min) if is_datetime else result
|
||||||
|
|
||||||
|
|
||||||
|
def end_of(value, granularity):
|
||||||
|
"""
|
||||||
|
Get end of a time period from a date or a datetime.
|
||||||
|
|
||||||
|
:param value: initial date or datetime.
|
||||||
|
:param granularity: Type of period in string, can be year, quarter, month, week, day or hour.
|
||||||
|
:return: A date/datetime object corresponding to the start of the specified period.
|
||||||
|
"""
|
||||||
|
is_datetime = isinstance(value, datetime)
|
||||||
|
if granularity == "year":
|
||||||
|
result = value.replace(month=12, day=31)
|
||||||
|
elif granularity == "quarter":
|
||||||
|
# Q1 = Mar 31st
|
||||||
|
# Q2 = Jun 30th
|
||||||
|
# Q3 = Sep 30th
|
||||||
|
# Q4 = Dec 31st
|
||||||
|
result = get_quarter(value)[1]
|
||||||
|
elif granularity == "month":
|
||||||
|
result = value + relativedelta(day=1, months=1, days=-1)
|
||||||
|
elif granularity == 'week':
|
||||||
|
# `calendar.weekday` uses ISO8601 for start of week reference, this means that
|
||||||
|
# by default MONDAY is the first day of the week and SUNDAY is the last.
|
||||||
|
result = value + relativedelta(days=6-calendar.weekday(value.year, value.month, value.day))
|
||||||
|
elif granularity == "day":
|
||||||
|
result = value
|
||||||
|
elif granularity == "hour" and is_datetime:
|
||||||
|
return datetime.combine(value, time.max).replace(hour=value.hour)
|
||||||
|
elif is_datetime:
|
||||||
|
raise ValueError(
|
||||||
|
"Granularity must be year, quarter, month, week, day or hour for value %s" % value
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
"Granularity must be year, quarter, month, week or day for value %s" % value
|
||||||
|
)
|
||||||
|
|
||||||
|
return datetime.combine(result, time.max) if is_datetime else result
|
||||||
|
|
||||||
|
|
||||||
|
def add(value, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Return the sum of ``value`` and a :class:`relativedelta`.
|
||||||
|
|
||||||
|
:param value: initial date or datetime.
|
||||||
|
:param args: positional args to pass directly to :class:`relativedelta`.
|
||||||
|
:param kwargs: keyword args to pass directly to :class:`relativedelta`.
|
||||||
|
:return: the resulting date/datetime.
|
||||||
|
"""
|
||||||
|
return value + relativedelta(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def subtract(value, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Return the difference between ``value`` and a :class:`relativedelta`.
|
||||||
|
|
||||||
|
:param value: initial date or datetime.
|
||||||
|
:param args: positional args to pass directly to :class:`relativedelta`.
|
||||||
|
:param kwargs: keyword args to pass directly to :class:`relativedelta`.
|
||||||
|
:return: the resulting date/datetime.
|
||||||
|
"""
|
||||||
|
return value - relativedelta(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def json_default(obj):
|
||||||
|
"""
|
||||||
|
Properly serializes date and datetime objects.
|
||||||
|
"""
|
||||||
|
from odoo import fields
|
||||||
|
if isinstance(obj, date):
|
||||||
|
if isinstance(obj, datetime):
|
||||||
|
return fields.Datetime.to_string(obj)
|
||||||
|
return fields.Date.to_string(obj)
|
||||||
|
return ustr(obj)
|
||||||
|
|||||||
Reference in New Issue
Block a user