def test_01_code_and_format(self): date_str = '2017-01-31' lang = self.env['res.lang'] # Activate French and Simplified Chinese (test with non-ASCII characters) lang.search([('active', '=', False), ('code', 'in', ['fr_FR', 'zh_CN'])]).write({'active': True}) # -- test `date` # Change a single parameter self.assertEqual(misc.format_date(lang.with_context(lang='fr_FR').env, date_str), '31/01/2017') self.assertEqual(misc.format_date(lang.env, date_str, lang_code='fr_FR'), '31/01/2017') self.assertEqual(misc.format_date(lang.env, date_str, date_format='MMM d, y'), 'Jan 31, 2017') # Change 2 parameters self.assertEqual(misc.format_date(lang.with_context(lang='zh_CN').env, date_str, lang_code='fr_FR'), '31/01/2017') self.assertEqual(misc.format_date(lang.with_context(lang='zh_CN').env, date_str, date_format='MMM d, y'), u'1\u6708 31, 2017') self.assertEqual(misc.format_date(lang.env, date_str, lang_code='fr_FR', date_format='MMM d, y'), 'janv. 31, 2017') # Change 3 parameters self.assertEqual(misc.format_date(lang.with_context(lang='zh_CN').env, date_str, lang_code='en_US', date_format='MMM d, y'), 'Jan 31, 2017') # -- test `datetime` datetime_str = '2017-01-31 10:33:00' # Change languages and timezones self.assertEqual(misc.format_datetime(lang.with_context(lang='fr_FR').env, datetime_str, tz='Europe/Brussels'), '31 janv. 2017 à 11:33:00') self.assertEqual(misc.format_datetime(lang.with_context(lang='zh_CN').env, datetime_str, tz='America/New_York'), '2017\u5E741\u670831\u65E5 \u4E0A\u53485:33:00') # '2017年1月31日 上午5:33:00' # Change language, timezone and format self.assertEqual(misc.format_datetime(lang.with_context(lang='fr_FR').env, datetime_str, tz='America/New_York', dt_format='short'), '31/01/2017 05:33') self.assertEqual(misc.format_datetime(lang.with_context(lang='en_US').env, datetime_str, tz='Europe/Brussels', dt_format='MMM d, y'), 'Jan 31, 2017') # Check given `lang_code` overwites context lang self.assertEqual(misc.format_datetime(lang.env, datetime_str, tz='Europe/Brussels', dt_format='long', lang_code='fr_FR'), '31 janvier 2017 à 11:33:00 +0100') self.assertEqual(misc.format_datetime(lang.with_context(lang='zh_CN').env, datetime_str, tz='Europe/Brussels', dt_format='long', lang_code='en_US'), 'January 31, 2017 at 11:33:00 AM +0100') # -- test `time` time_part = datetime.time(16, 30, 22) time_part_tz = datetime.time(16, 30, 22, tzinfo=pytz.timezone('US/Eastern')) # 4:30 PM timezoned self.assertEqual(misc.format_time(lang.with_context(lang='fr_FR').env, time_part), '16:30:22') self.assertEqual(misc.format_time(lang.with_context(lang='zh_CN').env, time_part), '\u4e0b\u53484:30:22') # Check format in different languages self.assertEqual(misc.format_time(lang.with_context(lang='fr_FR').env, time_part, time_format='short'), '16:30') self.assertEqual(misc.format_time(lang.with_context(lang='zh_CN').env, time_part, time_format='short'), '\u4e0b\u53484:30') # Check timezoned time part self.assertIn(misc.format_time(lang.with_context(lang='fr_FR').env, time_part_tz, time_format='long'), ['16:30:22 -0504', '16:30:22 HNE']) self.assertEqual(misc.format_time(lang.with_context(lang='zh_CN').env, time_part_tz, time_format='full'), '\u5317\u7f8e\u4e1c\u90e8\u6807\u51c6\u65f6\u95f4\u0020\u4e0b\u53484:30:22') # Check given `lang_code` overwites context lang self.assertEqual(misc.format_time(lang.with_context(lang='fr_FR').env, time_part, time_format='short', lang_code='zh_CN'), '\u4e0b\u53484:30') self.assertEqual(misc.format_time(lang.with_context(lang='zh_CN').env, time_part, time_format='medium', lang_code='fr_FR'), '16:30:22')
def test_00_accepted_types(self): datetime_str = '2017-01-31 12:00:00' date_datetime = datetime.datetime.strptime(datetime_str, "%Y-%m-%d %H:%M:%S") date_date = date_datetime.date() date_str = '2017-01-31' time_part = datetime.time(16, 30, 22) self.assertEqual(misc.format_date(self.env, date_datetime), '01/31/2017') self.assertEqual(misc.format_date(self.env, date_date), '01/31/2017') self.assertEqual(misc.format_date(self.env, date_str), '01/31/2017') self.assertEqual(misc.format_date(self.env, ''), '') self.assertEqual(misc.format_date(self.env, False), '') self.assertEqual(misc.format_date(self.env, None), '') self.assertEqual(misc.format_datetime(self.env, date_datetime), 'Jan 31, 2017, 1:00:00 PM') self.assertEqual(misc.format_datetime(self.env, datetime_str), 'Jan 31, 2017, 1:00:00 PM') self.assertEqual(misc.format_datetime(self.env, ''), '') self.assertEqual(misc.format_datetime(self.env, False), '') self.assertEqual(misc.format_datetime(self.env, None), '') self.assertEqual(misc.format_time(self.env, time_part), '4:30:22 PM') self.assertEqual(misc.format_time(self.env, ''), '') self.assertEqual(misc.format_time(self.env, False), '') self.assertEqual(misc.format_time(self.env, None), '')
def _get_statement_line(self, st_line): """ Returns the data required by the bank statement reconciliation widget to display a statement line """ statement_currency = st_line.journal_id.currency_id or st_line.journal_id.company_id.currency_id if st_line.amount_currency and st_line.currency_id: amount = st_line.amount_currency amount_currency = st_line.amount amount_currency_str = formatLang(self.env, abs(amount_currency), currency_obj=statement_currency) else: amount = st_line.amount amount_currency = amount amount_currency_str = "" amount_str = formatLang(self.env, abs(amount), currency_obj=st_line.currency_id or statement_currency) data = { 'id': st_line.id, 'ref': st_line.ref, 'note': st_line.note or "", 'name': st_line.name, 'date': format_date(self.env, st_line.date), 'amount': amount, 'amount_str': amount_str, # Amount in the statement line currency 'currency_id': st_line.currency_id.id or statement_currency.id, 'partner_id': st_line.partner_id.id, 'journal_id': st_line.journal_id.id, 'statement_id': st_line.statement_id.id, 'account_id': [ st_line.journal_id.default_debit_account_id.id, st_line.journal_id.default_debit_account_id.display_name ], 'account_code': st_line.journal_id.default_debit_account_id.code, 'account_name': st_line.journal_id.default_debit_account_id.name, 'partner_name': st_line.partner_id.name, 'communication_partner_name': st_line.partner_name, 'amount_currency_str': amount_currency_str, # Amount in the statement currency 'amount_currency': amount_currency, # Amount in the statement currency 'has_no_partner': not st_line.partner_id.id, 'company_id': st_line.company_id.id, } if st_line.partner_id: data[ 'open_balance_account_id'] = amount > 0 and st_line.partner_id.property_account_receivable_id.id or st_line.partner_id.property_account_payable_id.id return data
def _prepare_move_lines(self, move_lines, target_currency=False, target_date=False, recs_count=0): """ Returns move lines formatted for the manual/bank reconciliation widget :param move_line_ids: :param target_currency: currency (browse) you want the move line debit/credit converted into :param target_date: date to use for the monetary conversion """ context = dict(self._context or {}) ret = [] for line in move_lines: company_currency = line.company_id.currency_id line_currency = (line.currency_id and line.amount_currency ) and line.currency_id or company_currency ret_line = { 'id': line.id, 'name': line.name and line.name != '/' and line.move_id.name != line.name and line.move_id.name + ': ' + line.name or line.move_id.name, 'ref': line.move_id.ref or '', # For reconciliation between statement transactions and already registered payments (eg. checks) # NB : we don't use the 'reconciled' field because the line we're selecting is not the one that gets reconciled 'account_id': [line.account_id.id, line.account_id.display_name], 'already_paid': line.account_id.internal_type == 'liquidity', 'account_code': line.account_id.code, 'account_name': line.account_id.name, 'account_type': line.account_id.internal_type, 'date_maturity': format_date(self.env, line.date_maturity), 'date': format_date(self.env, line.date), 'journal_id': [line.journal_id.id, line.journal_id.display_name], 'partner_id': line.partner_id.id, 'partner_name': line.partner_id.name, 'currency_id': line_currency.id, } debit = line.debit credit = line.credit amount = line.amount_residual amount_currency = line.amount_residual_currency # For already reconciled lines, don't use amount_residual(_currency) if line.account_id.internal_type == 'liquidity': amount = debit - credit amount_currency = line.amount_currency target_currency = target_currency or company_currency # Use case: # Let's assume that company currency is in USD and that we have the 3 following move lines # Debit Credit Amount currency Currency # 1) 25 0 0 NULL # 2) 17 0 25 EUR # 3) 33 0 25 YEN # # If we ask to see the information in the reconciliation widget in company currency, we want to see # The following information # 1) 25 USD (no currency information) # 2) 17 USD [25 EUR] (show 25 euro in currency information, in the little bill) # 3) 33 USD [25 YEN] (show 25 yen in currency information) # # If we ask to see the information in another currency than the company let's say EUR # 1) 35 EUR [25 USD] # 2) 25 EUR (no currency information) # 3) 50 EUR [25 YEN] # In that case, we have to convert the debit-credit to the currency we want and we show next to it # the value of the amount_currency or the debit-credit if no amount currency if target_currency == company_currency: if line_currency == target_currency: amount = amount amount_currency = "" total_amount = debit - credit total_amount_currency = "" else: amount = amount amount_currency = amount_currency total_amount = debit - credit total_amount_currency = line.amount_currency if target_currency != company_currency: if line_currency == target_currency: amount = amount_currency amount_currency = "" total_amount = line.amount_currency total_amount_currency = "" else: amount_currency = line.currency_id and amount_currency or amount company = line.account_id.company_id date = target_date or line.date amount = company_currency._convert(amount, target_currency, company, date) total_amount = company_currency._convert( (line.debit - line.credit), target_currency, company, date) total_amount_currency = line.currency_id and line.amount_currency or ( line.debit - line.credit) ret_line['recs_count'] = recs_count ret_line['debit'] = amount > 0 and amount or 0 ret_line['credit'] = amount < 0 and -amount or 0 ret_line['amount_currency'] = amount_currency ret_line['amount_str'] = formatLang(self.env, abs(amount), currency_obj=target_currency) ret_line['total_amount_str'] = formatLang( self.env, abs(total_amount), currency_obj=target_currency) ret_line['amount_currency_str'] = amount_currency and formatLang( self.env, abs(amount_currency), currency_obj=line_currency) or "" ret_line[ 'total_amount_currency_str'] = total_amount_currency and formatLang( self.env, abs(total_amount_currency), currency_obj=line_currency) or "" ret.append(ret_line) return ret
def amend_entries(self): # set the accrual account on the selected journal items accrual_account = self.revenue_accrual_account if self.account_type == 'income' else self.expense_accrual_account # Generate journal entries. move_data = {} for aml in self.active_move_line_ids: ref1 = _( 'Accrual Adjusting Entry (%s%% recognized) for invoice: %s' ) % (self.percentage, aml.move_id.name) ref2 = _( 'Accrual Adjusting Entry (%s%% recognized) for invoice: %s' ) % (100 - self.percentage, aml.move_id.name) move_data.setdefault( aml.move_id, ( [ # Values to create moves. { 'date': self.date, 'ref': ref1, 'journal_id': self.journal_id.id, 'line_ids': [], }, { 'date': aml.move_id.date, 'ref': ref2, 'journal_id': self.journal_id.id, 'line_ids': [], }, ], [ # Messages to log on the chatter. (_('Accrual Adjusting Entry ({percent}% recognized) for invoice:' ) + ' <a href=# data-oe-model=account.move data-oe-id={id}>{name}</a>' ).format( percent=self.percentage, id=aml.move_id.id, name=aml.move_id.name, ), (_('Accrual Adjusting Entry ({percent}% recognized) for invoice:' ) + ' <a href=# data-oe-model=account.move data-oe-id={id}>{name}</a>' ).format( percent=100 - self.percentage, id=aml.move_id.id, name=aml.move_id.name, ), (_('Accrual Adjusting Entries ({percent}%% recognized) have been created for this invoice on {date}' ) + ' <a href=# data-oe-model=account.move data-oe-id=%(first_id)d>%(first_name)s</a> and <a href=# data-oe-model=account.move data-oe-id=%(second_id)d>%(second_name)s</a>' ).format( percent=self.percentage, date=format_date(self.env, self.date), ), ])) reported_debit = aml.company_id.currency_id.round( (self.percentage / 100) * aml.debit) reported_credit = aml.company_id.currency_id.round( (self.percentage / 100) * aml.credit) if aml.currency_id: reported_amount_currency = aml.currency_id.round( (self.percentage / 100) * aml.amount_currency) else: reported_amount_currency = 0.0 move_data[aml.move_id][0][0]['line_ids'] += [ (0, 0, { 'name': aml.name, 'debit': reported_debit, 'credit': reported_credit, 'amount_currency': reported_amount_currency, 'currency_id': aml.currency_id.id, 'account_id': aml.account_id.id, 'partner_id': aml.partner_id.id, }), (0, 0, { 'name': ref1, 'debit': reported_credit, 'credit': reported_debit, 'amount_currency': -reported_amount_currency, 'currency_id': aml.currency_id.id, 'account_id': accrual_account.id, 'partner_id': aml.partner_id.id, }), ] move_data[aml.move_id][0][1]['line_ids'] += [ (0, 0, { 'name': aml.name, 'debit': reported_credit, 'credit': reported_debit, 'amount_currency': -reported_amount_currency, 'currency_id': aml.currency_id.id, 'account_id': aml.account_id.id, 'partner_id': aml.partner_id.id, }), (0, 0, { 'name': ref2, 'debit': reported_debit, 'credit': reported_credit, 'amount_currency': reported_amount_currency, 'currency_id': aml.currency_id.id, 'account_id': accrual_account.id, 'partner_id': aml.partner_id.id, }), ] move_vals = [] log_messages = [] for v in move_data.values(): move_vals += v[0] log_messages += v[1] created_moves = self.env['account.move'].create(move_vals) created_moves.post() # Reconcile. index = 0 for move in self.active_move_line_ids.mapped('move_id'): accrual_moves = created_moves[index:index + 2] to_reconcile = accrual_moves.mapped('line_ids').filtered( lambda line: line.account_id == accrual_account) to_reconcile.reconcile() move.message_post( body=log_messages[index // 2 + 2] % { 'first_id': accrual_moves[0].id, 'first_name': accrual_moves[0].name, 'second_id': accrual_moves[1].id, 'second_name': accrual_moves[1].name, }) accrual_moves[0].message_post(body=log_messages[index // 2 + 0]) accrual_moves[1].message_post(body=log_messages[index // 2 + 1]) index += 2 # open the generated entries action = { 'name': _('Generated Entries'), 'domain': [('id', 'in', created_moves.ids)], 'res_model': 'account.move', 'view_mode': 'tree,form', 'type': 'ir.actions.act_window', 'views': [(self.env.ref('account.view_move_tree').id, 'tree'), (False, 'form')], } if len(created_moves) == 1: action.update({'view_mode': 'form', 'res_id': created_moves.id}) return action
def _compute_date_deadline_formatted(self): for task in self: task.date_deadline_formatted = format_date(self.env, task.date_deadline) if task.date_deadline else None
def _check_hash_integrity(self): """Checks that all posted moves have still the same data as when they were posted and raises an error with the result. """ def build_move_info(move): return (move.name, move.inalterable_hash, fields.Date.to_string(move.date)) journals = self.env['account.journal'].search([('company_id', '=', self.id)]) results_by_journal = { 'results': [], 'printing_date': format_date(self.env, fields.Date.to_string(fields.Date.today())) } for journal in journals: rslt = { 'journal_name': journal.name, 'journal_code': journal.code, 'restricted_by_hash_table': journal.restrict_mode_hash_table and 'V' or 'X', 'msg_cover': '', 'first_hash': 'None', 'first_move_name': 'None', 'first_move_date': 'None', 'last_hash': 'None', 'last_move_name': 'None', 'last_move_date': 'None', } if not journal.restrict_mode_hash_table: rslt.update( {'msg_cover': _('This journal is not in strict mode.')}) results_by_journal['results'].append(rslt) continue all_moves_count = self.env['account.move'].search_count([ ('state', '=', 'posted'), ('journal_id', '=', journal.id) ]) moves = self.env['account.move'].search( [('state', '=', 'posted'), ('journal_id', '=', journal.id), ('secure_sequence_number', '!=', 0)], order="secure_sequence_number ASC") if not moves: rslt.update({ 'msg_cover': _('There isn\'t any journal entry flagged for data inalterability yet for this journal.' ), }) results_by_journal['results'].append(rslt) continue previous_hash = u'' start_move_info = [] hash_corrupted = False for move in moves: if move.inalterable_hash != move._compute_hash( previous_hash=previous_hash): rslt.update({ 'msg_cover': _('Corrupted data on journal entry with id %s.') % move.id }) results_by_journal['results'].append(rslt) hash_corrupted = True break if not previous_hash: #save the date and sequence number of the first move hashed start_move_info = build_move_info(move) previous_hash = move.inalterable_hash end_move_info = build_move_info(move) if hash_corrupted: continue rslt.update({ 'first_move_name': start_move_info[0], 'first_hash': start_move_info[1], 'first_move_date': format_date(self.env, start_move_info[2]), 'last_move_name': end_move_info[0], 'last_hash': end_move_info[1], 'last_move_date': format_date(self.env, end_move_info[2]), }) if len(moves) == all_moves_count: rslt.update({'msg_cover': _('All entries are hashed.')}) else: rslt.update({ 'msg_cover': _('Entries are hashed from %s (%s)') % (start_move_info[0], format_date(self.env, start_move_info[2])) }) results_by_journal['results'].append(rslt) return results_by_journal