[REF] *: use native date/datetime for Date/Datetime fields
From this commit onwards, Date fields will return datetime.date objects and Datetime fields will return datetime.datetime objects, this implies a number of things that are clearly explained both in the ORM API for master. This commit also introduces a number of helper functions for dates and datetimes that are exposed in tools.date_utils and fields.Date[time], explained in the documentation as well. Task-ID: 47189
This commit is contained in:
committed by
Adrian Torres
parent
09ba95b2d0
commit
960360afe4
@@ -2,7 +2,6 @@
|
||||
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo 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.'))
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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,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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,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')
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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 []
|
||||
|
||||
@@ -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..')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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().
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,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
@@ -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
@@ -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
@@ -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')
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user