[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:
Raphael Collet
2018-08-06 14:37:19 +02:00
committed by Adrian Torres
parent 09ba95b2d0
commit 960360afe4
82 changed files with 768 additions and 452 deletions
+2 -3
View File
@@ -2,7 +2,6 @@
from odoo.exceptions import ValidationError
from odoo import api, fields, models, _
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
from datetime import datetime
@@ -37,8 +36,8 @@ class AccountFiscalYear(models.Model):
'''
for fy in self:
# Starting date must be prior to the ending date
date_from = datetime.strptime(fy.date_from, DEFAULT_SERVER_DATE_FORMAT)
date_to = datetime.strptime(fy.date_to, DEFAULT_SERVER_DATE_FORMAT)
date_from = fy.date_from
date_to = fy.date_to
if date_to < date_from:
raise ValidationError(_('The ending date must not be prior to the starting date.'))
+3 -2
View File
@@ -10,7 +10,8 @@ from dateutil.relativedelta import relativedelta
from werkzeug.urls import url_encode
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.exceptions import AccessError, UserError, RedirectWarning, ValidationError, Warning
@@ -216,7 +217,7 @@ class AccountInvoice(models.Model):
self.payments_widget = json.dumps(False)
if self.payment_move_line_ids:
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.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))
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
data[:0] = [build_graph_data(date, amount)]
amount -= val['amount']
@@ -92,7 +92,7 @@ class account_journal(models.Model):
@api.multi
def get_bar_graph_datas(self):
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'})
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)
+9 -8
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import time
from datetime import date
from collections import OrderedDict
from odoo import api, fields, models, _
from odoo.osv import expression
@@ -345,10 +346,10 @@ class AccountMove(models.Model):
@api.multi
def _check_lock_date(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'):
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'):
message = _("You cannot add/modify entries prior to and inclusive of the lock date %s") % (lock_date)
else:
@@ -738,7 +739,7 @@ class AccountMoveLine(models.Model):
total_debit = 0
total_credit = 0
total_amount_currency = 0
maxdate = '0000-00-00'
maxdate = date.min
to_balance = {}
for aml in amls:
total_debit += aml.debit
@@ -947,7 +948,7 @@ class AccountMoveLine(models.Model):
total = 0
total_currency = 0
writeoff_lines = []
date = time.strftime('%Y-%m-%d')
date = fields.Date.today()
for vals in lines:
# Check and complete 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):
raise UserError(_("Either pass both debit and credit or none."))
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:
date = vals['date']
if 'name' not in vals:
@@ -1369,8 +1370,8 @@ class AccountPartialReconcile(models.Model):
def _compute_max_date(self):
for rec in self:
rec.max_date = max(
fields.Datetime.from_string(rec.debit_move_id.date),
fields.Datetime.from_string(rec.credit_move_id.date)
rec.debit_move_id.date,
rec.credit_move_id.date
)
@api.model
@@ -1608,6 +1609,6 @@ class AccountFullReconcile(models.Model):
# The move date should be the maximum date between payment and invoice
# (in case of payment in advance). However, we should make sure the
# 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
return res
+11 -12
View File
@@ -9,7 +9,7 @@ from odoo import fields, models, api, _
from odoo.exceptions import ValidationError, UserError, RedirectWarning
from odoo.tools.misc import DEFAULT_SERVER_DATE_FORMAT
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):
@@ -110,16 +110,15 @@ Best Regards,'''))
:param vals: The values passed to the write method.
'''
period_lock_date = vals.get('period_lock_date') and\
time.strptime(vals['period_lock_date'], DEFAULT_SERVER_DATE_FORMAT)
fields.Date.from_string(vals['period_lock_date'])
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)
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:
old_fiscalyear_lock_date = company.fiscalyear_lock_date and\
time.strptime(company.fiscalyear_lock_date, DEFAULT_SERVER_DATE_FORMAT)
old_fiscalyear_lock_date = company.fiscalyear_lock_date
# 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:
@@ -143,7 +142,7 @@ Best Regards,'''))
# In case of no new period lock date in vals, fallback to the one defined in the company
if not period_lock_date:
if company.period_lock_date:
period_lock_date = time.strptime(company.period_lock_date, DEFAULT_SERVER_DATE_FORMAT)
period_lock_date = company.period_lock_date
else:
continue
@@ -182,8 +181,8 @@ Best Regards,'''))
], limit=1)
if fiscalyear:
return {
'date_from': datetime.strptime(fiscalyear.date_from, DEFAULT_SERVER_DATE_FORMAT).date(),
'date_to': datetime.strptime(fiscalyear.date_to, DEFAULT_SERVER_DATE_FORMAT).date(),
'date_from': fiscalyear.date_from,
'date_to': fiscalyear.date_to,
'record': fiscalyear,
}
@@ -204,7 +203,7 @@ Best Regards,'''))
('date_to', '>=', date_from_str),
], limit=1)
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([
('company_id', '=', self.id),
@@ -212,7 +211,7 @@ Best Regards,'''))
('date_to', '>=', date_to_str),
], limit=1)
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}
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
import time
from odoo import api, models, _
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import float_is_zero
from datetime import datetime
@@ -19,7 +19,8 @@ class ReportAgedPartnerBalance(models.AbstractModel):
# The context key allow it to appear
ctx = self._context
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]:
stop = start - relativedelta(days=period_length)
periods[str(i)] = {
@@ -220,7 +221,7 @@ class ReportAgedPartnerBalance(models.AbstractModel):
docs = self.env[model].browse(self.env.context.get('active_id'))
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':
account_type = ['receivable']
@@ -4,7 +4,6 @@ from datetime import datetime
import time
from odoo import api, models, _
from odoo.exceptions import UserError
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
class ReportPartnerLedger(models.AbstractModel):
@@ -35,7 +34,6 @@ class ReportPartnerLedger(models.AbstractModel):
lang_id = lang._lang_get(lang_code)
date_format = lang_id.date_format
for r in res:
r['date'] = datetime.strptime(r['date'], DEFAULT_SERVER_DATE_FORMAT).strftime(date_format)
r['displayed_name'] = '-'.join(
r[field_name] for field_name in ('move_name', 'ref', 'name')
if r[field_name] not in (None, '', '/')
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
from odoo.addons.account.tests.account_test_classes import AccountingTestCase
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
import odoo.tests
from odoo import fields
from datetime import datetime
@@ -19,10 +19,10 @@ class TestFiscalPosition(AccountingTestCase):
:param expected_date_from: The expected date_from 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)
self.assertEqual(res['date_from'].strftime(DEFAULT_SERVER_DATE_FORMAT), expected_date_from)
self.assertEqual(res['date_to'].strftime(DEFAULT_SERVER_DATE_FORMAT), expected_date_to)
self.assertEqual(res['date_from'], fields.Date.from_string(expected_date_from))
self.assertEqual(res['date_to'], fields.Date.from_string(expected_date_to))
def test_default_fiscal_year(self):
'''Basic case with a fiscal year xxxx-01-01 - xxxx-12-31.'''
+1 -1
View File
@@ -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.tests import tagged
import time
@@ -27,7 +27,7 @@ class AccountAgedTrialBalance(models.TransientModel):
if not data['form']['date_from']:
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]:
stop = start - relativedelta(days=period_length - 1)
+7 -11
View File
@@ -7,7 +7,6 @@ from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _
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
@@ -164,7 +163,7 @@ class AccountAssetAsset(models.Model):
if self.prorata:
amount = amount_to_depr / self.method_number
if sequence == 1:
date = datetime.strptime(self.date, DF)
date = self.date
if self.method_period % 12 != 0:
month_days = calendar.monthrange(date.year, date.month)[1]
days = month_days - date.day + 1
@@ -176,7 +175,7 @@ class AccountAssetAsset(models.Model):
amount = residual_amount * self.method_progress_factor
if self.prorata:
if sequence == 1:
date = datetime.strptime(self.date, DF)
date = self.date
if self.method_period % 12 != 0:
month_days = calendar.monthrange(date.year, date.month)[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):
undone_dotation_number = self.method_number
if self.method_time == 'end':
end_date = datetime.strptime(self.method_end, DF).date()
end_date = self.method_end
undone_dotation_number = 0
while depreciation_date <= end_date:
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 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)
else:
# depreciation_date computed from the purchase date
depreciation_date = self.date
depreciation_date = datetime.strptime(depreciation_date, DF)
if self.date_first_depreciation == 'last_day_period':
# depreciation_date = the last day of the month
depreciation_date = depreciation_date + relativedelta(day=31)
@@ -226,14 +224,12 @@ class AccountAssetAsset(models.Model):
if self.method_period == 12:
depreciation_date = depreciation_date + relativedelta(month=self.company_id.fiscalyear_last_month)
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)
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 = 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
month_day = depreciation_date.day
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),
'remaining_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))
@@ -327,7 +323,7 @@ class AccountAssetAsset(models.Model):
# Create a new depr. line with the residual amount and post it
sequence = len(asset.depreciation_line_ids) - len(unposted_depreciation_line_ids) + 1
today = datetime.today().strftime(DF)
today = fields.Datetime.today()
vals = {
'amount': asset.value_residual,
'asset_id': asset.id,
@@ -5,7 +5,6 @@ from datetime import datetime
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF
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']:
self.asset_mrr = self.price_subtotal_signed / months
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))
self.asset_start_date = start_date.strftime(DF)
self.asset_end_date = end_date.strftime(DF)
self.asset_start_date = start_date
self.asset_end_date = end_date
@api.one
def asset_create(self):
+9 -10
View File
@@ -205,22 +205,21 @@ class CrossoveredBudgetLines(models.Model):
@api.multi
def _compute_theoritical_amount(self):
# 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:
if line.paid_date:
if fields.Datetime.to_string(today) <= line.paid_date:
if today <= line.paid_date:
theo_amt = 0.00
else:
theo_amt = line.planned_amount
else:
line_timedelta = fields.Datetime.from_string(line.date_to) - fields.Datetime.from_string(
line.date_from)
elapsed_timedelta = today - fields.Datetime.from_string(line.date_from)
line_timedelta = line.date_to - line.date_from
elapsed_timedelta = today - line.date_from
if elapsed_timedelta.days < 0:
# If the budget line has not started yet, theoretical amount should be zero
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
theo_amt = (elapsed_timedelta.total_seconds() / line_timedelta.total_seconds()) * line.planned_amount
else:
@@ -265,14 +264,14 @@ class CrossoveredBudgetLines(models.Model):
@api.one
@api.constrains('date_from', 'date_to')
def _line_dates_between_budget_dates(self):
budget_date_from = fields.Datetime.from_string(self.crossovered_budget_id.date_from)
budget_date_to = fields.Datetime.from_string(self.crossovered_budget_id.date_to)
budget_date_from = self.crossovered_budget_id.date_from
budget_date_to = self.crossovered_budget_id.date_to
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:
raise ValidationError(_('"Start Date" of the budget line should be included in the Period of the budget'))
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:
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)],
})
#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
date_from = first_january.date()
date_to = self.last_day_of_budget.date()
crossovered_budget = self.env['crossovered.budget'].create({
'name': 'test budget name',
'date_from': str(first_january.date()),
'date_to': str(self.last_day_of_budget.date()),
'date_from': date_from,
'date_to': date_to,
})
crossovered_budget_line_obj = self.env['crossovered.budget.lines']
self.line = crossovered_budget_line_obj.create({
'crossovered_budget_id': crossovered_budget.id,
'general_budget_id': buget_post.id,
'date_from': str(first_january.date()),
'date_to': str(self.last_day_of_budget.date()),
'date_from': date_from,
'date_to': date_to,
'planned_amount': -364,
})
self.paid_date_line = crossovered_budget_line_obj.create({
'crossovered_budget_id': crossovered_budget.id,
'general_budget_id': buget_post.id,
'date_from': str(first_january.date()),
'date_to': str(self.last_day_of_budget.date()),
'date_from': date_from,
'date_to': date_to,
'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)
@@ -74,7 +77,7 @@ class TestTheoreticalAmount(TestAccountBudgetCommon):
]
for date, expected_amount in test_list:
_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)
#invalidate the cache of the budget lines to recompute the theoritical amount at next iteration
self.line.invalidate_cache()
@@ -84,13 +87,13 @@ class TestTheoreticalAmount(TestAccountBudgetCommon):
(str(datetime.now().year) + '-01-01', 0),
(str(datetime.now().year) + '-01-02', 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(self.last_day_of_budget.date()), -364),
]
for date, expected_amount in test_list:
_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)
#invalidate the cache of the budget lines to recompute the theoritical amount at next iteration
self.paid_date_line.invalidate_cache()
+2 -1
View File
@@ -10,6 +10,7 @@ import time
import odoo
from odoo import api, fields, models, SUPERUSER_ID
from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.tools import date_utils
_logger = logging.getLogger(__name__)
@@ -20,7 +21,7 @@ TIMEOUT = 50
# Bus
#----------------------------------------------------------
def json_dump(v):
return json.dumps(v, separators=(',', ':'))
return json.dumps(v, separators=(',', ':'), default=date_utils.json_default)
def hashable(key):
if isinstance(key, list):
+3 -3
View File
@@ -43,11 +43,11 @@ class BusPresence(models.Model):
# update the presence or a create a new one
if not presence: # create a new presence for the user
values['user_id'] = self._uid
values['last_presence'] = last_presence.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
values['last_presence'] = last_presence
self.create(values)
else: # update the last_presence if necessary, and write values
if datetime.datetime.strptime(presence.last_presence, DEFAULT_SERVER_DATETIME_FORMAT) < last_presence:
values['last_presence'] = last_presence.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
if presence.last_presence < last_presence:
values['last_presence'] = last_presence
# Hide transaction serialization errors, which can be ignored, the presence update is not essential
with tools.mute_logger('odoo.sql_db'):
presence.write(values)
+16 -13
View File
@@ -351,7 +351,7 @@ class AlarmManager(models.AbstractModel):
@api.model
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)
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
break
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)
for alert in last_found:
self.do_mail_reminder(alert)
@@ -873,9 +873,9 @@ class Meeting(models.Model):
"""
for meeting in self:
if meeting.allday:
meeting.start_date = meeting.start
meeting.start_date = meeting.start.date()
meeting.start_datetime = False
meeting.stop_date = meeting.stop
meeting.stop_date = meeting.stop.date()
meeting.stop_datetime = False
meeting.duration = 0.0
@@ -897,13 +897,13 @@ class Meeting(models.Model):
enddate = tz.localize(enddate)
enddate = enddate.replace(hour=18)
enddate = enddate.astimezone(pytz.utc)
meeting.stop = fields.Datetime.to_string(enddate)
meeting.stop = enddate
startdate = fields.Datetime.from_string(meeting.start_date)
startdate = tz.localize(startdate) # Add "+hh:mm" timezone
startdate = startdate.replace(hour=8) # Set 8 AM in localtime
startdate = startdate.astimezone(pytz.utc) # Convert to UTC
meeting.start = fields.Datetime.to_string(startdate)
meeting.start = startdate
else:
meeting.write({'start': meeting.start_datetime,
'stop': meeting.stop_datetime})
@@ -945,9 +945,9 @@ class Meeting(models.Model):
@api.onchange('start_datetime', 'duration')
def _onchange_duration(self):
if self.start_datetime:
start = fields.Datetime.from_string(self.start_datetime)
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')
def _onchange_start_date(self):
@@ -971,9 +971,9 @@ class Meeting(models.Model):
def ics_datetime(idate, allday=False):
if idate:
if allday:
return fields.Date.from_string(idate)
return idate
else:
return fields.Datetime.from_string(idate).replace(tzinfo=pytz.timezone('UTC'))
return idate.replace(tzinfo=pytz.timezone('UTC'))
return False
try:
@@ -989,7 +989,7 @@ class Meeting(models.Model):
if not meeting.start or not meeting.stop:
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('dtend').value = ics_datetime(meeting.stop, meeting.allday)
event.add('summary').value = meeting.name
@@ -1245,7 +1245,8 @@ class Meeting(models.Model):
return ''
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 '') +\
((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)
data = self.read(['allday', 'start', 'stop', 'rrule', 'duration'])[0]
d_class = fields.Date if data['allday'] else fields.Datetime
if data.get('rrule'):
data.update(
values,
@@ -1387,7 +1389,8 @@ class Meeting(models.Model):
rrule_type=False,
rrule='',
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
+12 -12
View File
@@ -2,7 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import datetime
from datetime import datetime, timedelta
from datetime import datetime, timedelta, time
from odoo import fields
from odoo.tests.common import TransactionCase
@@ -34,7 +34,7 @@ class TestCalendar(TransactionCase):
})
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'),
"Sanity check"
)
@@ -102,8 +102,8 @@ class TestCalendar(TransactionCase):
# I create a recuring rule for my event
calendar_event_sprint_review = self.CalendarEvent.create({
'name': 'Begin of month meeting',
'start': fields.Date.today() + ' 12:00:00',
'stop': fields.Date.today() + ' 18:00:00',
'start': datetime.combine(fields.Date.today(), time(12, 0)),
'stop': datetime.combine(fields.Date.today(), time(18, 0)),
'recurrency': True,
'rrule': 'FREQ=MONTHLY;INTERVAL=1;COUNT=12;BYDAY=1MO'
})
@@ -130,7 +130,7 @@ class TestCalendar(TransactionCase):
'stop': '2017-07-12 15:00:00',
})
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'),
"Sanity check"
)
@@ -154,11 +154,11 @@ class TestCalendar(TransactionCase):
records = m.detach_recurring_event(values)
self.assertEqual(
(m.start_datetime, m.stop_datetime),
(u'2017-07-12 14:30:00', u'2017-07-12 15:00:00'),
(str(m.start_datetime), str(m.stop_datetime)),
('2017-07-12 14:30:00', u'2017-07-12 15:00:00'),
)
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'),
)
@@ -243,7 +243,7 @@ class TestCalendar(TransactionCase):
self.assertEqual(test_record.activity_ids.summary, test_name)
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.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
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.note, test_description2)
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
test_record.activity_ids.unlink()
@@ -268,8 +268,8 @@ class TestCalendar(TransactionCase):
).create({
'name': test_name,
'description': test_description,
'start': fields.Datetime.to_string(now + timedelta(days=-1)),
'stop': fields.Datetime.to_string(now + timedelta(hours=2)),
'start': now + timedelta(days=-1),
'stop': now + timedelta(hours=2),
'user_id': self.env.user.id,
})
self.assertEqual(test_event.res_model, test_record._name)
+1 -1
View File
@@ -39,7 +39,7 @@ class AlarmManager(models.AbstractModel):
""" Cron method, overriden here to send SMS reminders as well
"""
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)
cron = self.env['ir.model.data'].get_object('calendar', 'ir_cron_scheduler_alarm')
+1 -1
View File
@@ -1054,7 +1054,7 @@ class Lead(models.Model):
meetings = self.env['calendar.event'].search(meetings_domain)
for meeting in meetings:
if meeting['start']:
start = datetime.strptime(meeting['start'], tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
start = meeting['start']
if start == today:
result['meeting']['today'] += 1
if today <= start <= today + timedelta(days=7):
+3 -3
View File
@@ -347,7 +347,7 @@ class EventRegistration(models.Model):
partner_id = fields.Many2one(
'res.partner', string='Contact',
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)
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)
@@ -515,8 +515,8 @@ class EventRegistration(models.Model):
@api.multi
def get_date_range_str(self):
self.ensure_one()
today = fields.Datetime.from_string(fields.Datetime.now())
event_date = fields.Datetime.from_string(self.event_begin_date)
today = fields.Datetime.now()
event_date = self.event_begin_date
diff = (event_date.date() - today.date())
if diff.days <= 0:
return _('today')
+2 -2
View File
@@ -97,7 +97,7 @@ class EventMailScheduler(models.Model):
else:
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
def execute(self):
@@ -190,7 +190,7 @@ class EventMailRegistration(models.Model):
def _compute_scheduled_date(self):
if self.registration_id:
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)
else:
self.scheduled_date = False
+2 -2
View File
@@ -120,7 +120,7 @@ class TestEventFlow(TestEventCommon):
self.patcher = patch('odoo.addons.event.models.event.fields.Datetime', wraps=Datetime)
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.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.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.assertEqual(self.event_0.registration_ids.get_date_range_str(), u'next month')
+4 -4
View File
@@ -13,7 +13,7 @@ class TestMailSchedule(TestEventCommon):
@mute_logger('odoo.addons.base.models.ir_model', 'odoo.models')
def test_00_event_mail_schedule(self):
""" Test mail scheduling for events """
now = fields.datetime.now()
now = fields.Datetime.now()
event_date_begin = now + relativedelta(days=1)
event_date_end = now + relativedelta(days=3)
@@ -56,7 +56,7 @@ class TestMailSchedule(TestEventCommon):
# 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')
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')
for registration in schedulers[0].mail_registration_ids:
@@ -65,7 +65,7 @@ class TestMailSchedule(TestEventCommon):
# check before event scheduler
schedulers = self.EventMail.search([('event_id', '=', test_event.id), ('interval_type', '=', 'before_event')])
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
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].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')
+2 -2
View File
@@ -299,9 +299,9 @@ class FleetVehicleOdometer(models.Model):
for record in self:
name = record.vehicle_id.name
if not name:
name = record.date
name = str(record.date)
elif record.date:
name += ' / ' + record.date
name += ' / ' + str(record.date)
record.name = name
@api.onchange('vehicle_id')
+5 -6
View File
@@ -145,7 +145,7 @@ class FleetVehicleLogContract(models.Model):
if record.cost_subtype_id.name:
name += ' / ' + record.cost_subtype_id.name
if record.date:
name += ' / ' + record.date
name += ' / ' + str(record.date)
record.name = name
@api.depends('expiration_date', 'state')
@@ -231,7 +231,7 @@ class FleetVehicleLogContract(models.Model):
if not contract.start_date or contract.cost_frequency == 'no':
continue
found = False
last_cost_date = contract.start_date
startdate = contract.start_date
if contract.generated_cost_ids:
last_autogenerated_cost = VehicleCost.search([
('contract_id', '=', contract.id),
@@ -239,12 +239,11 @@ class FleetVehicleLogContract(models.Model):
], offset=0, limit=1, order='date desc')
if last_autogenerated_cost:
found = True
last_cost_date = last_autogenerated_cost.date
startdate = fields.Date.from_string(last_cost_date)
startdate = last_autogenerated_cost.date.date()
if found:
startdate += deltas.get(contract.cost_frequency)
today = fields.Date.from_string(fields.Date.context_today(self))
while (startdate <= today) & (startdate <= fields.Date.from_string(contract.expiration_date)):
today = fields.Date.context_today(self)
while (startdate <= today) & (startdate <= contract.expiration_date):
data = {
'amount': contract.cost_generated,
'date': fields.Date.context_today(self),
+2 -2
View File
@@ -141,11 +141,11 @@ class Challenge(models.Model):
report period.
"""
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)
if offset:
challenge.next_report_date = fields.Date.to_string(last + offset)
challenge.next_report_date = last + offset
else:
challenge.next_report_date = False
@@ -193,12 +193,12 @@ class GoogleCalendar(models.AbstractModel):
def generate_data(self, event, isCreating=False):
if event.allday:
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'
vstype = 'dateTime'
else:
start_date = fields.Datetime.context_timestamp(self, fields.Datetime.from_string(event.start)).isoformat('T')
final_date = fields.Datetime.context_timestamp(self, fields.Datetime.from_string(event.stop)).isoformat('T')
start_date = fields.Datetime.context_timestamp(self, event.start).isoformat('T')
final_date = fields.Datetime.context_timestamp(self, event.stop).isoformat('T')
type = 'dateTime'
vstype = 'date'
attendee_list = []
@@ -852,9 +852,9 @@ class GoogleCalendar(models.AbstractModel):
return True
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)
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')
def get_sequence(self, instance_id):
+1 -5
View File
@@ -1,10 +1,7 @@
# -*- coding: utf-8 -*-
# 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.tools import DEFAULT_SERVER_DATETIME_FORMAT
class HrAttendance(models.Model):
@@ -43,8 +40,7 @@ class HrAttendance(models.Model):
def _compute_worked_hours(self):
for attendance in self:
if attendance.check_out:
delta = datetime.strptime(attendance.check_out, DEFAULT_SERVER_DATETIME_FORMAT) - datetime.strptime(
attendance.check_in, DEFAULT_SERVER_DATETIME_FORMAT)
delta = attendance.check_out - attendance.check_in
attendance.worked_hours = delta.total_seconds() / 3600.0
@api.constrains('check_in', 'check_out')
+3 -3
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# 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 dateutil.relativedelta import relativedelta
@@ -37,13 +37,13 @@ class TestHrContracts(TransactionCase):
self.assertEquals(self.contract.state, 'close')
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.contract = self.contracts.create(self.test_contract)
self.apply_cron()
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({
'date_start': datetime.now() + relativedelta(days=-50),
'state': 'pending',
+5 -5
View File
@@ -376,12 +376,12 @@ class HolidaysRequest(models.Model):
def _check_leave_type_validity(self):
for leave in self:
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)
vstop = fields.Datetime.from_string(leave.holiday_status_id.validity_stop)
dfrom = fields.Datetime.from_string(leave.date_from)
dto = fields.Datetime.from_string(leave.date_to)
vstart = leave.holiday_status_id.validity_start
vstop = leave.holiday_status_id.validity_stop
dfrom = leave.date_from
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, \
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):
for allocation in self:
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)
vstop = fields.Datetime.from_string(allocation.holiday_status_id.validity_stop)
today = fields.Datetime.from_string(fields.Datetime.now())
vstart = allocation.holiday_status_id.validity_start
vstop = allocation.holiday_status_id.validity_stop
today = fields.Date.today()
if vstart > today or vstop < today:
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)
# 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({
'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)
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({
'name': 'one shot one kill',
@@ -1,9 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime
from odoo import api, fields, models, tools, SUPERUSER_ID
from odoo import api, fields, models, SUPERUSER_ID
from odoo.tools.translate import _
from odoo.exceptions import UserError
@@ -176,13 +174,13 @@ class Applicant(models.Model):
@api.one
def _compute_day(self):
if self.date_open:
date_create = datetime.strptime(self.create_date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
date_open = datetime.strptime(self.date_open, tools.DEFAULT_SERVER_DATETIME_FORMAT)
date_create = self.create_date
date_open = self.date_open
self.day_open = (date_open - date_create).total_seconds() / (24.0 * 3600)
if self.date_closed:
date_create = datetime.strptime(self.create_date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
date_closed = datetime.strptime(self.date_closed, tools.DEFAULT_SERVER_DATETIME_FORMAT)
date_create = self.create_date
date_closed = self.date_closed
self.day_close = (date_closed - date_create).total_seconds() / (24.0 * 3600)
self.delay_close = self.day_close - self.day_open
@@ -5,7 +5,7 @@ import babel.dates
from odoo import api, fields, models
from odoo.fields import Datetime
from odoo.fields import Datetime, Date
class FleetVehicle(models.Model):
@@ -80,17 +80,18 @@ class FleetVehicle(models.Model):
def _get_acquisition_date(self):
self.ensure_one()
return babel.dates.format_date(
date=Datetime.from_string(self.acquisition_date),
date=self.acquisition_date,
format='MMMM y',
locale=self._context.get('lang') or 'en_US'
)
def _get_car_atn(self, acquisition_date, car_value, fuel_type, co2):
# Compute the correction coefficient from the age of the car
now = Datetime.from_string(Datetime.now())
start = Datetime.from_string(acquisition_date)
if start:
number_of_month = (now.year - start.year) * 12.0 + now.month - start.month + int(bool(now.day - start.day + 1))
now = Date.today()
if acquisition_date:
number_of_month = ((now.year - acquisition_date.year) * 12.0 + now.month -
acquisition_date.month +
int(bool(now.day - acquisition_date.day + 1)))
if number_of_month <= 12:
age_coefficient = 1.00
elif number_of_month <= 24:
+3 -4
View File
@@ -5,11 +5,10 @@
import base64
import io
from datetime import datetime
from odoo import api, fields, models, _
from odoo.exceptions import Warning
from odoo.tools import pycompat, DEFAULT_SERVER_DATE_FORMAT
from odoo.tools import pycompat
class AccountFrFec(models.TransientModel):
@@ -68,7 +67,7 @@ class AccountFrFec(models.TransientModel):
'''
company = self.env.user.company_id
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
self._cr.execute(
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')
'''
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
self._cr.execute(
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.
import time
from datetime import datetime
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)
if slip:
from_date = datetime.strptime(slip.date_from, '%Y-%m-%d')
to_date = datetime.strptime(slip.date_to, '%Y-%m-%d')
from_date = slip.date_from
to_date = slip.date_to
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')
return res
+3 -3
View File
@@ -123,9 +123,9 @@ class LunchOrder(models.Model):
"""
Prevents the user to create an order in the past
"""
date_order = datetime.datetime.strptime(self.date, '%Y-%m-%d')
date_today = datetime.datetime.strptime(fields.Date.context_today(self), '%Y-%m-%d')
if (date_order < date_today):
date_order = self.date
date_today = fields.Date.context_today(self)
if date_order < date_today:
raise ValidationError(_('The date of your order is in the past.'))
@api.one
+3 -1
View File
@@ -172,7 +172,9 @@ class MailActivity(models.Model):
def _onchange_activity_type_id(self):
if self.activity_type_id:
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')
def _onchange_recommended_activity_type_id(self):
+1 -1
View File
@@ -30,7 +30,7 @@ def format_date(env, date, pattern=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')
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)
+4 -4
View File
@@ -46,8 +46,8 @@ class MailTracking(models.Model):
})
elif col_info['type'] == 'date':
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,
'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,
'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 fields.Datetime.to_string(datetime.combine(fields.Date.from_string(new_value), datetime.min.time())) or False,
})
elif col_info['type'] == 'boolean':
values.update({
@@ -88,8 +88,8 @@ class MailTracking(models.Model):
result.append(record['%s_value_datetime' % type])
elif record.field_type == 'date':
if record['%s_value_datetime' % type]:
new_date = datetime.strptime(record['%s_value_datetime' % type], tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
result.append(new_date.strftime(tools.DEFAULT_SERVER_DATE_FORMAT))
new_date = record['%s_value_datetime' % type]
result.append(fields.Date.to_string(new_date))
else:
result.append(record['%s_value_datetime' % type])
elif record.field_type == 'boolean':
+8 -2
View File
@@ -3,6 +3,8 @@
from odoo import api, fields, models
from datetime import date
class Invoice(models.Model):
_inherit = 'account.invoice'
@@ -36,7 +38,8 @@ class AccountInvoiceLine(models.Model):
# Product line has changed to a membership product
date_from = line.product_id.membership_date_from
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
MemberLine.create({
'partner': line.invoice_id.partner_id.id,
@@ -62,7 +65,10 @@ class AccountInvoiceLine(models.Model):
# Product line is a membership product
date_from = invoice_line.product_id.membership_date_from
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
MemberLine.create({
'partner': invoice_line.invoice_id.partner_id.id,
+3 -3
View File
@@ -59,7 +59,7 @@ class TestMrpOrder(TestMrpCommon):
})
inventory.action_validate()
test_date_planned = datetime.now() - timedelta(days=1)
test_date_planned = Dt.now() - timedelta(days=1)
test_quantity = 2.0
self.bom_1.routing_id = False
man_order = self.env['mrp.production'].sudo(self.user_mrp_user).create({
@@ -76,7 +76,7 @@ class TestMrpOrder(TestMrpCommon):
# check production move
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_uom, man_order.product_uom_id)
self.assertEqual(production_move.product_qty, man_order.product_qty)
@@ -85,7 +85,7 @@ class TestMrpOrder(TestMrpCommon):
# check consumption moves
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)
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)
+1 -2
View File
@@ -6,7 +6,6 @@ from uuid import uuid4
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT
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_state = session[0].state
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
else:
pos_config.pos_session_username = False
+1 -1
View File
@@ -32,7 +32,7 @@ class PosMakePayment(models.TransientModel):
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)
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')
def _on_change_session(self):
+3 -3
View File
@@ -171,7 +171,7 @@ class PurchaseOrder(models.Model):
for line in new_po.order_line:
seller = line.product_id._select_seller(
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)
return new_po
@@ -526,7 +526,7 @@ class PurchaseOrderLine(models.Model):
"""
date_order = po.date_order if po else self.order_id.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:
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(
partner_id=self.partner_id,
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,
params=params)
@@ -280,7 +280,7 @@ class PurchaseRequisitionLine(models.Model):
'product_qty': product_qty,
'price_unit': price_unit,
'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,
'analytic_tag_ids': self.analytic_tag_ids.ids,
'move_dest_ids': self.move_dest_id and [(4, self.move_dest_id.id)] or []
+2 -2
View File
@@ -98,7 +98,7 @@ class StockRule(models.Model):
seller = product_id._select_seller(
partner_id=partner,
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)
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(
partner_id=partner,
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)
taxes = product_id.supplier_taxes_id
@@ -29,19 +29,17 @@ class TestPurchaseLeadTime(TestPurchase):
# 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)
po_order_date = fields.Datetime.to_string(order_date)
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.')
self.assertEqual(purchase.date_order, 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
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, po_schedule_date, 'Schedule date should be equal to: Order date of Purchase order + Delivery Lead Time.')
self.assertEqual(purchase.date_planned, schedule_date, 'Schedule date should be equal to: Order date of Purchase order + Delivery Lead Time.')
# check the picking created or not
self.assertTrue(purchase.picking_ids, "Picking should be created.")
# 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):
""" 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_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)
po_order_date = fields.Datetime.to_string(order_date)
self.assertEqual(purchase2.date_order, po_order_date, 'Order date should be equal to: Date of the procurement order - Delivery Lead Time.')
self.assertEqual(purchase2.date_order, 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
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_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.')
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.')
# 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_line_2 = fields.Datetime.to_string(schedule_date_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.')
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.')
# 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.')
# Check the picking created or not
@@ -121,13 +116,11 @@ class TestPurchaseLeadTime(TestPurchase):
# Check order date of purchase order
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, po_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, 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
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, po_schedule_date, 'Schedule date should be equal to: Order date of Purchase order + Delivery Lead Time(supplier and pull rules).')
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).')
# Check the picking crated or not
self.assertTrue(purchase.picking_ids, "Picking should be created.")
@@ -135,10 +128,8 @@ class TestPurchaseLeadTime(TestPurchase):
# 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_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_schedule_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_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_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_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..')
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..')
+2 -2
View File
@@ -163,9 +163,9 @@ class SaleOrder(models.Model):
order.access_url = '/my/orders/%s' % (order.id)
def _compute_is_expired(self):
now = datetime.now()
today = fields.Date.today()
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
else:
order.is_expired = False
+2 -2
View File
@@ -39,8 +39,8 @@ class SaleOrder(models.Model):
def _compute_effective_date(self):
for order in self:
pickings = order.picking_ids.filtered(lambda x: x.state == 'done' and x.location_dest_id.usage == 'customer')
dates_list = pickings.mapped('date_done')
order.effective_date = dates_list and min(dates_list)
dates_list = [date for date in pickings.mapped('date_done') if date]
order.effective_date = dates_list and min(dates_list).date()
@api.onchange('commitment_date')
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
security_delay = timedelta(days=new_order.company_id.security_lead)
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:
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
# 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))
self.assertEquals(expected_date, sale_order.expected_date, "Wrong expected date on sale order!")
expected_date = fields.Datetime.now() + timedelta(days=5)
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
# current date + longest lead time from all of it's order lines
sale_order.write({'picking_policy': 'one'})
expected_date = fields.Datetime.to_string(fields.Datetime.from_string(fields.Datetime.now()) + timedelta(days=15))
self.assertEquals(expected_date, sale_order.expected_date, "Wrong expected date on sale order!")
expected_date = fields.Datetime.now() + timedelta(days=15)
self.assertAlmostEquals(expected_date, sale_order.expected_date,
msg="Wrong expected date on sale order!", delta=timedelta(seconds=1))
sale_order.action_confirm()
# Setting confirmation date of SO to 5 days from today so that the expected/effective date could be checked
# 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})
# 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
expected_date = fields.Datetime.to_string(confirm_date + timedelta(days=15))
self.assertEquals(expected_date, sale_order.expected_date, "Wrong expected date on sale order!")
expected_date = confirm_date + timedelta(days=15)
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
# SO confirmation date + shortest lead time from all of it's order lines
sale_order.write({'picking_policy': 'direct'})
expected_date = fields.Datetime.to_string(confirm_date + timedelta(days=5))
self.assertEquals(expected_date, sale_order.expected_date, "Wrong expected date on sale order!")
expected_date = confirm_date + timedelta(days=5)
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
picking = sale_order.picking_ids[0]
+2 -2
View File
@@ -280,13 +280,13 @@ class SaleOrderLine(models.Model):
"""
values = super(SaleOrderLine, self)._prepare_procurement_values(group_id)
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)
values.update({
'company_id': self.order_id.company_id,
'group_id': group_id,
'sale_line_id': self.id,
'date_planned': date_planned.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'date_planned': date_planned,
'route_ids': self.route_id,
'warehouse_id': self.order_id.warehouse_id or False,
'partner_dest_id': self.order_id.partner_shipping_id
+3 -1
View File
@@ -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 = [('product_id', 'in', self.ids)] + domain_quant_loc
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
domain_move_in = [('product_id', 'in', self.ids)] + domain_move_in_loc
+3 -3
View File
@@ -426,11 +426,11 @@ class StockMove(models.Model):
if 'date_expected' in propagated_changes_dict:
propagated_changes_dict.pop('date_expected')
if propagated_date_field:
current_date = datetime.strptime(move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT)
new_date = datetime.strptime(vals.get(propagated_date_field), DEFAULT_SERVER_DATETIME_FORMAT)
current_date = move.date_expected
new_date = fields.Datetime.from_string(vals.get(propagated_date_field))
delta_days = (new_date - current_date).total_seconds() / 86400
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)
propagated_changes_dict['date_expected'] = new_move_date
#For pushed moves as well as for pulled moves, propagate by recursive call of write().
+4 -2
View File
@@ -136,7 +136,7 @@ class StockRule(models.Model):
Care this function is not call by method run. It is called explicitely
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':
move.write({
'date': new_date,
@@ -193,7 +193,9 @@ class StockRule(models.Model):
:param procurement: browse record
: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
# a new move with the correct qty
qty_left = product_qty
+11 -11
View File
@@ -3602,8 +3602,9 @@ class StockMove(TransactionCase):
('product_id', '=', self.product3.id),
('lot_id', '=', lot1.id),
])
from datetime import datetime, timedelta
initial_in_date_lot1 = datetime.now() - timedelta(days=5)
from odoo.fields import Datetime
from datetime import timedelta
initial_in_date_lot1 = Datetime.now() - timedelta(days=5)
quant_lot1.in_date = initial_in_date_lot1
# 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
# in pack.
self.assertEqual(len(quant_in_pack), 1)
from odoo.fields import Datetime
self.assertEqual(quant_in_pack.in_date, Datetime.to_string(initial_in_date_lot1))
self.assertAlmostEqual(quant_in_pack.in_date, initial_in_date_lot1, delta=timedelta(seconds=1))
self.assertEqual(quant_in_pack.lot_id, lot1)
# Now, edit the move line and actually move the other lot
@@ -3640,7 +3640,7 @@ class StockMove(TransactionCase):
('lot_id', '=', lot1.id),
])
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
quant_lot2 = self.env['stock.quant'].search([
@@ -3649,7 +3649,7 @@ class StockMove(TransactionCase):
('lot_id', '=', lot2.id),
])
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):
""" 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),
('lot_id', '=', lot1.id),
])
from datetime import datetime, timedelta
initial_in_date_lot1 = datetime.now() - timedelta(days=5)
from odoo.fields import Datetime
from datetime import timedelta
initial_in_date_lot1 = Datetime.now() - timedelta(days=5)
quant_lot1.in_date = initial_in_date_lot1
# Move one quant to pack location
@@ -3741,12 +3742,11 @@ class StockMove(TransactionCase):
('product_id', '=', self.product3.id),
])
self.assertEqual(len(quants), 2)
from odoo.fields import Datetime
for quant in quants:
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:
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):
""" Receive some products, send some to transit, check the product's `available_qty`
+5 -5
View File
@@ -763,7 +763,8 @@ class StockQuant(TransactionCase):
'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)
quant = self.env['stock.quant'].search([
@@ -773,10 +774,9 @@ class StockQuant(TransactionCase):
self.assertEqual(len(quant), 1)
self.assertEqual(quant.quantity, 1)
self.assertEqual(quant.lot_id.id, lot1.id)
from odoo.fields import Datetime
self.assertEqual(quant.in_date, Datetime.to_string(in_date1))
self.assertEqual(quant.in_date, 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)
quant = self.env['stock.quant'].search([
@@ -786,4 +786,4 @@ class StockQuant(TransactionCase):
self.assertEqual(len(quant), 1)
self.assertEqual(quant.quantity, 2)
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 odoo.exceptions import UserError
from odoo.fields import Date
from odoo.fields import Date, Datetime
from odoo.tests.common import TransactionCase
@@ -3260,7 +3260,7 @@ class TestStockValuation(TransactionCase):
def test_at_date_standard_1(self):
self.product1.product_tmpl_id.cost_method = 'standard'
now = Date.from_string(Date.today())
now = Datetime.now()
date1 = now - timedelta(days=8)
date2 = now - timedelta(days=7)
date3 = now - timedelta(days=6)
@@ -3387,42 +3387,42 @@ class TestStockValuation(TransactionCase):
self.assertEqual(self.product1.stock_value, 712.5)
# 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=Date.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=Date.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=Date.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=Date.to_string(date8)).qty_available, 95)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).qty_available, 0)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).qty_available, 10)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).qty_available, 30)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).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=Datetime.to_string(date6)).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=Datetime.to_string(date8)).qty_available, 95)
# 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=Date.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=Date.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=Date.to_string(date8)).stock_value, 712.5)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).stock_value, 100)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).stock_value, 300)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).stock_value, 150)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).stock_value, 75)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).stock_value, -25)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date8)).stock_value, 712.5)
# 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=Date.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=Date.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=Date.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=Date.to_string(date8)).qty_at_date, 95.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=Datetime.to_string(date2)).qty_at_date, 10.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=Datetime.to_string(date4)).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=Datetime.to_string(date6)).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=Datetime.to_string(date8)).qty_at_date, 95.0)
# edit the done quantity of move1, decrease it
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date2)).qty_available, 10)
self.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date2)).qty_at_date, 10.0)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).qty_available, 10)
self.assertAlmostEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).qty_at_date, 10.0)
move1.quantity_done = 5
move1.account_move_ids.write({'date': date2})
# 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.assertAlmostEqual(self.product1.with_context(to_date=Date.to_string(date2)).qty_at_date, 5.0)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).qty_available, 5)
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
# 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
# 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
# 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=Date.to_string(date6)).qty_at_date, -10.0)
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date6)).stock_value, -50)
self.assertAlmostEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).qty_available, -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=Datetime.to_string(date6)).stock_value, -50)
move4.quantity_done = 15
move4.account_move_ids.write({'date': date6})
# -(20*5) + (5*7.5)
self.assertEqual(move4.value, -62.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=Date.to_string(date6)).qty_at_date, -5.0)
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date6)).stock_value, -25)
self.assertAlmostEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).qty_available, -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=Datetime.to_string(date6)).stock_value, -25)
def test_at_date_fifo_1(self):
""" 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'
now = Date.from_string(Date.today())
now = Datetime.now()
date1 = now - timedelta(days=8)
date2 = now - timedelta(days=7)
date3 = now - timedelta(days=6)
@@ -3580,18 +3580,18 @@ class TestStockValuation(TransactionCase):
# ending: manual valuation
# ---------------------------------------------------------------------
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=Date.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=Date.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=Date.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=Date.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=Date.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=Date.to_string(date6)).stock_value, 1375)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).qty_at_date, 20)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).stock_value, 200)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).qty_at_date, 30)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).stock_value, 320)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).qty_at_date, 15)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).stock_value, 160)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).qty_at_date, -5)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).stock_value, -125)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).qty_at_date, 95)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).stock_value, 1375)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).qty_at_date, 95)
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.stock_value, 1375)
@@ -3599,25 +3599,25 @@ class TestStockValuation(TransactionCase):
# ending: perpetual valuation
# ---------------------------------------------------------------------
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=Date.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=Date.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=Date.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=Date.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=Date.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=Date.to_string(date6)).stock_value, 1275)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).qty_at_date, 10)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).stock_value, 100)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).qty_at_date, 20)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).stock_value, 220)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).qty_at_date, 5)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).stock_value, 60)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).qty_at_date, -15)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).stock_value, -180)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).qty_at_date, 85)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).stock_value, 1320)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).qty_at_date, 85)
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.stock_value, 1375)
def test_at_date_fifo_2(self):
self.product1.product_tmpl_id.cost_method = 'fifo'
now = Date.from_string(Date.today())
now = Datetime.now()
date1 = now - timedelta(days=8)
date2 = now - timedelta(days=7)
date3 = now - timedelta(days=6)
@@ -3741,18 +3741,18 @@ class TestStockValuation(TransactionCase):
# ending: manual valuation
# ---------------------------------------------------------------------
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=Date.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=Date.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=Date.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=Date.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=Date.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=Date.to_string(date6)).stock_value, 100)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).qty_at_date, 10)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).stock_value, 100)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).qty_at_date, 20)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).stock_value, 250)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).qty_at_date, -10)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).stock_value, -200)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).qty_at_date, 0)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).stock_value, 0)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).qty_at_date, 10)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).stock_value, 100)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).qty_at_date, 10)
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.stock_value, 100)
@@ -3760,17 +3760,17 @@ class TestStockValuation(TransactionCase):
# ending: perpetual valuation
# ---------------------------------------------------------------------
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=Date.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=Date.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=Date.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=Date.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=Date.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=Date.to_string(date6)).stock_value, 100)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).qty_at_date, 10)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date1)).stock_value, 100)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).qty_at_date, 20)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date2)).stock_value, 250)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).qty_at_date, -10)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date3)).stock_value, -150)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).qty_at_date, 0)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date4)).stock_value, 50)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).qty_at_date, 10)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date5)).stock_value, 150)
self.assertEqual(self.product1.with_context(to_date=Datetime.to_string(date6)).qty_at_date, 10)
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.stock_value, 100)
@@ -86,7 +86,7 @@ class TestLifoPrice(common.TransactionCase):
with out_form.move_ids_without_package.new() as move:
move.product_id = product_lifo_icecream
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()
# I assign this outgoing shipment
+1 -1
View File
@@ -41,7 +41,7 @@ class Image(models.AbstractModel):
if max_width or 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
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)
+2 -2
View File
@@ -540,7 +540,7 @@ class Website(models.Model):
if page.view_id and page.view_id.priority != 16:
record['__priority'] = min(round(page.view_id.priority / 32.0, 1), 1)
if page['write_date']:
record['__lastmod'] = page['write_date'][:10]
record['__lastmod'] = page['write_date'].date()
yield record
@api.multi
@@ -563,7 +563,7 @@ class Website(models.Model):
def image_url(self, record, field, size=None):
""" Returns a local url that points to the image field of a given browse record. """
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
return '/web/image/%s/%s/%s%s?unique=%s' % (record._name, record.id, field, size, sha)
+2 -1
View File
@@ -221,7 +221,8 @@ class BlogPost(models.Model):
result = True
for post in self:
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
result &= super(BlogPost, self).write(copy_vals)
self._check_for_publication(vals)
@@ -562,14 +562,14 @@
<title t-esc="blog.name"/>
<link t-att-href="'%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">
<title t-esc="post.name"/>
<link t-att-href="'%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>
<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>
</feed>
</template>
+1 -1
View File
@@ -150,7 +150,7 @@ class Event(models.Model):
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."))
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('dtend').value = fields.Datetime.from_string(event.date_end).replace(tzinfo=pytz.timezone('UTC'))
cal_event.add('summary').value = event.name
+1 -1
View File
@@ -256,7 +256,7 @@ class WebsiteForum(http.Controller):
values.update({
'main_object': 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},
'filters': filters,
'reversed': reversed,
+1 -1
View File
@@ -312,7 +312,7 @@ class Post(models.Model):
@api.depends('vote_count', 'forum_id.relevancy_post_vote', 'forum_id.relevancy_time_decay')
def _compute_relevancy(self):
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)
else:
self.relevancy = 0
+1 -1
View File
@@ -47,7 +47,7 @@ class SaleOrder(models.Model):
@api.depends('team_id.team_type', 'date_order', 'order_line', 'state', 'partner_id')
def _compute_abandoned_cart(self):
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:
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)
+1 -1
View File
@@ -429,7 +429,7 @@
<div id="o-carousel-product" class="carousel slide" data-ride="carousel" data-interval="0">
<div class="carousel-outer">
<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']}"/>
<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" }'/>
+46 -2
View File
@@ -866,13 +866,57 @@ Basic fields
.. autoclass:: odoo.fields.Html
: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
: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
: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:
+2 -7
View File
@@ -358,7 +358,7 @@ class AssetsBundle(object):
attachments = self.env['ir.attachment'].sudo().search(assets_domain)
for attachment in attachments:
asset = assets[attachment.url]
if asset.last_modified > fields.Datetime.from_string(attachment['__last_update']):
if asset.last_modified > attachment['__last_update']:
outdated = True
break
if asset._content is None:
@@ -539,12 +539,7 @@ class WebAsset(object):
if self._filename:
return datetime.fromtimestamp(os.path.getmtime(self._filename))
elif self._ir_attach:
server_format = tools.DEFAULT_SERVER_DATETIME_FORMAT
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)
return self._ir_attach['__last_update']
except Exception:
pass
return datetime(1970, 1, 1)
+1 -6
View File
@@ -139,12 +139,7 @@ class IrHttp(models.AbstractModel):
return werkzeug.utils.redirect(name, 301)
response = werkzeug.wrappers.Response()
server_format = tools.DEFAULT_SERVER_DATETIME_FORMAT
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.last_modified = wdate
response.set_etag(checksum)
response.make_conditional(request.httprequest)
+4 -6
View File
@@ -196,9 +196,9 @@ class IrSequence(models.Model):
def _interpolation_dict():
now = range_date = effective_date = datetime.now(pytz.timezone(self._context.get('tz') or 'UTC'))
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'):
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 = {
'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_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:
date_to = datetime.strptime(date_range.date_from, '%Y-%m-%d') + timedelta(days=-1)
date_to = date_to.strftime('%Y-%m-%d')
date_to = date_range.date_from + timedelta(days=-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:
date_from = datetime.strptime(date_range.date_to, '%Y-%m-%d') + timedelta(days=1)
date_from = date_from.strftime('%Y-%m-%d')
date_from = date_range.date_to + timedelta(days=1)
seq_date_range = self.env['ir.sequence.date_range'].sudo().create({
'date_from': date_from,
'date_to': date_to,
@@ -22,7 +22,7 @@ class TestIrSequenceDateRangeStandard(SingleTransactionCase):
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. """
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)})
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):
""" 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
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)})
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):
""" Make some use of the sequences to create some subsequences """
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']
seq16 = self.env['ir.sequence'].with_context({'ir_sequence_date': january(16)})
+5 -4
View File
@@ -5,6 +5,7 @@ import json
import pkgutil
import re
from odoo import fields
from odoo.tests import common
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'})
self.assertFalse(result['messages'])
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'])
# UTC-0930
@@ -1073,7 +1074,7 @@ class test_datetime(ImporterCase):
['value'], [['2012-02-03 11:11:11']], {'tz': 'Pacific/Marquesas'})
self.assertFalse(result['messages'])
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'])
def test_usertz(self):
@@ -1087,7 +1088,7 @@ class test_datetime(ImporterCase):
['value'], [['2012-02-03 11:11:11']])
self.assertFalse(result['messages'])
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'])
def test_notz(self):
@@ -1099,7 +1100,7 @@ class test_datetime(ImporterCase):
result = self.import_(['value'], [['2012-02-03 11:11:11']])
self.assertFalse(result['messages'])
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'])
@@ -1,11 +1,13 @@
#
# 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.tools import mute_logger, float_repr, pycompat
from odoo.tools.date_utils import add, subtract, start_of, end_of
class TestFields(common.TransactionCase):
@@ -484,20 +486,117 @@ class TestFields(common.TransactionCase):
record.date = None
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)
self.assertEqual(record.date, '2012-05-01')
self.assertEqual(record.date, date(2012, 5, 1))
record.date = datetime(2012, 5, 1, 10, 45, 00)
self.assertEqual(record.date, '2012-05-01')
with self.assertRaises(TypeError):
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'
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):
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):
""" test selection fields """
record = self.env['test_new_api.mixed'].create({})
+116 -53
View File
@@ -4,7 +4,8 @@
""" High-level objects for fields. """
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 operator import attrgetter
import itertools
@@ -21,7 +22,8 @@ except ImportError:
import psycopg2
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_DATETIME_FORMAT as DATETIME_FORMAT
from .tools.translate import html_translate, _
@@ -1516,23 +1518,30 @@ class Date(Field):
column_type = ('date', 'date')
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
def today(*args):
""" Return the current day in the format expected by the ORM.
This function may be used to compute default values.
"""
return date.today().strftime(DATE_FORMAT)
return date.today()
@staticmethod
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
values.
"""
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
values.
:param datetime timestamp: optional datetime value to use instead of
the current date and time (must be a datetime, regular dates
can't be converted between timezones.)
:rtype: str
:param record: recordset from which the timezone will be obtained.
:param datetime timestamp: optional datetime value to use instead of
the current date and time (must be a datetime, regular dates
can't be converted between timezones).
:rtype: date
"""
today = timestamp or datetime.now()
context_today = None
@@ -1544,33 +1553,55 @@ class Date(Field):
except Exception:
_logger.debug("failed to compute context/client-specific today date, using UTC value for `today`",
exc_info=True)
return (context_today or today).strftime(DATE_FORMAT)
return (context_today or today).date()
@staticmethod
def from_string(value):
""" Convert an ORM ``value`` into a :class:`date` value. """
def to_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:
return None
if isinstance(value, date):
if isinstance(value, datetime):
return value.date()
return value
value = value[:DATE_LENGTH]
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
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):
return super(Date, self).convert_to_column(value or None, record, values, validate)
:param value: value to convert.
: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):
if not value:
return False
if isinstance(value, pycompat.string_types):
if validate:
# force parsing for validation
self.from_string(value)
return value[:DATE_LENGTH]
return self.to_string(value)
if isinstance(value, datetime):
raise TypeError("%s (field %s) must be string or date, not datetime." % (value, self))
return self.from_string(value)
def convert_to_export(self, value, record):
if not value:
@@ -1583,26 +1614,40 @@ class Datetime(Field):
column_type = ('timestamp', 'timestamp')
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
def now(*args):
""" Return the current day and time in the format expected by the ORM.
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
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,
because datetime fields are automatically converted upon
display on client side. For default values :meth:`fields.datetime.now`
should be used instead.
"""
Returns the given timestamp converted to the client's timezone.
This method is *not* meant for use as a default initializer,
because datetime fields are automatically converted upon
display on client side. For default values, :meth:`fields.Datetime.now`
should be used instead.
:param datetime timestamp: naive datetime value (expressed in UTC)
to be converted to the client timezone
:rtype: datetime
:return: timestamp converted to timezone-aware datetime in context
timezone
:param record: recordset from which the timezone will be obtained.
:param datetime timestamp: naive datetime value (expressed in UTC)
to be converted to the client timezone.
:rtype: datetime
:return: timestamp converted to timezone-aware datetime in context timezone.
"""
assert isinstance(timestamp, datetime), 'Datetime instance expected'
tz_name = record._context.get('tz') or record.env.user.tz
@@ -1618,35 +1663,53 @@ class Datetime(Field):
return utc_timestamp
@staticmethod
def from_string(value):
""" Convert an ORM ``value`` into a :class:`datetime` value. """
def to_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:
return None
value = value[:DATETIME_LENGTH]
if len(value) == DATE_LENGTH:
value += " 00:00:00"
return datetime.strptime(value, DATETIME_FORMAT)
if isinstance(value, date):
if isinstance(value, datetime):
return value
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
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):
return super(Datetime, self).convert_to_column(value or None, record, values, validate)
:param value: value to convert.
: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):
if not value:
return False
if isinstance(value, pycompat.string_types):
if validate:
# force parsing for validation
self.from_string(value)
value = value[:DATETIME_LENGTH]
if len(value) == DATE_LENGTH:
value += " 00:00:00"
return value
return self.to_string(value)
if isinstance(value, date) and not isinstance(value, datetime):
raise TypeError("%s (field %s) must be string or datetime, not date." % (value, self))
return self.from_string(value)
def convert_to_export(self, value, record):
if not value:
+6 -3
View File
@@ -25,6 +25,7 @@ from os.path import join as opj
from zlib import adler32
import babel.core
from datetime import datetime, date
import passlib.utils
import psycopg2
import json
@@ -44,10 +45,11 @@ except ImportError:
psutil = None
import odoo
from odoo import fields
from .service.server import memory_info
from .service import security, model as service_model
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
@@ -617,6 +619,7 @@ class JsonRequest(WebRequest):
self.context = self.params.pop('context', dict(self.session.context))
def _json_response(self, result=None, error=None):
response = {
'jsonrpc': '2.0',
'id': self.jsonrequest.get('id')
@@ -632,10 +635,10 @@ class JsonRequest(WebRequest):
# We need then to manage http sessions manually.
response['session_id'] = self.session.sid
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:
mime = 'application/json'
body = json.dumps(response, default=ustr)
body = json.dumps(response, default=date_utils.json_default)
return Response(
body, status=error and error.pop('http_status', 200) or 200,
+16 -5
View File
@@ -120,6 +120,7 @@ import traceback
from functools import partial
from zlib import crc32
from datetime import date, datetime, time
import odoo.modules
from odoo.tools import pycompat
from ..models import MAGIC_COLUMNS, BaseModel
@@ -1095,12 +1096,22 @@ class expression(object):
# -------------------------------------------------
else:
if field.type == 'datetime' and right and len(right) == 10:
if operator in ('>', '<='):
right += ' 23:59:59'
if field.type == 'datetime' and right:
if isinstance(right, pycompat.string_types) and len(right) == 10:
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:
right += ' 00:00:00'
push(create_substitution_leaf(leaf, (left, operator, right), model))
push_result(leaf)
elif field.translate is True and right:
need_wildcard = operator in ('like', 'ilike', 'not like', 'not ilike')
-8
View File
@@ -26,12 +26,6 @@ psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
_logger = logging.getLogger(__name__)
types_mapping = {
'date': (1082,),
'time': (1083,),
'datetime': (1114,),
}
def unbuffer(symb, cr):
if symb is None:
return None
@@ -42,8 +36,6 @@ def undecimalize(symb, cr):
return None
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))
+116
View File
@@ -2,8 +2,10 @@
import math
import calendar
from datetime import date, datetime, time
from dateutil.relativedelta import relativedelta
from . import ustr
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]
date_to = type(date)(date.year + 1, month, min(day, max_day))
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)