Merge branch '8.0'

Conflicts:
	README.md
	__unported__/account_cancel_invoice_check_payment_order/__openerp__.py
	__unported__/account_cancel_invoice_check_voucher/__openerp__.py
	__unported__/account_compute_tax_amount/__openerp__.py
	__unported__/account_fiscal_position_vat_check/__openerp__.py
	__unported__/account_move_validation_improvement/__openerp__.py
	__unported__/account_reversal/__openerp__.py
	__unported__/currency_rate_date_check/__openerp__.py
This commit is contained in:
Alexandre Fayolle
2014-08-21 08:50:16 +02:00
73 changed files with 2078 additions and 686 deletions

View File

@@ -1,27 +1,33 @@
# Config file .travis.yml
language: python
python:
- "2.7"
env:
- VERSION="8.0" ODOO_REPO="odoo/odoo" EXCLUDE="account_constraints,account_partner_required,async_move_line_importer"
- VERSION="8.0" ODOO_REPO="odoo/odoo" INCLUDE="account_constraints"
- VERSION="8.0" ODOO_REPO="odoo/odoo" INCLUDE="account_partner_required"
- VERSION="8.0" ODOO_REPO="odoo/odoo" INCLUDE="async_move_line_importer"
- VERSION="8.0" ODOO_REPO="OCA/OCB" EXCLUDE="account_constraints, account_partner_required,async_move_line_importer"
- VERSION="8.0" ODOO_REPO="OCA/OCB" INCLUDE="account_constraints"
- VERSION="8.0" ODOO_REPO="OCA/OCB" INCLUDE="account_partner_required"
- VERSION="8.0" ODOO_REPO="odoo/odoo" INCLUDE="async_move_line_importer"
virtualenv:
system_site_packages: true
before_install:
- git clone https://github.com/nbessi/maintainer-quality-tools.git $HOME/maintainer-quality-tools -b consolidated
- git clone https://github.com/OCA/connector $HOME/connector -b 7.0
env:
- PATH=$HOME/maintainer-quality-tools/travis:$PATH
services:
- postgresql
install:
- $HOME/maintainer-quality-tools/travis/travis_install_nightly 7.0
- pip install coveralls flake8
- git clone https://github.com/oca/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools
- git clone https://github.com/OCA/connector $HOME/connector -b ${VERSION}
- export PATH=${HOME}/maintainer-quality-tools/travis:${PATH}
- travis_install_nightly ${VERSION}
script:
- travis_run_flake8 steps
- travis_run_tests_encapsulated 7.0 account $HOME/connector
# - travis_run_flake8
- travis_run_tests
after_success:
coveralls

View File

@@ -1,3 +1,6 @@
[![Build Status](https://travis-ci.org/OCA/account-financial-tools.svg?branch=8.0)](https://travis-ci.org/OCA/account-financial-tools)
[![Coverage Status](https://coveralls.io/repos/OCA/account-financial-tools/badge.png?branch=8.0)](https://coveralls.io/r/OCA/account-financial-tools?branch=8.0)
Account financial Tools for Odoo/OpenERP
========================================

View File

@@ -30,6 +30,6 @@ payment order.
""",
'website': 'http://www.camptocamp.com',
'data': [],
'installable': True,
'installable': False,
'active': False,
}

View File

@@ -30,7 +30,7 @@ Constraint forbidding to cancel an invoice already
imported in bank statement with a voucher.
""",
'website': 'http://www.camptocamp.com',
'date' : [],
'date': [],
'installable': False,
'active': False,
}

View File

@@ -32,26 +32,33 @@ class account_invoice(orm.Model):
invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
for invoice in invoices:
if invoice['move_id']:
# This invoice have a move line, we search move_line concerned by this move
cr.execute("""SELECT abs.name AS statement_name,
abs.date AS statement_date,
absl.name
FROM account_bank_statement_line AS absl
INNER JOIN account_bank_statement AS abs
ON absl.statement_id = abs.id
WHERE EXISTS (SELECT 1
FROM account_voucher_line JOIN account_move_line ON
(account_voucher_line.move_line_id = account_move_line.id)
WHERE voucher_id=absl.voucher_id
AND account_move_line.move_id = %s )""",
(invoice['move_id'][0],))
# This invoice have a move line, we search move_line concerned
# by this move
cr.execute(
"""
SELECT abs.name AS statement_name,
abs.date AS statement_date,
absl.name
FROM account_bank_statement_line AS absl
INNER JOIN account_bank_statement AS abs
ON absl.statement_id = abs.id
WHERE EXISTS (SELECT 1
FROM account_voucher_line JOIN account_move_line ON
(account_voucher_line.move_line_id = account_move_line.id)
WHERE voucher_id=absl.voucher_id
AND account_move_line.move_id = %s )
""",
(invoice['move_id'][0],)
)
statement_lines = cr.dictfetchone()
if statement_lines:
raise osv.except_osv(
_('Error!'),
_('Invoice already imported in bank statment (%s) at %s on line %s'
_('Invoice already imported in bank statment (%s) '
'at %s on line %s'
% (statement_lines['statement_name'],
statement_lines['statement_date'],
statement_lines['name'],)))
return super(account_invoice, self).action_cancel(cr, uid, ids, context=context)
return super(account_invoice, self).action_cancel(cr, uid, ids,
context=context)

View File

@@ -3,7 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (c) 2010 Zikzakmedia S.L. (http://www.zikzakmedia.com)
# Copyright (c) 2010 Pexego Sistemas Informáticos S.L. (http://www.pexego.es)
# Copyright (c) 2010 Pexego Sistemas Informáticos S.L.(http://www.pexego.es)
# Copyright (c) 2013 Joaquin Gutierrez (http://www.gutierrezweb.es)
# Pedro Manuel Baeza <pedro.baeza@serviciosbaeza.com>
# $Id$

View File

@@ -2,7 +2,7 @@
##############################################################################
#
# Copyright (c) 2010 Zikzakmedia S.L. (http://www.zikzakmedia.com)
# Copyright (c) 2010 Pexego Sistemas Informáticos S.L. (http://www.pexego.es)
# Copyright (c) 2010 Pexego Sistemas Informáticos S.L.(http://www.pexego.es)
# @authors: Jordi Esteve (Zikzakmedia), Borja López Soilán (Pexego)
#
# This program is free software: you can redistribute it and/or modify
@@ -32,7 +32,8 @@ def _reopen(self, res_id, model):
'res_id': res_id,
'res_model': self._name,
'target': 'new',
# save original model in context, because selecting the list of available
# save original model in context,
# because selecting the list of available
# templates requires a model in context
'context': {
'default_model': model,
@@ -118,8 +119,10 @@ class wizard_update_charts_accounts(orm.TransientModel):
_get_lang_selection_options,
'Language',
size=5,
help="For records searched by name (taxes, tax codes, fiscal positions), "
"the template name will be matched against the record name on this language."
help="For records searched by name "
"(taxes, tax codes, fiscal positions), "
"the template name will be matched against the record "
"name on this language."
),
'update_tax_code': fields.boolean(
'Update tax codes',
@@ -132,11 +135,13 @@ class wizard_update_charts_accounts(orm.TransientModel):
),
'update_account': fields.boolean(
'Update accounts',
help="Existing accounts are updated. Accounts are searched by code."
help="Existing accounts are updated. "
"Accounts are searched by code."
),
'update_fiscal_position': fields.boolean(
'Update fiscal positions',
help="Existing fiscal positions are updated. Fiscal positions are searched by name."
help="Existing fiscal positions are updated. "
"Fiscal positions are searched by name."
),
'update_children_accounts_parent': fields.boolean(
"Update children accounts parent",
@@ -163,18 +168,32 @@ class wizard_update_charts_accounts(orm.TransientModel):
'Taxes',
ondelete='cascade'
),
'account_ids': fields.one2many('wizard.update.charts.accounts.account',
'update_chart_wizard_id', 'Accounts', ondelete='cascade'),
'fiscal_position_ids': fields.one2many('wizard.update.charts.accounts.fiscal.position',
'update_chart_wizard_id', 'Fiscal positions', ondelete='cascade'),
'account_ids': fields.one2many(
'wizard.update.charts.accounts.account',
'update_chart_wizard_id',
'Accounts',
ondelete='cascade'
),
'fiscal_position_ids': fields.one2many(
'wizard.update.charts.accounts.fiscal.position',
'update_chart_wizard_id',
'Fiscal positions',
ondelete='cascade'
),
'new_tax_codes': fields.integer('New tax codes', readonly=True),
'new_taxes': fields.integer('New taxes', readonly=True),
'new_accounts': fields.integer('New accounts', readonly=True),
'new_fps': fields.integer('New fiscal positions', readonly=True),
'updated_tax_codes': fields.integer('Updated tax codes', readonly=True),
'updated_tax_codes': fields.integer(
'Updated tax codes',
readonly=True
),
'updated_taxes': fields.integer('Updated taxes', readonly=True),
'updated_accounts': fields.integer('Updated accounts', readonly=True),
'updated_fps': fields.integer('Updated fiscal positions', readonly=True),
'updated_fps': fields.integer(
'Updated fiscal positions',
readonly=True
),
'log': fields.text('Messages and Errors', readonly=True)
}
@@ -262,8 +281,9 @@ class wizard_update_charts_accounts(orm.TransientModel):
_defaults = {
'state': 'init',
'company_id': lambda self, cr, uid, context: self.pool.get('res.users').browse(
cr, uid, [uid], context)[0].company_id.id,
'company_id': lambda self, cr, uid, context:
self.pool.get('res.users').browse(
cr, uid, [uid], context)[0].company_id.id,
'chart_template_id': _get_chart,
'update_tax_code': True,
'update_tax': True,
@@ -271,7 +291,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
'update_fiscal_position': True,
'update_children_accounts_parent': True,
'continue_on_errors': False,
'lang': lambda self, cr, uid, context: context and context.get('lang') or None,
'lang': lambda self, cr, uid, context:
context and context.get('lang') or None,
}
def onchange_company_id(self, cr, uid, ids, company_id, context=None):
@@ -280,7 +301,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
"""
res = {
'value': {
'code_digits': self._get_code_digits(cr, uid, context=context, company_id=company_id),
'code_digits': self._get_code_digits(
cr, uid, context=context, company_id=company_id),
}
}
return res
@@ -378,7 +400,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
acc_templ_mapping[acc_templ.id] = acc_ids and acc_ids[0] or False
return acc_templ_mapping[acc_templ.id]
def _map_fp_template(self, cr, uid, wizard, fp_templ_mapping, fp_template, context=None):
def _map_fp_template(self, cr, uid, wizard, fp_templ_mapping,
fp_template, context=None):
"""
Adds a fiscal position template -> fiscal position id to the mapping.
"""
@@ -562,7 +585,9 @@ class wizard_update_charts_accounts(orm.TransientModel):
for delay_vals_wiz in delay_wiz_tax:
wiz_taxes_obj.create(cr, uid, delay_vals_wiz, context)
return {'new': new_taxes, 'updated': updated_taxes, 'mapping': tax_templ_mapping}
return {'new': new_taxes,
'updated': updated_taxes,
'mapping': tax_templ_mapping}
def _find_accounts(self, cr, uid, wizard, context=None):
"""
@@ -614,7 +639,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
modified = False
notes = ""
account = acc_obj.browse(cr, uid, account_id, context=context)
if account.name != acc_templ.name and account.name != wizard.company_id.name:
if (account.name != acc_templ.name and
account.name != wizard.company_id.name):
notes += _("The name is different.\n")
modified = True
if account.type != acc_templ.type:
@@ -689,14 +715,18 @@ class wizard_update_charts_accounts(orm.TransientModel):
if fp_templ.tax_ids and fp.tax_ids:
for fp_tax_templ in fp_templ.tax_ids:
found = False
tax_src_id = self._map_tax_template(cr, uid, wizard,
tax_templ_mapping,
fp_tax_templ.tax_src_id,
context=None)
tax_dest_id = self._map_tax_template(cr, uid, wizard,
tax_templ_mapping,
fp_tax_templ.tax_dest_id,
context=None)
tax_src_id = self._map_tax_template(
cr, uid, wizard,
tax_templ_mapping,
fp_tax_templ.tax_src_id,
context=None
)
tax_dest_id = self._map_tax_template(
cr, uid, wizard,
tax_templ_mapping,
fp_tax_templ.tax_dest_id,
context=None
)
for fp_tax in fp.tax_ids:
if fp_tax.tax_src_id.id == tax_src_id:
if not fp_tax.tax_dest_id:
@@ -708,12 +738,18 @@ class wizard_update_charts_accounts(orm.TransientModel):
found = True
break
if not found:
notes += _("Tax mapping not found on the fiscal position instance: %s -> %s.\n") % (
msg = (fp_tax_templ.tax_dest_id and
fp_tax_templ.tax_dest_id.name or
_('None'))
notes += _("Tax mapping not found on the fiscal "
"position instance: %s -> %s.\n") % (
fp_tax_templ.tax_src_id.name,
fp_tax_templ.tax_dest_id and fp_tax_templ.tax_dest_id.name or _('None'))
msg
)
modified = True
elif fp_templ.tax_ids and not fp.tax_ids:
notes += _("The template has taxes the fiscal position instance does not.\n")
notes += _("The template has taxes the fiscal "
"position instance does not.\n")
modified = True
# Check fiscal position accounts for changes
if fp_templ.account_ids and fp.account_ids:
@@ -751,7 +787,9 @@ class wizard_update_charts_accounts(orm.TransientModel):
'update_fiscal_position_id': fp_id,
'notes': notes,
}, context=context)
return {'new': new_fps, 'updated': updated_fps, 'mapping': fp_templ_mapping}
return {'new': new_fps,
'updated': updated_fps,
'mapping': fp_templ_mapping}
def action_find_records(self, cr, uid, ids, context=None):
"""
@@ -822,12 +860,13 @@ class wizard_update_charts_accounts(orm.TransientModel):
tax_code_template.parent_id,
context=context)
# Values
p_id = tax_code_template.parent_id.id
vals = {
'name': tax_code_name,
'code': tax_code_template.code,
'info': tax_code_template.info,
'parent_id': (tax_code_template.parent_id and
tax_code_template_mapping.get(tax_code_template.parent_id.id)),
tax_code_template_mapping.get(p_id)),
'company_id': wizard.company_id.id,
'sign': tax_code_template.sign,
}
@@ -847,16 +886,19 @@ class wizard_update_charts_accounts(orm.TransientModel):
updated_tax_codes += 1
modified = True
else:
tax_code_id = wiz_tax_code.update_tax_code_id and wiz_tax_code.update_tax_code_id.id
tax_code_id = (wiz_tax_code.update_tax_code_id and
wiz_tax_code.update_tax_code_id.id)
modified = False
# Store the tax codes on the map
tax_code_template_mapping[tax_code_template.id] = tax_code_id
if modified:
# Detect errors
p_id = tax_code_template.parent_id.id
if (tax_code_template.parent_id and
not tax_code_template_mapping.get(tax_code_template.parent_id.id)):
not tax_code_template_mapping.get(p_id)):
log.add(
_("Tax code %s: The parent tax code %s can not be set.\n") % (
_("Tax code %s: The parent tax code %s "
"can not be set.\n") % (
tax_code_name, tax_code_template.parent_id.name),
True
)
@@ -866,7 +908,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
'mapping': tax_code_template_mapping
}
def _update_taxes(self, cr, uid, wizard, log, tax_code_template_mapping, context=None):
def _update_taxes(self, cr, uid, wizard, log, tax_code_template_mapping,
context=None):
"""
Search for, and load, tax templates to create/update.
"""
@@ -887,7 +930,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
tax_template.ref_base_code_id,
tax_template.ref_tax_code_id
]
for tax_code_template in [tmpl for tmpl in tax_code_templates_to_find if tmpl]:
for tax_code_template in \
[tmpl for tmpl in tax_code_templates_to_find if tmpl]:
self._map_tax_code_template(cr, uid, wizard,
tax_code_template_mapping,
tax_code_template, context=context)
@@ -899,21 +943,35 @@ class wizard_update_charts_accounts(orm.TransientModel):
'type': tax_template.type,
'applicable_type': tax_template.applicable_type,
'domain': tax_template.domain,
'parent_id': tax_template.parent_id and tax_template_mapping.get(tax_template.parent_id.id),
'parent_id': (
tax_template.parent_id and
tax_template_mapping.get(tax_template.parent_id.id)
),
'child_depend': tax_template.child_depend,
'python_compute': tax_template.python_compute,
'python_compute_inv': tax_template.python_compute_inv,
'python_applicable': tax_template.python_applicable,
'base_code_id': (tax_template.base_code_id and
tax_code_template_mapping.get(tax_template.base_code_id.id)),
'tax_code_id': (tax_template.tax_code_id and
tax_code_template_mapping.get(tax_template.tax_code_id.id)),
'base_code_id': (
tax_template.base_code_id and
tax_code_template_mapping.get(tax_template.base_code_id.id)
),
'tax_code_id': (
tax_template.tax_code_id and
tax_code_template_mapping.get(tax_template.tax_code_id.id)
),
'base_sign': tax_template.base_sign,
'tax_sign': tax_template.tax_sign,
'ref_base_code_id': (tax_template.ref_base_code_id and
tax_code_template_mapping.get(tax_template.ref_base_code_id.id)),
'ref_tax_code_id': (tax_template.ref_tax_code_id and
tax_code_template_mapping.get(tax_template.ref_tax_code_id.id)),
'ref_base_code_id': (
tax_template.ref_base_code_id and
tax_code_template_mapping.get(
tax_template.ref_base_code_id.id
)
),
'ref_tax_code_id': (
tax_template.ref_tax_code_id and
tax_code_template_mapping.get(
tax_template.ref_tax_code_id.id)
),
'ref_base_sign': tax_template.ref_base_sign,
'ref_tax_sign': tax_template.ref_tax_sign,
'include_base_amount': tax_template.include_base_amount,
@@ -943,41 +1001,63 @@ class wizard_update_charts_accounts(orm.TransientModel):
if modified:
# Add to the dict of taxes waiting for accounts.
taxes_pending_for_accounts[tax_id] = {
'account_collected_id': (tax_template.account_collected_id and
tax_template.account_collected_id.id or False),
'account_paid_id': (tax_template.account_paid_id and
tax_template.account_paid_id.id or False),
'account_collected_id': (
tax_template.account_collected_id and
tax_template.account_collected_id.id or False
),
'account_paid_id': (
tax_template.account_paid_id and
tax_template.account_paid_id.id or False
),
}
# Detect errors
if tax_template.parent_id and not tax_template_mapping.get(tax_template.parent_id.id):
if (tax_template.parent_id and not
tax_template_mapping.get(tax_template.parent_id.id)):
log.add(
_("Tax %s: The parent tax %s can not be set.\n") % (
tax_template.name, tax_template.parent_id.name),
True
)
if tax_template.base_code_id and not tax_code_template_mapping.get(tax_template.base_code_id.id):
if (tax_template.base_code_id and not
tax_code_template_mapping.get(
tax_template.base_code_id.id
)):
log.add(
_("Tax %s: The tax code for the base %s can not be set.\n") % (
_("Tax %s: The tax code for the base %s "
"can not be set.\n") % (
tax_template.name, tax_template.base_code_id.name),
True
)
if tax_template.tax_code_id and not tax_code_template_mapping.get(tax_template.tax_code_id.id):
if (tax_template.tax_code_id and not
tax_code_template_mapping.get(
tax_template.tax_code_id.id)):
log.add(
_("Tax %s: The tax code for the tax %s can not be set.\n") % (
tax_template.name, tax_template.tax_code_id.name),
_("Tax %s: The tax code for the tax %s "
"can not be set.\n") % (
tax_template.name, tax_template.tax_code_id.name
),
True
)
if (tax_template.ref_base_code_id and
not tax_code_template_mapping.get(tax_template.ref_base_code_id.id)):
not tax_code_template_mapping.get(
tax_template.ref_base_code_id.id)):
log.add(
_("Tax %s: The tax code for the base refund %s can not be set.\n") % (
tax_template.name, tax_template.ref_base_code_id.name),
_("Tax %s: The tax code for the base refund %s "
"can not be set.\n") % (
tax_template.name,
tax_template.ref_base_code_id.name
),
True
)
if tax_template.ref_tax_code_id and not tax_code_template_mapping.get(tax_template.ref_tax_code_id.id):
if (tax_template.ref_tax_code_id and not
tax_code_template_mapping.get(
tax_template.ref_tax_code_id.id)):
log.add(
_("Tax %s: The tax code for the tax refund %s can not be set.\n") % (
tax_template.name, tax_template.ref_tax_code_id.name),
_("Tax %s: The tax code for the tax refund %s"
" can not be set.\n") % (
tax_template.name,
tax_template.ref_tax_code_id.name
),
True
)
return {
@@ -987,7 +1067,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
'pending': taxes_pending_for_accounts
}
def _update_children_accounts_parent(self, cr, uid, wizard, log, parent_account_id, context=None):
def _update_children_accounts_parent(self, cr, uid, wizard,
log, parent_account_id, context=None):
"""
Updates the parent_id of accounts that seem to be children of the
given account (accounts that start with the same code and are brothers
@@ -1010,24 +1091,30 @@ class wizard_update_charts_accounts(orm.TransientModel):
if children_ids:
try:
account_account.write(cr, uid, children_ids, {'parent_id': parent_account.id}, context=context)
account_account.write(cr, uid, children_ids,
{'parent_id': parent_account.id},
context=context)
except orm.except_orm, ex:
log.add(
_("Exception setting the parent of account %s children: %s - %s.\n") % (
parent_account.code, ex.name, ex.value),
_("Exception setting the parent of "
"account %s children: %s - %s.\n") % (
parent_account.code, ex.name, ex.value
),
True
)
return True
def _update_accounts(self, cr, uid, wizard, log, tax_template_mapping, context=None):
def _update_accounts(self, cr, uid, wizard, log, tax_template_mapping,
context=None):
"""
Search for, and load, account templates to create/update.
"""
accounts = self.pool.get('account.account')
root_account_id = wizard.chart_template_id.account_root_id.id
# Disable the parent_store computing on account_account during the batch
# processing, we will force _parent_store_compute afterwards.
# Disable the parent_store computing on account_account
# during the batch processing,
# we will force _parent_store_compute afterwards.
self.pool._init = True
new_accounts = 0
updated_accounts = 0
@@ -1035,7 +1122,10 @@ class wizard_update_charts_accounts(orm.TransientModel):
for wiz_account in wizard.account_ids:
account_template = wiz_account.account_id
# Ensure the parent account template is on the map.
self._map_account_template(cr, uid, wizard, account_template_mapping, account_template.parent_id, context)
self._map_account_template(cr, uid, wizard,
account_template_mapping,
account_template.parent_id,
context)
# Ensure the related tax templates are on the map.
for tax_template in account_template.tax_ids:
self._map_tax_template(cr, uid, wizard, tax_template_mapping,
@@ -1052,17 +1142,27 @@ class wizard_update_charts_accounts(orm.TransientModel):
code = '%s%s' % (
code, '0' * (wizard.code_digits - len(code)))
# Values
p_id = account_template.parent_id.id
vals = {
'name': (root_account_id == account_template.id) and wizard.company_id.name or account_template.name,
'currency_id': account_template.currency_id and account_template.currency_id.id or False,
'name': ((root_account_id == account_template.id) and
wizard.company_id.name or
account_template.name),
'currency_id': (account_template.currency_id and
account_template.currency_id.id or
False),
'code': code,
'type': account_template.type,
'user_type': account_template.user_type and account_template.user_type.id or False,
'user_type': (account_template.user_type and
account_template.user_type.id or
False),
'reconcile': account_template.reconcile,
'shortcut': account_template.shortcut,
'note': account_template.note,
'parent_id': (account_template.parent_id
and account_template_mapping.get(account_template.parent_id.id) or False),
'parent_id': (
account_template.parent_id
and account_template_mapping.get(p_id) or
False
),
'tax_ids': [(6, 0, tax_ids)],
'company_id': wizard.company_id.id,
}
@@ -1090,15 +1190,20 @@ class wizard_update_charts_accounts(orm.TransientModel):
log.add(_("Exception writing account %s: %s - %s.\n")
% (code, ex.name, ex.value), True)
else:
account_id = wiz_account.update_account_id and wiz_account.update_account_id.id
account_id = (wiz_account.update_account_id and
wiz_account.update_account_id.id)
# Store the account on the map
account_template_mapping[account_template.id] = account_id
if modified:
# Detect errors
if account_template.parent_id and not account_template_mapping.get(account_template.parent_id.id):
if (account_template.parent_id and not
account_template_mapping.get(
account_template.parent_id.id)):
log.add(
_("Account %s: The parent account %s can not be set.\n") % (
code, account_template.parent_id.code),
_("Account %s: The parent account %s "
"can not be set.\n") % (
code, account_template.parent_id.code
),
True
)
# Set this account as the parent of the accounts that seem to
@@ -1142,25 +1247,40 @@ class wizard_update_charts_accounts(orm.TransientModel):
if (acc_templ_mapping.get(value['account_collected_id']) and
acc_templ_mapping.get(value['account_paid_id'])):
vals = {
'account_collected_id': acc_templ_mapping[value['account_collected_id']],
'account_paid_id': acc_templ_mapping[value['account_paid_id']],
'account_collected_id': acc_templ_mapping[
value['account_collected_id']
],
'account_paid_id': acc_templ_mapping[
value['account_paid_id']
],
}
taxes.write(cr, uid, [key], vals)
else:
tax = taxes.browse(cr, uid, key)
if not acc_templ_mapping.get(value['account_collected_id']):
log.add(_("Tax %s: The collected account can not be set.\n") % (tax.name), True)
val = value['account_collected_id']
if not acc_templ_mapping.get(val):
log.add(
_("Tax %s: The collected account "
"can not be set.\n") % (
tax.name
),
True
)
if not acc_templ_mapping.get(value['account_paid_id']):
log.add(_("Tax %s: The paid account can not be set.\n")
% (tax.name), True)
def _update_fiscal_positions(self, cr, uid, wizard, log, tax_template_mapping, acc_templ_mapping, context=None):
def _update_fiscal_positions(self, cr, uid, wizard, log,
tax_template_mapping,
acc_templ_mapping, context=None):
"""
Search for, and load, fiscal position templates to create/update.
"""
fiscalpositions = self.pool.get('account.fiscal.position')
fiscalpositions_taxes = self.pool.get('account.fiscal.position.tax')
fiscalpositions_account = self.pool.get('account.fiscal.position.account')
fiscalpositions_account = self.pool.get(
'account.fiscal.position.account'
)
new_fps = 0
updated_fps = 0
@@ -1178,7 +1298,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
fp_id = fiscalpositions.create(cr, uid, vals_fp)
new_fps += 1
modified = True
elif wizard.update_fiscal_position and wiz_fp.update_fiscal_position_id:
elif (wizard.update_fiscal_position and
wiz_fp.update_fiscal_position_id):
# Update the given fiscal position (remove the tax and account
# mappings, that will be regenerated later)
fp_id = wiz_fp.update_fiscal_position_id.id
@@ -1193,33 +1314,47 @@ class wizard_update_charts_accounts(orm.TransientModel):
cr, uid, [('position_id', '=', fp_id)])
fiscalpositions_account.unlink(cr, uid, fp_account_ids)
else:
fp_id = wiz_fp.update_fiscal_position_id and wiz_fp.update_fiscal_position_id.id
fp_id = (wiz_fp.update_fiscal_position_id and
wiz_fp.update_fiscal_position_id.id)
if modified:
# (Re)create the tax mappings
for fp_tax in fp_template.tax_ids:
# Ensure the related tax templates are on the map.
self._map_tax_template(cr, uid, wizard, tax_template_mapping, fp_tax.tax_src_id, context)
self._map_tax_template(cr, uid, wizard,
tax_template_mapping,
fp_tax.tax_src_id, context)
if fp_tax.tax_dest_id:
self._map_tax_template(cr, uid, wizard, tax_template_mapping, fp_tax.tax_dest_id, context)
self._map_tax_template(cr, uid, wizard,
tax_template_mapping,
fp_tax.tax_dest_id, context)
# Create the fp tax mapping
vals_tax = {
'tax_src_id': tax_template_mapping.get(fp_tax.tax_src_id.id),
'tax_dest_id': fp_tax.tax_dest_id and tax_template_mapping.get(fp_tax.tax_dest_id.id),
'tax_src_id': tax_template_mapping.get(
fp_tax.tax_src_id.id
),
'tax_dest_id': (fp_tax.tax_dest_id and
tax_template_mapping.get(
fp_tax.tax_dest_id.id)),
'position_id': fp_id,
}
fiscalpositions_taxes.create(cr, uid, vals_tax)
# Check for errors
if not tax_template_mapping.get(fp_tax.tax_src_id.id):
log.add(
_("Fiscal position %s: The source tax %s can not be set.\n") % (
fp_template.name, fp_tax.tax_src_id.code),
_("Fiscal position %s: The source tax %s "
"can not be set.\n") % (
fp_template.name, fp_tax.tax_src_id.code
),
True
)
if fp_tax.tax_dest_id and not tax_template_mapping.get(fp_tax.tax_dest_id.id):
if fp_tax.tax_dest_id and not tax_template_mapping.get(
fp_tax.tax_dest_id.id):
log.add(
_("Fiscal position %s: The destination tax %s can not be set.\n") % (
fp_template.name, fp_tax.tax_dest_id.name),
_("Fiscal position %s: The destination"
"tax %s can not be set.\n") % (
fp_template.name, fp_tax.tax_dest_id.name
),
True
)
# (Re)create the account mappings
@@ -1236,23 +1371,37 @@ class wizard_update_charts_accounts(orm.TransientModel):
context=context)
# Create the fp account mapping
vals_account = {
'account_src_id': acc_templ_mapping.get(fp_account.account_src_id.id),
'account_dest_id': (fp_account.account_dest_id and
acc_templ_mapping.get(fp_account.account_dest_id.id)),
'account_src_id': acc_templ_mapping.get(
fp_account.account_src_id.id
),
'account_dest_id': (
fp_account.account_dest_id and
acc_templ_mapping.get(
fp_account.account_dest_id.id
)
),
'position_id': fp_id,
}
fiscalpositions_account.create(cr, uid, vals_account)
# Check for errors
if not acc_templ_mapping.get(fp_account.account_src_id.id):
log.add(
_("Fiscal position %s: The source account %s can not be set.\n") % (
fp_template.name, fp_account.account_src_id.code),
_("Fiscal position %s: The source account %s "
"can not be set.\n") % (
fp_template.name,
fp_account.account_src_id.code
),
True
)
if fp_account.account_dest_id and not acc_templ_mapping.get(fp_account.account_dest_id.id):
if (fp_account.account_dest_id and not
acc_templ_mapping.get(
fp_account.account_dest_id.id)):
log.add(
_("Fiscal position %s: The destination account %s can not be set.\n") % (
fp_template.name, fp_account.account_dest_id.code),
_("Fiscal position %s: The destination account %s "
"can not be set.\n") % (
fp_template.name,
fp_account.account_dest_id.code
),
True
)
@@ -1363,15 +1512,28 @@ class wizard_update_charts_accounts_tax(orm.TransientModel):
"""
_name = 'wizard.update.charts.accounts.tax'
_columns = {
'tax_id': fields.many2one('account.tax.template', 'Tax template', required=True, ondelete='set null'),
'tax_id': fields.many2one(
'account.tax.template',
'Tax template',
required=True,
ondelete='set null'
),
'update_chart_wizard_id': fields.many2one(
'wizard.update.charts.accounts', 'Update chart wizard', required=True, ondelete='cascade'),
'wizard.update.charts.accounts',
'Update chart wizard',
required=True,
ondelete='cascade'
),
'type': fields.selection([
('new', 'New template'),
('updated', 'Updated template'),
], 'Type'),
'update_tax_id': fields.many2one(
'account.tax', 'Tax to update', required=False, ondelete='set null'),
'account.tax',
'Tax to update',
required=False,
ondelete='set null'
),
'notes': fields.text('Notes'),
}

View File

@@ -25,7 +25,7 @@
"author": "Camptocamp",
"description": """Recompute tax_amount to avoid sign problem""",
'website': 'http://www.camptocamp.com',
'data' : [],
'data': [],
'installable': False,
'active': False,
}

View File

@@ -30,14 +30,16 @@ class account_move_line(orm.Model):
# We set the tax_amount invisible, because we recompute it in every case.
_columns = {
'tax_amount': fields.float('Tax/Base Amount',
digits_compute=dp.get_precision('Account'),
invisible=True,
select=True,
help="If the Tax account is a tax code account, "
"this field will contain the taxed amount. "
"If the tax account is base tax code, "
"this field will contain the basic amount (without tax)."),
'tax_amount': fields.float(
'Tax/Base Amount',
digits_compute=dp.get_precision('Account'),
invisible=True,
select=True,
help="If the Tax account is a tax code account, "
"this field will contain the taxed amount. "
"If the tax account is base tax code, "
"this field will contain the basic amount (without tax)."
),
}
def create(self, cr, uid, vals, context=None, check=True):
@@ -55,11 +57,14 @@ class account_move_line(orm.Model):
context=context)
return result
def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
result = super(account_move_line, self).write(cr, uid, ids, vals,
context=context,
check=check,
update_check=update_check)
def write(self, cr, uid, ids, vals, context=None, check=True,
update_check=True):
result = super(account_move_line, self).write(
cr, uid, ids, vals,
context=context,
check=check,
update_check=update_check
)
if result:
if ('debit' in vals) or ('credit' in vals):
move_lines = self.read(cr, uid, ids,

View File

@@ -51,8 +51,8 @@ Summary of constraints are:
invoice or a bank statement, no matter what the status of the move
(draft, validated or posted). This is useful in a standard context but
even more if you're using `account_default_draft_move`. This way you ensure
that the user cannot make mistakes even in draft state, he must pass through the
parent object to make his modification.
that the user cannot make mistakes even in draft state, he must pass
through the parent object to make his modification.
Contributors
* Stéphane Bidoul <stephane.bidoul@acsone.eu>

View File

@@ -62,9 +62,12 @@ class AccountMoveLine(orm.Model):
_inherit = 'account.move.line'
def _authorized_reconcile(self, vals):
""" Check if only reconcile_id and/or reconcile_partial_id are altered. We
cannot change other vals, but we should be able to write or unlink those field
(e.g. when you want to manually unreconcile an entry generated by an invoice).
""" Check if only reconcile_id and/or reconcile_partial_id are altered.
We cannot change other vals, but we should be able to write or unlink
those field.
e.g. when you want to manually unreconcile an entry
generated by an invoice
"""
if not vals:
return False
@@ -72,7 +75,8 @@ class AccountMoveLine(orm.Model):
write_keys = set(vals)
return rec_keys.issuperset(write_keys)
def _check_invoice_related_move(self, cr, uid, ids, vals=None, context=None):
def _check_invoice_related_move(self, cr, uid, ids, vals=None,
context=None):
for line in self.browse(cr, uid, ids, context=context):
if line.invoice:
if self._authorized_reconcile(vals):
@@ -81,11 +85,14 @@ class AccountMoveLine(orm.Model):
(line.invoice.name, line.invoice.id))
raise osv.except_osv(
_('Error'),
_('You cannot do this on an entry generated by an invoice. You must '
'change the related invoice directly.\n%s.') % err_msg)
_('You cannot do this on an entry generated '
'by an invoice. You must change the related '
'invoice directly.\n%s.') % err_msg
)
return True
def _check_statement_related_move(self, cr, uid, ids, vals=None, context=None):
def _check_statement_related_move(self, cr, uid, ids, vals=None,
context=None):
for line in self.browse(cr, uid, ids, context=context):
if line.statement_id:
if self._authorized_reconcile(vals):
@@ -94,8 +101,10 @@ class AccountMoveLine(orm.Model):
(line.statement_id.name, line.statement_id.id))
raise osv.except_osv(
_('Error'),
_('You cannot do this on an entry generated by a bank statement. '
'You must change the related bank statement directly.\n%s.') % err_msg)
_('You cannot do this on an entry generated '
'by a bank statement. You must change the related'
'bank statement directly.\n%s.') % err_msg
)
return True
def unlink(self, cr, uid, ids, context=None, check=True):
@@ -103,7 +112,8 @@ class AccountMoveLine(orm.Model):
- Is the move related to an invoice
- Is the move related to a bank statement
- Is other values than reconcile_partial_id and/or reconcile_id modified
- Is other values than reconcile_partial_id and/or
reconcile_id modified
In that case, we forbid the move to be deleted even if draft. We
should never delete directly a move line related or generated by
@@ -115,14 +125,18 @@ class AccountMoveLine(orm.Model):
if not context.get('from_parent_object', False):
self._check_invoice_related_move(cr, uid, ids, context=context)
self._check_statement_related_move(cr, uid, ids, context=context)
return super(AccountMoveLine, self).unlink(cr, uid, ids, context=context, check=check)
return super(AccountMoveLine, self).unlink(cr, uid, ids,
context=context,
check=check)
def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
def write(self, cr, uid, ids, vals, context=None, check=True,
update_check=True):
""" Add the following checks:
- Is the move related to an invoice
- Is the move related to a bank statement
- Is other values than reconcile_partial_id and/or reconcile_id modified
- Is other values than reconcile_partial_id and/or
reconcile_id modified
In that case, we forbid the move to be modified even if draft.
We should never update directly a move line related or generated
@@ -132,8 +146,10 @@ class AccountMoveLine(orm.Model):
if context is None:
context = {}
if not context.get('from_parent_object', False):
self._check_invoice_related_move(cr, uid, ids, vals, context=context)
self._check_statement_related_move(cr, uid, ids, vals, context=context)
self._check_invoice_related_move(cr, uid, ids, vals,
context=context)
self._check_statement_related_move(cr, uid, ids, vals,
context=context)
return super(AccountMoveLine, self).write(
cr, uid, ids, vals,
context=context, check=check, update_check=update_check
@@ -194,7 +210,8 @@ class AccountInvoice(orm.Model):
else:
context = context.copy()
context['from_parent_object'] = True
return super(AccountInvoice, self).action_cancel(cr, uid, ids, context=context)
return super(AccountInvoice, self).action_cancel(cr, uid, ids,
context=context)
def action_move_create(self, cr, uid, ids, context=None):
"""Override the method to add the key 'from_parent_object' in
@@ -204,7 +221,8 @@ class AccountInvoice(orm.Model):
else:
context = context.copy()
context['from_parent_object'] = True
return super(AccountInvoice, self).action_move_create(cr, uid, ids, context=context)
return super(AccountInvoice, self).action_move_create(cr, uid, ids,
context=context)
class AccountBankStatement(orm.Model):
@@ -220,9 +238,11 @@ class AccountBankStatement(orm.Model):
else:
context = context.copy()
context['from_parent_object'] = True
return super(AccountBankStatement, self).button_cancel(cr, uid, ids, context=context)
return super(AccountBankStatement, self).button_cancel(cr, uid, ids,
context=context)
def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id,
def create_move_from_st_line(self, cr, uid, st_line_id,
company_currency_id,
st_line_number, context=None):
"""Add the from_parent_object key in context in order to be able
to post the move.

View File

@@ -44,7 +44,8 @@ class CreditControlLine(orm.Model):
required=True,
select=True
),
# maturity date of related move line we do not use a related field in order to
# maturity date of related move line we do not use
# a related field in order to
# allow manual changes
'date_due': fields.date(
'Due date',
@@ -196,7 +197,8 @@ class CreditControlLine(orm.Model):
data['date_due'] = move_line.date_maturity
data['state'] = 'draft'
data['channel'] = level.channel
data['invoice_id'] = move_line.invoice.id if move_line.invoice else False
data['invoice_id'] = (move_line.invoice.id if
move_line.invoice else False)
data['partner_id'] = move_line.partner_id.id
data['amount_due'] = (move_line.amount_currency or move_line.debit or
move_line.credit)
@@ -282,7 +284,9 @@ class CreditControlLine(orm.Model):
if line.state != 'draft':
raise orm.except_orm(
_('Error !'),
_('You are not allowed to delete a credit control line that '
'is not in draft state.'))
_('You are not allowed to delete a credit control '
'line that is not in draft state.')
)
return super(CreditControlLine, self).unlink(cr, uid, ids, context=context)
return super(CreditControlLine, self).unlink(cr, uid, ids,
context=context)

View File

@@ -63,7 +63,8 @@ class CreditControlPolicy(orm.Model):
'active': True,
}
def _move_lines_domain(self, cr, uid, policy, controlling_date, context=None):
def _move_lines_domain(self, cr, uid, policy, controlling_date,
context=None):
"""Build the default domain for searching move lines"""
account_ids = [a.id for a in policy.account_ids]
return [('account_id', 'in', account_ids),
@@ -74,8 +75,8 @@ class CreditControlPolicy(orm.Model):
def _due_move_lines(self, cr, uid, policy, controlling_date, context=None):
""" Get the due move lines for the policy of the company.
The set of ids will be reduced and extended according to the specific policies
defined on partners and invoices.
The set of ids will be reduced and extended according
to the specific policies defined on partners and invoices.
Do not use direct SQL in order to respect security rules.
@@ -88,7 +89,8 @@ class CreditControlPolicy(orm.Model):
return set()
domain_line = self._move_lines_domain(cr, uid, policy,
controlling_date, context=context)
controlling_date,
context=context)
return set(move_l_obj.search(cr, uid, domain_line, context=context))
def _move_lines_subset(self, cr, uid, policy, controlling_date,
@@ -131,7 +133,8 @@ class CreditControlPolicy(orm.Model):
if add_obj_ids:
domain = list(default_domain)
domain.append((move_relation_field, 'in', add_obj_ids))
to_add_ids = set(move_l_obj.search(cr, uid, domain, context=context))
to_add_ids = set(move_l_obj.search(cr, uid, domain,
context=context))
# The lines which are linked to another policy do not have to be
# included in the run for this policy.
@@ -143,10 +146,12 @@ class CreditControlPolicy(orm.Model):
if neg_obj_ids:
domain = list(default_domain)
domain.append((move_relation_field, 'in', neg_obj_ids))
to_remove_ids = set(move_l_obj.search(cr, uid, domain, context=context))
to_remove_ids = set(move_l_obj.search(cr, uid, domain,
context=context))
return to_add_ids, to_remove_ids
def _get_partner_related_lines(self, cr, uid, policy, controlling_date, context=None):
def _get_partner_related_lines(self, cr, uid, policy, controlling_date,
context=None):
""" Get the move lines for a policy related to a partner.
:param browse_record policy: policy
@@ -158,9 +163,11 @@ class CreditControlPolicy(orm.Model):
the process
"""
return self._move_lines_subset(cr, uid, policy, controlling_date,
'res.partner', 'partner_id', context=context)
'res.partner', 'partner_id',
context=context)
def _get_invoice_related_lines(self, cr, uid, policy, controlling_date, context=None):
def _get_invoice_related_lines(self, cr, uid, policy, controlling_date,
context=None):
""" Get the move lines for a policy related to an invoice.
:param browse_record policy: policy
@@ -172,10 +179,13 @@ class CreditControlPolicy(orm.Model):
the process
"""
return self._move_lines_subset(cr, uid, policy, controlling_date,
'account.invoice', 'invoice', context=context)
'account.invoice', 'invoice',
context=context)
def _get_move_lines_to_process(self, cr, uid, policy_id, controlling_date, context=None):
"""Build a list of move lines ids to include in a run for a policy at a given date.
def _get_move_lines_to_process(self, cr, uid, policy_id, controlling_date,
context=None):
"""Build a list of move lines ids to include in a run
for a policy at a given date.
:param int/long policy: id of the policy
:param str controlling_date: date of credit control
@@ -189,7 +199,8 @@ class CreditControlPolicy(orm.Model):
policy = self.browse(cr, uid, policy_id, context=context)
# there is a priority between the lines, depicted by the calls below
# warning, side effect method called on lines
lines = self._due_move_lines(cr, uid, policy, controlling_date, context=context)
lines = self._due_move_lines(cr, uid, policy, controlling_date,
context=context)
add_ids, remove_ids = self._get_partner_related_lines(cr, uid, policy,
controlling_date,
context=context)
@@ -234,8 +245,11 @@ class CreditControlPolicy(orm.Model):
if policy not in allowed:
allowed_names = u"\n".join(x.name for x in allowed)
raise orm.except_orm(
_('You can only use a policy set on account %s') % account.name,
_("Please choose one of the following policies:\n %s") % allowed_names)
_('You can only use a policy set on '
'account %s') % account.name,
_("Please choose one of the following "
"policies:\n %s") % allowed_names
)
return True
@@ -253,19 +267,24 @@ class CreditControlPolicyLevel(orm.Model):
translate=True),
'level': fields.integer('Level', required=True),
'computation_mode': fields.selection([('net_days', 'Due Date'),
('end_of_month', 'Due Date, End Of Month'),
('previous_date', 'Previous Reminder')],
'Compute Mode',
required=True),
'computation_mode': fields.selection(
[('net_days', 'Due Date'),
('end_of_month', 'Due Date, End Of Month'),
('previous_date', 'Previous Reminder')],
'Compute Mode',
required=True
),
'delay_days': fields.integer('Delay (in days)', required='True'),
'email_template_id': fields.many2one('email.template', 'Email Template',
'email_template_id': fields.many2one('email.template',
'Email Template',
required=True),
'channel': fields.selection([('letter', 'Letter'),
('email', 'Email')],
'Channel', required=True),
'custom_text': fields.text('Custom Message', required=True, translate=True),
'custom_text': fields.text('Custom Message',
required=True,
translate=True),
'custom_mail_text': fields.text('Custom Mail Message',
required=True, translate=True),
@@ -281,7 +300,8 @@ class CreditControlPolicyLevel(orm.Model):
cr, uid,
[('policy_id', '=', level.policy_id.id)],
order='level asc', limit=1, context=context)
smallest_level = self.browse(cr, uid, smallest_level_id[0], context)
smallest_level = self.browse(cr, uid, smallest_level_id[0],
context)
if smallest_level.computation_mode == 'previous_date':
return False
return True
@@ -316,16 +336,20 @@ class CreditControlPolicyLevel(orm.Model):
# ----- sql time related methods ---------
def _net_days_get_boundary(self):
return " (mv_line.date_maturity + %(delay)s)::date <= date(%(controlling_date)s)"
return " (mv_line.date_maturity + %(delay)s)::date <= "
"date(%(controlling_date)s)"
def _end_of_month_get_boundary(self):
return ("(date_trunc('MONTH', (mv_line.date_maturity + %(delay)s))+INTERVAL '1 MONTH - 1 day')::date"
return ("(date_trunc('MONTH', (mv_line.date_maturity + %(delay)s))+"
"INTERVAL '1 MONTH - 1 day')::date"
"<= date(%(controlling_date)s)")
def _previous_date_get_boundary(self):
return "(cr_line.date + %(delay)s)::date <= date(%(controlling_date)s)"
def _get_sql_date_boundary_for_computation_mode(self, cr, uid, level, controlling_date, context=None):
def _get_sql_date_boundary_for_computation_mode(self, cr, uid, level,
controlling_date,
context=None):
"""Return a where clauses statement for the given
controlling date and computation mode of the level"""
fname = "_%s_get_boundary" % (level.computation_mode,)
@@ -333,12 +357,15 @@ class CreditControlPolicyLevel(orm.Model):
fnc = getattr(self, fname)
return fnc()
else:
raise NotImplementedError(_('Can not get function for computation mode: '
'%s is not implemented') % (fname,))
raise NotImplementedError(
_('Can not get function for computation mode: '
'%s is not implemented') % (fname,)
)
# -----------------------------------------
def _get_first_level_move_line_ids(self, cr, uid, level, controlling_date, lines, context=None):
def _get_first_level_move_line_ids(self, cr, uid, level, controlling_date,
lines, context=None):
"""Retrieve all the move lines that are linked to a first level.
We use Raw SQL for performance. Security rule where applied in
policy object when the first set of lines were retrieved"""
@@ -358,8 +385,10 @@ class CreditControlPolicyLevel(orm.Model):
" AND state NOT IN ('draft', 'ignored'))"
" AND (mv_line.debit IS NOT NULL AND mv_line.debit != 0.0)\n")
sql += " AND"
sql += self._get_sql_date_boundary_for_computation_mode(cr, uid, level,
controlling_date, context)
sql += self._get_sql_date_boundary_for_computation_mode(
cr, uid, level,
controlling_date, context
)
data_dict = {'controlling_date': controlling_date,
'line_ids': tuple(lines),
'delay': level.delay_days}
@@ -369,7 +398,8 @@ class CreditControlPolicyLevel(orm.Model):
level_lines.update([x[0] for x in res])
return level_lines
def _get_other_level_move_line_ids(self, cr, uid, level, controlling_date, lines, context=None):
def _get_other_level_move_line_ids(self, cr, uid, level, controlling_date,
lines, context=None):
""" Retrieve the move lines for other levels than first level.
"""
level_lines = set()
@@ -379,11 +409,12 @@ class CreditControlPolicyLevel(orm.Model):
" FROM account_move_line mv_line\n"
" JOIN credit_control_line cr_line\n"
" ON (mv_line.id = cr_line.move_line_id)\n"
" WHERE cr_line.id = (SELECT credit_control_line.id FROM credit_control_line\n"
" WHERE credit_control_line.move_line_id = mv_line.id\n"
" AND state != 'ignored'"
" AND NOT manually_overridden"
" ORDER BY credit_control_line.level desc limit 1)\n"
" WHERE cr_line.id = (SELECT credit_control_line.id "
" FROM credit_control_line\n"
" WHERE credit_control_line.move_line_id = mv_line.id\n"
" AND state != 'ignored'"
" AND NOT manually_overridden"
" ORDER BY credit_control_line.level desc limit 1)\n"
" AND cr_line.level = %(previous_level)s\n"
" AND (mv_line.debit IS NOT NULL AND mv_line.debit != 0.0)\n"
# lines from a previous level with a draft or ignored state
@@ -393,10 +424,15 @@ class CreditControlPolicyLevel(orm.Model):
" AND cr_line.state NOT IN ('draft', 'ignored')\n"
" AND mv_line.id in %(line_ids)s\n")
sql += " AND "
sql += self._get_sql_date_boundary_for_computation_mode(cr, uid, level,
controlling_date, context)
previous_level_id = self._previous_level(cr, uid, level, context=context)
previous_level = self.browse(cr, uid, previous_level_id, context=context)
sql += self._get_sql_date_boundary_for_computation_mode(
cr, uid, level,
controlling_date,
context
)
previous_level_id = self._previous_level(cr, uid, level,
context=context)
previous_level = self.browse(cr, uid, previous_level_id,
context=context)
data_dict = {'controlling_date': controlling_date,
'line_ids': tuple(lines),
'delay': level.delay_days,
@@ -409,7 +445,8 @@ class CreditControlPolicyLevel(orm.Model):
level_lines.update([x[0] for x in res])
return level_lines
def get_level_lines(self, cr, uid, level_id, controlling_date, lines, context=None):
def get_level_lines(self, cr, uid, level_id, controlling_date, lines,
context=None):
"""get all move lines in entry lines that match the current level"""
assert not (isinstance(level_id, list) and len(level_id) > 1), \
"level_id: only one id expected"

View File

@@ -25,7 +25,8 @@ from openerp.report import report_sxw
class CreditSummaryReport(report_sxw.rml_parse):
def __init__(self, cr, uid, name, context):
super(CreditSummaryReport, self).__init__(cr, uid, name, context=context)
super(CreditSummaryReport, self).__init__(cr, uid, name,
context=context)
self.localcontext.update({
'time': time,
'cr': cr,

View File

@@ -76,13 +76,17 @@ class CreditControlRun(orm.Model):
cr, uid, id, default=default, context=context)
def _get_policies(self, cr, uid, context=None):
return self.pool['credit.control.policy'].search(cr, uid, [], context=context)
return self.pool['credit.control.policy'].search(cr, uid, [],
context=context)
_defaults = {'state': 'draft',
'policy_ids': _get_policies}
def _check_run_date(self, cr, uid, ids, controlling_date, context=None):
"""Ensure that there is no credit line in the future using controlling_date"""
"""Ensure that there is no credit line in the future
using controlling_date
"""
run_obj = self.pool['credit.control.run']
runs = run_obj.search(cr, uid, [('date', '>', controlling_date)],
order='date DESC', limit=1, context=context)
@@ -126,25 +130,38 @@ class CreditControlRun(orm.Model):
for policy in policies:
if policy.do_nothing:
continue
lines = policy._get_move_lines_to_process(run.date, context=context)
manual_lines = policy._lines_different_policy(lines, context=context)
lines = policy._get_move_lines_to_process(run.date,
context=context)
manual_lines = policy._lines_different_policy(lines,
context=context)
lines.difference_update(manual_lines)
manually_managed_lines.update(manual_lines)
policy_generated_ids = []
if lines:
# policy levels are sorted by level so iteration is in the correct order
# policy levels are sorted by level
# so iteration is in the correct order
for level in reversed(policy.level_ids):
level_lines = level.get_level_lines(run.date, lines, context=context)
policy_generated_ids += cr_line_obj.create_or_update_from_mv_lines(
cr, uid, [], list(level_lines), level.id, run.date, context=context)
level_lines = level.get_level_lines(run.date, lines,
context=context)
policy_generated_ids += \
cr_line_obj.create_or_update_from_mv_lines(
cr, uid,
[],
list(level_lines),
level.id,
run.date,
context=context
)
generated_ids.extend(policy_generated_ids)
if policy_generated_ids:
report += _("Policy \"%s\" has generated %d Credit Control Lines.\n") % \
(policy.name, len(policy_generated_ids))
credit_line_ids += policy_generated_ids
else:
report += _("Policy \"%s\" has not generated any Credit Control Lines.\n" %
policy.name)
report += _(
"Policy \"%s\" has not generated any "
"Credit Control Lines.\n" % policy.name
)
vals = {'state': 'done',
'report': report,
@@ -162,7 +179,8 @@ class CreditControlRun(orm.Model):
cr.execute('SELECT id FROM credit_control_run'
' LIMIT 1 FOR UPDATE NOWAIT')
except Exception:
# In case of exception openerp will do a rollback for us and free the lock
# In case of exception openerp will do a rollback
# for us and free the lock
raise orm.except_orm(_('Error'),
_('A credit control run is already running'
' in background, please try later.'))

View File

@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
import time
from behave import given, when
from support import model

View File

@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
@given(u'I change level for invoice "{invoice_name}" to "{level_name}" of policy "{policy_name}"')
def impl(ctx, invoice_name, level_name, policy_name):
invoice = model('account.invoice').get([('number', '=', invoice_name)])

View File

@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
from support import *
import datetime

View File

@@ -27,34 +27,57 @@ logger = logging.getLogger('credit.control.line.mailing')
class CreditCommunication(TransientModel):
"""Shell class used to provide a base model to email template and reporting.
Il use this approche in version 7 a browse record will exist even if not saved"""
Il use this approche in version 7 a browse record
will exist even if not saved
"""
_name = "credit.control.communication"
_description = "credit control communication"
_rec_name = 'partner_id'
_columns = {'partner_id': fields.many2one('res.partner', 'Partner', required=True),
_columns = {
'partner_id': fields.many2one(
'res.partner',
'Partner',
required=True
),
'current_policy_level': fields.many2one('credit.control.policy.level',
'Level', required=True),
'current_policy_level': fields.many2one(
'credit.control.policy.level',
'Level',
required=True
),
'credit_control_line_ids': fields.many2many('credit.control.line',
rel='comm_credit_rel',
string='Credit Lines'),
'credit_control_line_ids': fields.many2many(
'credit.control.line',
rel='comm_credit_rel',
string='Credit Lines'
),
'company_id': fields.many2one('res.company', 'Company',
required=True),
'company_id': fields.many2one(
'res.company',
'Company',
required=True
),
'user_id': fields.many2one('res.users', 'User')}
'user_id': fields.many2one('res.users', 'User')
}
def _get_comp(self, cr, uid, context=None):
self.pool.get('res.company')._company_default_get(
cr, uid,
'credit.control.policy',
context=context
)
_defaults = {
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(
cr, uid, 'credit.control.policy', context=c),
'company_id': _get_comp,
'user_id': lambda s, cr, uid, c: uid
}
def get_email(self, cr, uid, com_id, context=None):
"""Return a valid email for customer"""
if isinstance(com_id, list):
assert len(com_id) == 1, "get_email only support one id as parameter"
assert len(com_id) == 1, "get_email only support one id as param."
com_id = com_id[0]
form = self.browse(cr, uid, com_id, context=context)
contact = form.get_contact_address()
@@ -70,7 +93,8 @@ class CreditCommunication(TransientModel):
add_id = add_ids.get('invoice', add_ids.get('default', False))
return pmod.browse(cr, uid, add_id, context)
def _get_credit_lines(self, cr, uid, line_ids, partner_id, level_id, context=None):
def _get_credit_lines(self, cr, uid, line_ids, partner_id, level_id,
context=None):
"""Return credit lines related to a partner and a policy level"""
cr_line_obj = self.pool.get('credit.control.line')
cr_l_ids = cr_line_obj.search(cr,
@@ -81,19 +105,25 @@ class CreditCommunication(TransientModel):
context=context)
return cr_l_ids
def _generate_comm_from_credit_line_ids(self, cr, uid, line_ids, context=None):
def _generate_comm_from_credit_line_ids(self, cr, uid, line_ids,
context=None):
"""Aggregate credit control line by partner, level, and currency
It also generate a communication object per aggregation.
"""
if not line_ids:
return []
comms = []
sql = ("SELECT distinct partner_id, policy_level_id, "
" credit_control_line.currency_id, credit_control_policy_level.level"
" FROM credit_control_line JOIN credit_control_policy_level "
" ON (credit_control_line.policy_level_id = credit_control_policy_level.id)"
" WHERE credit_control_line.id in %s"
" ORDER by credit_control_policy_level.level, credit_control_line.currency_id")
sql = (
"SELECT distinct partner_id, policy_level_id, "
" credit_control_line.currency_id, "
" credit_control_policy_level.level"
" FROM credit_control_line JOIN credit_control_policy_level "
" ON (credit_control_line.policy_level_id = "
" credit_control_policy_level.id)"
" WHERE credit_control_line.id in %s"
" ORDER by credit_control_policy_level.level, "
" credit_control_line.currency_id"
)
cr.execute(sql, (tuple(line_ids),))
res = cr.dictfetchall()
@@ -140,13 +170,15 @@ class CreditCommunication(TransientModel):
email_values['body_html'] = email_values['body']
email_values['type'] = 'email'
email_id = email_message_obj.create(cr, uid, email_values, context=context)
email_id = email_message_obj.create(cr, uid, email_values,
context=context)
state = 'sent'
# The mail will not be send, however it will be in the pool, in an
# error state. So we create it, link it with the credit control line
# and put this latter in a `email_error` state we not that we have a
# problem with the email
# error state. So we create it, link it with
# the credit control line
# and put this latter in a `email_error` state we not that we have
# a problem with the email
if any(not email_values.get(field) for field in essential_fields):
state = 'email_error'
@@ -168,7 +200,10 @@ class CreditCommunication(TransientModel):
'res_id': email_id,
'type': 'binary',
}
att_ids.append(att_obj.create(cr, uid, data_attach, context=context))
att_ids.append(
att_obj.create(cr, uid, data_attach,
context=context)
)
email_message_obj.write(cr, uid, [email_id],
{'attachment_ids': [(6, 0, att_ids)]},
context=context)
@@ -176,7 +211,10 @@ class CreditCommunication(TransientModel):
return email_ids
def _generate_report(self, cr, uid, comms, context=None):
"""Will generate a report by inserting mako template of related policy template"""
"""Will generate a report by inserting mako template
of related policy template
"""
service = netsvc.LocalService('report.credit_control_summary')
ids = [x.id for x in comms]
result, format = service.create(cr, uid, ids, {}, {})

View File

@@ -47,7 +47,9 @@ class CreditControlEmailer(orm.TransientModel):
'line_ids': fields.many2many(
'credit.control.line',
string='Credit Control Lines',
domain="[('state', '=', 'to_be_sent'), ('channel', '=', 'email')]"),
domain=[('state', '=', 'to_be_sent'),
('channel', '=', 'email')]
),
}
_defaults = {
@@ -71,7 +73,10 @@ class CreditControlEmailer(orm.TransientModel):
form = self.browse(cr, uid, wiz_id, context)
if not form.line_ids:
raise orm.except_orm(_('Error'), _('No credit control lines selected.'))
raise orm.except_orm(
_('Error'),
_('No credit control lines selected.')
)
line_ids = [l.id for l in form.line_ids]
filtered_ids = self._filter_line_ids(

View File

@@ -69,7 +69,9 @@ class CreditControlMarker(orm.TransientModel):
line_obj = self.pool.get('credit.control.line')
if not state:
raise ValueError(_('state can not be empty'))
line_obj.write(cr, uid, filtered_ids, {'state': state}, context=context)
line_obj.write(cr, uid, filtered_ids,
{'state': state},
context=context)
return filtered_ids
def mark_lines(self, cr, uid, wiz_id, context=None):
@@ -82,7 +84,10 @@ class CreditControlMarker(orm.TransientModel):
form = self.browse(cr, uid, wiz_id, context)
if not form.line_ids:
raise orm.except_orm(_('Error'), _('No credit control lines selected.'))
raise orm.except_orm(
_('Error'),
_('No credit control lines selected.')
)
line_ids = [l.id for l in form.line_ids]
@@ -90,7 +95,8 @@ class CreditControlMarker(orm.TransientModel):
if not filtered_ids:
raise orm.except_orm(
_('Information'),
_('No lines will be changed. All the selected lines are already done.')
_('No lines will be changed. '
'All the selected lines are already done.')
)
self._mark_lines(cr, uid, filtered_ids, form.name, context)

View File

@@ -41,8 +41,10 @@ class CreditControlPrinter(orm.TransientModel):
return res
_columns = {
'mark_as_sent': fields.boolean('Mark letter lines as sent',
help="Only letter lines will be marked."),
'mark_as_sent': fields.boolean(
'Mark letter lines as sent',
help="Only letter lines will be marked."
),
'report_file': fields.binary('Generated Report', readonly=True),
'report_name': fields.char('Report name'),
'state': fields.char('state', size=32),
@@ -90,10 +92,12 @@ class CreditControlPrinter(orm.TransientModel):
comms = comm_obj._generate_comm_from_credit_line_ids(cr, uid, line_ids,
context=context)
report_file = comm_obj._generate_report(cr, uid, comms, context=context)
report_file = comm_obj._generate_report(cr, uid, comms,
context=context)
form.write({'report_file': base64.b64encode(report_file),
'report_name': 'credit_control_esr_bvr_%s.pdf' % fields.datetime.now(),
'report_name': ('credit_control_esr_bvr_%s.pdf' %
fields.datetime.now()),
'state': 'done'})
if form.mark_as_sent:

View File

@@ -41,17 +41,20 @@ class FeesComputer(orm.BaseModel):
def _get_compute_fun(self, level_fees_type):
"""Retrieve function of class that should compute the fees based on type
:param level_fee_type: type exisiting in model `credit.control.policy.level`
:param level_fee_type: type exisiting in model
`credit.control.policy.level`
for field dunning_fees_type
:returns: a function of class :class:`FeesComputer` with following signature
:returns: a function of class :class:`FeesComputer`
with following signature
self, cr, uid, credit_line (record), context
"""
if level_fees_type == 'fixed':
return self.compute_fixed_fees
else:
raise NotImplementedError('fees type %s is not supported' % level_fees_type)
raise NotImplementedError('fees type %s is not supported' %
level_fees_type)
def _compute_fees(self, cr, uid, credit_line_ids, context=None):
"""Compute fees for `credit_line_ids` parameter
@@ -68,7 +71,8 @@ class FeesComputer(orm.BaseModel):
if not credit_line_ids:
return credit_line_ids
c_model = self.pool['credit.control.line']
credit_lines = c_model.browse(cr, uid, credit_line_ids, context=context)
credit_lines = c_model.browse(cr, uid, credit_line_ids,
context=context)
for credit in credit_lines:
# if there is no dependence between generated credit lines
# this could be threaded

View File

@@ -28,12 +28,12 @@ class credit_control_run(orm.Model):
def _generate_credit_lines(self, cr, uid, run_id, context=None):
"""Override method to add fees computation"""
credit_line_ids = super(credit_control_run, self)._generate_credit_lines(
cr,
uid,
run_id,
context=context
)
credit_line_ids = super(credit_control_run,
self)._generate_credit_lines(
cr,
uid,
run_id,
context=context)
fees_model = self.pool['credit.control.dunning.fees.computer']
fees_model._compute_fees(cr, uid, credit_line_ids, context=context)
return credit_line_ids

View File

@@ -51,7 +51,9 @@ class FixedFeesTester(test_common.TransactionCase):
self.usd_level.dunning_fixed_amount = 5.0
self.usd_level.dunning_currency_id = self.usd
self.usd_level.dunning_type = 'fixed'
self.dunning_model = self.registry('credit.control.dunning.fees.computer')
self.dunning_model = self.registry(
'credit.control.dunning.fees.computer'
)
def test_type_getter(self):
"""Test that correct compute function is returned for "fixed" type"""

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author Vincent Renaville/Joel Grand-Guillaume. Copyright 2012 Camptocamp SA
# Author Vincent Renaville/Joel Grand-Guillaume.
# Copyright 2012 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -26,11 +27,13 @@ class AccountInvoice(orm.Model):
def action_move_create(self, cr, uid, ids, context=None):
"""Set move line in draft state after creating them."""
res = super(AccountInvoice, self).action_move_create(cr, uid, ids, context=context)
res = super(AccountInvoice, self).action_move_create(cr, uid, ids,
context=context)
move_obj = self.pool.get('account.move')
for inv in self.browse(cr, uid, ids, context=context):
if inv.move_id:
move_obj.write(cr, uid, [inv.move_id.id], {'state': 'draft'}, context=context)
move_obj.write(cr, uid, [inv.move_id.id], {'state': 'draft'},
context=context)
return res
@@ -38,8 +41,11 @@ class AccountMove(orm.Model):
_inherit = 'account.move'
def button_cancel(self, cr, uid, ids, context=None):
""" We rewrite function button_cancel, to allow invoice or bank statement with linked draft moved
to be canceled """
""" We rewrite function button_cancel, to allow invoice or bank
statement with linked draft moved
to be canceled
"""
for line in self.browse(cr, uid, ids, context=context):
if line.state == 'draft':
continue
@@ -48,12 +54,11 @@ class AccountMove(orm.Model):
raise osv.except_osv(
_('Error!'),
_('You cannot modify a posted entry of this journal.'
'First you should set the journal to allow cancelling entries.')
'First you should set the journal '
'to allow cancelling entries.')
)
if ids:
cr.execute('UPDATE account_move '
'SET state=%s '
'WHERE id IN %s', ('draft', tuple(ids),))
return True
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -23,7 +23,8 @@ from openerp.osv import orm
class AccountBankStatement(orm.Model):
_inherit = "account.bank.statement"
def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id,
def create_move_from_st_line(self, cr, uid,
st_line_id, company_currency_id,
st_line_number, context=None):
move_ids = super(AccountBankStatement, self).create_move_from_st_line(
cr, uid, st_line_id, company_currency_id,
@@ -35,7 +36,8 @@ class AccountBankStatement(orm.Model):
voucher_obj = self.pool.get('account.voucher')
st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
if st_line.voucher_id:
v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
v = voucher_obj.browse(cr, uid, st_line.voucher_id.id,
context=context)
move_ids = v.move_id.id
if not isinstance(move_ids, (tuple, list)):

View File

@@ -32,17 +32,25 @@ Check that the Customer has a VAT number on invoice validation
==============================================================
This module adds an option **Customer must have VAT** on fiscal positions.
When a user tries to validate a customer invoice or refund with a fiscal position
When a user tries to validate a customer invoice or refund
with a fiscal position
that have this option, OpenERP will check that the customer has a VAT number.
If it doesn't, OpenERP will block the validation of the invoice and display an error message.
If it doesn't, OpenERP will block the validation of the invoice
and display an error message.
In the European Union (EU), when an EU company sends an invoice to another EU company in another country,
In the European Union (EU), when an EU company sends an invoice to
another EU company in another country,
it can invoice without VAT (most of the time) but the VAT number of the customer must be displayed on the invoice.
it can invoice without VAT (most of the time) but the VAT number of the
customer must be displayed on the invoice.
This module also displays a warning when a user sets a fiscal position with the option
**Customer must have VAT** on a customer and this customer doesn't have a VAT number in OpenERP yet.
This module also displays a warning when a user sets
a fiscal position with the option
**Customer must have VAT**
on a customer and this customer doesn't have a VAT number in OpenERP yet.
Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com>
for any help or question about this module.
@@ -57,7 +65,7 @@ Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com>
'images': [
'images/fiscal_position_form.jpg',
'images/vat_check_invoice_validation.jpg',
],
],
'installable': False,
'active': False,
'application': True,

View File

@@ -30,7 +30,8 @@ class account_fiscal_position(orm.Model):
_columns = {
'customer_must_have_vat': fields.boolean(
'Customer Must Have VAT number',
help="If enabled, OpenERP will check that the customer has a VAT number "
help="If enabled, OpenERP will check "
"that the customer has a VAT number "
"when the user validates a customer invoice/refund."
),
}
@@ -53,9 +54,11 @@ class account_invoice(orm.Model):
type_label = _('a Customer Refund')
raise orm.except_orm(
_('Missing VAT number:'),
_("You are trying to validate %s with the fiscal position '%s' "
_("You are trying to validate %s "
"with the fiscal position '%s' "
"that require the customer to have a VAT number. "
"But the Customer '%s' doesn't have a VAT number in OpenERP."
"But the Customer '%s' doesn't "
"have a VAT number in OpenERP."
"Please add the VAT number of this Customer in OpenERP "
" and try to validate again.")
% (type_label, invoice.fiscal_position.name,

View File

@@ -27,7 +27,8 @@ from openerp.tools.translate import _
class res_partner(orm.Model):
_inherit = 'res.partner'
def fiscal_position_change(self, cr, uid, ids, account_position, vat, customer):
def fiscal_position_change(self, cr, uid, ids, account_position,
vat, customer):
'''Warning is the fiscal position requires a vat number and the partner
doesn't have one yet'''
if account_position and customer and not vat:
@@ -40,7 +41,8 @@ class res_partner(orm.Model):
'message': _(
"You have set the fiscal position '%s' "
"that require the customer to have a VAT number. "
"You should add the VAT number of this customer in OpenERP."
"You should add the VAT number of this customer"
" in OpenERP."
) % fp['name']
}
}

View File

@@ -1,22 +1,21 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2004-2011 Zikzakmedia S.L. (http://zikzakmedia.com) All Rights Reserved.
# Copyright (C) 2004-2011 Zikzakmedia S.L. (http://zikzakmedia.com)
# All Rights Reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import invoice
from . import invoice

View File

@@ -1,23 +1,23 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP - Account invoice currency
# Copyright (C) 2004-2011 Zikzakmedia S.L. (http://zikzakmedia.com) All Rights Reserved.
# Jordi Esteve <jesteve@zikzakmedia.com>
# Copyright (c) 2013 Joaquin Gutierrez (http://www.gutierrezweb.es)
# OpenERP - Account invoice currency
# Copyright (C) 2004-2011 Zikzakmedia S.L. (http://zikzakmedia.com)
# Jordi Esteve <jesteve@zikzakmedia.com>
# Copyright (c) 2013 Joaquin Gutierrez (http://www.gutierrezweb.es)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
@@ -32,7 +32,8 @@
This Module adds functional fields to show invoice in the company currency
==========================================================================
Amount Untaxed, Amount Tax and Amount Total invoice fields in the company currency.
Amount Untaxed, Amount Tax and Amount Total invoice
fields in the company currency.
These fields are shown in "Other information" tab in invoice form.
""",
'license': "AGPL-3",
@@ -43,5 +44,3 @@ These fields are shown in "Other information" tab in invoice form.
'installable': False,
'active': False,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -1,22 +1,22 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2004-2011
# Pexego Sistemas Informáticos. (http://pexego.es) All Rights Reserved
# Zikzakmedia S.L. (http://zikzakmedia.com) All Rights Reserved.
# Copyright (C) 2004-2011
# Pexego Sistemas Informáticos. (http://pexego.es) All Rights Reserved
# Zikzakmedia S.L. (http://zikzakmedia.com) All Rights Reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
@@ -63,7 +63,8 @@ class account_invoice(orm.Model):
'cc_amount_total': 0.0,
}
# It could be computed only in open or paid invoices with a generated account move
# It could be computed only in open or paid invoices with a
# generated account move
if invoice.move_id:
# Accounts to compute amount_untaxed
line_account = []
@@ -74,20 +75,28 @@ class account_invoice(orm.Model):
# Accounts to compute amount_tax
tax_account = []
for line in invoice.tax_line:
if line.account_id.id not in tax_account and line.amount != 0:
if (line.account_id.id not in tax_account and
line.amount != 0):
tax_account.append(line.account_id.id)
# The company currency amounts are the debit-credit amounts in the account moves
# The company currency amounts are the debit-credit
# amounts in the account moves
for line in invoice.move_id.line_id:
if line.account_id.id in line_account:
res[invoice.id]['cc_amount_untaxed'] += line.debit - line.credit
amt = line.debit - line.credit
res[invoice.id]['cc_amount_untaxed'] += amt
if line.account_id.id in tax_account:
res[invoice.id]['cc_amount_tax'] += line.debit - line.credit
amt = line.debit - line.credit
res[invoice.id]['cc_amount_tax'] += amt
if invoice.type in ('out_invoice', 'in_refund'):
res[invoice.id]['cc_amount_untaxed'] = -res[invoice.id]['cc_amount_untaxed']
res[invoice.id]['cc_amount_tax'] = -res[invoice.id]['cc_amount_tax']
res[invoice.id]['cc_amount_total'] = (res[invoice.id]['cc_amount_tax'] +
res[invoice.id]['cc_amount_untaxed'])
res[invoice.id]['cc_amount_untaxed'] = \
-res[invoice.id]['cc_amount_untaxed']
res[invoice.id]['cc_amount_tax'] = \
-res[invoice.id]['cc_amount_tax']
res[invoice.id]['cc_amount_total'] = (
res[invoice.id]['cc_amount_tax'] +
res[invoice.id]['cc_amount_untaxed']
)
return res
_columns = {
@@ -97,15 +106,20 @@ class account_invoice(orm.Model):
digits_compute=dp.get_precision('Account'),
string='Company Cur. Untaxed',
help="Invoice untaxed amount in the company currency"
"(useful when invoice currency is different from company currency).",
"(useful when invoice currency is different "
"from company currency).",
store={
'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line',
'currency_id',
'move_id'], 20),
'account.invoice': (lambda self, cr, uid, ids, c={}: ids,
['invoice_line',
'currency_id',
'move_id'],
20),
'account.invoice.tax': (_get_invoice_tax2, None, 20),
'account.invoice.line': (_get_invoice_line2, ['price_unit',
'invoice_line_tax_id',
'quantity', 'discount'], 20),
'account.invoice.line': (_get_invoice_line2,
['price_unit',
'invoice_line_tax_id',
'quantity', 'discount'],
20),
},
multi='cc_all'
),
@@ -116,15 +130,20 @@ class account_invoice(orm.Model):
digits_compute=dp.get_precision('Account'),
string='Company Cur. Tax',
help="Invoice tax amount in the company currency "
"(useful when invoice currency is different from company currency).",
"(useful when invoice currency is different "
"from company currency).",
store={
'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line',
'currency_id',
'move_id'], 20),
'account.invoice': (lambda self, cr, uid, ids, c={}: ids,
['invoice_line',
'currency_id',
'move_id'],
20),
'account.invoice.tax': (_get_invoice_tax2, None, 20),
'account.invoice.line': (_get_invoice_line2, ['price_unit',
'invoice_line_tax_id',
'quantity', 'discount'], 20),
'account.invoice.line': (_get_invoice_line2,
['price_unit',
'invoice_line_tax_id',
'quantity', 'discount'],
20),
},
multi='cc_all'
),
@@ -135,15 +154,20 @@ class account_invoice(orm.Model):
digits_compute=dp.get_precision('Account'),
string='Company Cur. Total',
help="Invoice total amount in the company currency "
"(useful when invoice currency is different from company currency).",
"(useful when invoice currency is different from "
"company currency).",
store={
'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line',
'currency_id',
'move_id'], 20),
'account.invoice': (lambda self, cr, uid, ids, c={}: ids,
['invoice_line',
'currency_id',
'move_id'],
20),
'account.invoice.tax': (_get_invoice_tax2, None, 20),
'account.invoice.line': (_get_invoice_line2, ['price_unit',
'invoice_line_tax_id',
'quantity', 'discount'], 20),
'account.invoice.line': (_get_invoice_line2,
['price_unit',
'invoice_line_tax_id',
'quantity', 'discount'],
20),
},
multi='cc_all'),
}

View File

@@ -39,9 +39,11 @@ This module:
* prevent users from deactivating the 'Check Date in Period' option.
So this module is an additionnal security for countries where, on an account move, the date must be inside the period.
So this module is an additionnal security for countries where, on an account
move, the date must be inside the period.
Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com> for any help or question about this module.
Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com>
for any help or question about this module.
""",
'author': 'Akretion',
'website': 'http://www.akretion.com',

View File

@@ -32,8 +32,7 @@ This module leaves the search view extension for move lines intact, but
disables the default search values for the dropdowns so that you do not
have to disable these before entering your own search queries.
.. image:: /account_move_line_no_default_search/static/src/img/move_line_search_view.png
..image:: /account_move_line_no_default_search/static/src/img/sample.png
""",
'website': 'http://therp.nl',
'depends': ['account'],

View File

@@ -12,11 +12,11 @@
<menuitem
action="action_account_move_line_search_extension"
icon="STOCK_JUSTIFY_FILL"
id="menu_account_move_line_search_extension"
id="account.menu_action_account_moves_all"
parent="account.menu_finance_entries"
sequence="1"
groups="account.group_account_user"
/>
</data>
</openerp>

View File

@@ -27,9 +27,15 @@
Templates for Journal Entries
User can configure journal entries templates, useful for recurring entries.
The amount of each template line can be computed (through python code) or kept as user input.
If user input, when using the template, user has to fill the amount of every input lines.
The journal entry form allows lo load, through a wizard, the template to use and the amounts to fill.
The amount of each template line can be computed (through python code)
or kept as user input.
If user input, when using the template, user has to fill
the amount of every input lines.
The journal entry form allows lo load, through a wizard,
the template to use and the amounts to fill.
""",
'author': 'Agile Business Group',

View File

@@ -51,21 +51,27 @@ class account_document_template(orm.Model):
def _generate_empty_lines(self, cr, uid, template_id):
lines = {}
for template_line in self.browse(cr, uid, template_id).template_line_ids:
t_lines = self.browse(cr, uid, template_id).template_line_ids
for template_line in t_lines:
lines[template_line.sequence] = None
return lines
def lines(self, line_number):
if self._computed_lines[line_number] is not None:
return self._computed_lines[line_number]
line = self._get_template_line(self._cr, self._uid, self._current_template_id, line_number)
line = self._get_template_line(self._cr,
self._uid,
self._current_template_id,
line_number)
if re.match('L\( *' + str(line_number) + ' *\)', line.python_code):
raise orm.except_orm(
_('Error'),
_('Line %s can\'t refer to itself') % str(line_number)
)
try:
self._computed_lines[line_number] = eval(line.python_code.replace('L', 'self.lines'))
self._computed_lines[line_number] = eval(
line.python_code.replace('L', 'self.lines')
)
except KeyError:
raise orm.except_orm(
_('Error'),
@@ -74,12 +80,14 @@ class account_document_template(orm.Model):
def compute_lines(self, cr, uid, template_id, input_lines):
# input_lines: dictionary in the form {line_number: line_amount}
# returns all the lines (included input lines) in the form {line_number: line_amount}
# returns all the lines (included input lines)
# in the form {line_number: line_amount}
template = self.browse(cr, uid, template_id)
if len(input_lines) != self._input_lines(cr, uid, template):
raise orm.except_orm(
_('Error'),
_('Inconsistency between input lines and filled lines for template %s') % template.name
_('Inconsistency between input lines and '
'filled lines for template %s') % template.name
)
self._current_template_id = template.id
self._cr = cr
@@ -106,6 +114,10 @@ class account_document_template_line(orm.Model):
_columns = {
'name': fields.char('Name', size=64, required=True),
'sequence': fields.integer('Sequence', required=True),
'type': fields.selection([('computed', 'Computed'), ('input', 'User input')], 'Type', required=True),
'type': fields.selection(
[('computed', 'Computed'), ('input', 'User input')],
'Type',
required=True
),
'python_code': fields.text('Python Code'),
}

View File

@@ -28,23 +28,40 @@ class account_move_template(orm.Model):
_name = 'account.move.template'
_columns = {
'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True),
'template_line_ids': fields.one2many('account.move.template.line', 'template_id', 'Template Lines'),
'company_id': fields.many2one(
'res.company',
'Company',
required=True,
change_default=True
),
'template_line_ids': fields.one2many(
'account.move.template.line',
'template_id',
'Template Lines'
),
'cross_journals': fields.boolean('Cross-Journals'),
'transitory_acc_id': fields.many2one('account.account', 'Transitory account', required=False),
}
_defaults = {
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(
cr, uid, 'account.move.template', context=c
'transitory_acc_id': fields.many2one(
'account.account',
'Transitory account',
required=False
),
}
def _get_default(self, cr, uid, context=None):
self.pool.get('res.company')._company_default_get(
cr, uid, 'account.move.template', context=context
)
_defaults = {
'company_id': _get_default
}
def _check_different_journal(self, cr, uid, ids, context=None):
# Check that the journal on these lines are different/same in the case of cross journals/single journal
# Check that the journal on these lines are different/same in the case
# of cross journals/single journal
journal_ids = []
all_journal_ids = []
move_template = self.pool.get('account.move.template').browse(cr, uid, ids)[0]
move_template = self.pool.get('account.move.template').browse(
cr, uid, ids)[0]
if not move_template.template_line_ids:
return True
for template_line in move_template.template_line_ids:
@@ -59,7 +76,8 @@ class account_move_template(orm.Model):
_constraints = [
(_check_different_journal,
'If the template is "cross-journals", the Journals must be different,'
'if the template does not "cross-journals" the Journals must be the same!',
'if the template does not "cross-journals" '
'the Journals must be the same!',
['journal_id'])
]
@@ -69,16 +87,28 @@ class account_move_template_line(orm.Model):
_inherit = 'account.document.template.line'
_columns = {
'journal_id': fields.many2one('account.journal', 'Journal', required=True),
'account_id': fields.many2one('account.account', 'Account',
required=True, ondelete="cascade"),
'journal_id': fields.many2one(
'account.journal',
'Journal',
required=True
),
'account_id': fields.many2one(
'account.account',
'Account',
required=True,
ondelete="cascade"
),
'move_line_type': fields.selection(
[('cr', 'Credit'),
('dr', 'Debit')],
'Move Line Type',
required=True
),
'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete="cascade"),
'analytic_account_id': fields.many2one(
'account.analytic.account',
'Analytic Account',
ondelete="cascade"
),
'template_id': fields.many2one('account.move.template', 'Template'),
'account_tax_id': fields.many2one('account.tax', 'Tax'),
}

View File

@@ -86,7 +86,8 @@ class wizard_select_template(orm.TransientModel):
return self.load_template(cr, uid, ids)
wizard.write({'state': 'template_selected'})
view_rec = model_data_obj.get_object_reference(cr, uid, 'account_move_template', 'wizard_select_template')
view_rec = model_data_obj.get_object_reference(
cr, uid, 'account_move_template', 'wizard_select_template')
view_id = view_rec and view_rec[1] or False
return {
@@ -106,7 +107,10 @@ class wizard_select_template(orm.TransientModel):
wizard = self.browse(cr, uid, ids, context=context)[0]
if not template_obj.check_zero_lines(cr, uid, wizard):
raise orm.except_orm(_('Error !'), _('At least one amount has to be non-zero!'))
raise orm.except_orm(
_('Error !'),
_('At least one amount has to be non-zero!')
)
input_lines = {}
for template_line in wizard.line_ids:
@@ -114,10 +118,14 @@ class wizard_select_template(orm.TransientModel):
period_id = account_period_obj.find(cr, uid, context=context)
if not period_id:
raise orm.except_orm(_('No period found !'), _('Unable to find a valid period !'))
raise orm.except_orm(
_('No period found !'),
_('Unable to find a valid period !')
)
period_id = period_id[0]
computed_lines = template_obj.compute_lines(cr, uid, wizard.template_id.id, input_lines)
computed_lines = template_obj.compute_lines(
cr, uid, wizard.template_id.id, input_lines)
moves = {}
for line in wizard.template_id.template_line_ids:
@@ -171,14 +179,16 @@ class wizard_select_template(orm.TransientModel):
})
return move_id
def _make_move_line(self, cr, uid, line, computed_lines, move_id, period_id, partner_id):
def _make_move_line(self, cr, uid, line, computed_lines,
move_id, period_id, partner_id):
account_move_line_obj = self.pool.get('account.move.line')
analytic_account_id = False
if line.analytic_account_id:
if not line.journal_id.analytic_journal_id:
raise orm.except_orm(
_('No Analytic Journal !'),
_("You have to dfine an analytic journal on the '%s' journal!")
_("You have to define an analytic "
"journal on the '%s' journal!")
% (line.journal_id.name,)
)
@@ -212,7 +222,8 @@ class wizard_select_template(orm.TransientModel):
if not line.journal_id.analytic_journal_id:
raise orm.except_orm(
_('No Analytic Journal !'),
_("You have to define an analytic journal on the '%s' journal!")
_("You have to define an analytic journal "
"on the '%s' journal!")
% (line.template_id.journal_id.name,)
)
analytic_account_id = line.analytic_account_id.id
@@ -238,10 +249,16 @@ class wizard_select_template_line(orm.TransientModel):
_description = 'Template Lines'
_name = "wizard.select.move.template.line"
_columns = {
'template_id': fields.many2one('wizard.select.move.template', 'Template'),
'template_id': fields.many2one('wizard.select.move.template',
'Template'),
'sequence': fields.integer('Number', required=True),
'name': fields.char('Name', size=64, required=True, readonly=True),
'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True),
'account_id': fields.many2one(
'account.account',
'Account',
required=True,
readonly=True
),
'move_line_type': fields.selection(
[('cr', 'Credit'),
('dr', 'Debit')],

View File

@@ -28,7 +28,7 @@ but extending it to multiple periods and multiple journals. It replaces the
base one defined in addons/account/wizard.
""",
'website': 'http://www.camptocamp.com',
'data' : ['wizard/account_validate_move_view.xml'],
'data': ['wizard/account_validate_move_view.xml'],
'installable': False,
'active': False,
}

View File

@@ -54,12 +54,16 @@ class test_account_partner_required(common.TransactionCase):
'account_id': self.ref('account.a_sale')})
move_line_id = self.move_line_obj.create(
self.cr, self.uid,
{'move_id': move_id,
'name': '/',
'debit': amount,
'credit': 0,
'account_id': self.ref('account.a_recv'),
'partner_id': self.ref('base.res_partner_1') if with_partner else False})
{
'move_id': move_id,
'name': '/',
'debit': amount,
'credit': 0,
'account_id': self.ref('account.a_recv'),
'partner_id': self.ref('base.res_partner_1') if
with_partner else False
}
)
return move_line_id
def _set_partner_policy(self, policy, aref='account.a_recv'):
@@ -116,6 +120,12 @@ class test_account_partner_required(common.TransactionCase):
self.move_line_obj.write(self.cr, self.uid, line_id,
{'account_id': self.ref('account.a_pay')})
# change account to a_pay with policy always with partner -> ok
self.move_line_obj.write(self.cr, self.uid, line_id,
{'account_id': self.ref('account.a_pay'),
'partner_id': self.ref('base.res_partner_1')})
self.move_line_obj.write(
self.cr,
self.uid,
line_id,
{
'account_id': self.ref('account.a_pay'),
'partner_id': self.ref('base.res_partner_1')
}
)

View File

@@ -3,7 +3,8 @@
#
# OpenERP - Account renumber wizard
# Copyright (C) 2009 Pexego Sistemas Informáticos. All Rights Reserved
# Copyright (c) 2013 Servicios Tecnológicos Avanzados (http://www.serviciosbaeza.com)
# Copyright (c) 2013 Servicios Tecnológicos Avanzados
# (http://www.serviciosbaeza.com)
# Pedro Manuel Baeza <pedro.baeza@serviciosbaeza.com>
# Copyright (c) 2013 Joaquin Gutierrez (http://www.gutierrezweb.es)
# $Id$
@@ -31,17 +32,19 @@
'category': "Enterprise Specific Modules",
'contributors': ['Pedro M. Baeza', 'Joaquín Gutierrez'],
'description': """
This module adds a wizard to renumber account moves by date only for admin users.
=================================================================================
This module adds a wizard to renumber account moves by date only for admin.
===========================================================================
The wizard, that will be added to the "End of Year Treatments",
let's you select one or more journals and fiscal periods,
set a starting number; and then renumber all the posted moves
from those journals and periods sorted by date.
It will recreate the sequence number of each account move using their journal sequence so:
It will recreate the sequence number of each account move
using their journal sequence so:
- Sequences per journal are supported.
- Sequences with prefixes and sufixes based on the move date are also supported.
- Sequences with prefixes and sufixes based on the move
date are also supported.
""",
"license": "AGPL-3",
"depends": [

View File

@@ -89,7 +89,9 @@ def create_lots_of_account_moves(dbname, user, passwd, howmany):
'name': 'Test_l2'})
],
'period_id': 1,
'date': '2009-01-%s' % ((i % 31) or 1),
'date': '2009-01-%s' % (
(i % 31) or 1
),
'partner_id': False,
'to_check': 0
},
@@ -97,7 +99,8 @@ def create_lots_of_account_moves(dbname, user, passwd, howmany):
# Validate the move
object_facade.execute(dbname, user_id, passwd,
u'account.move', 'button_validate', [move_id], {})
u'account.move', 'button_validate',
[move_id], {})
# ------------------------------------------------------------------------
# ------------------------------------------------------------------------
@@ -105,7 +108,8 @@ def create_lots_of_account_moves(dbname, user, passwd, howmany):
if __name__ == "__main__":
if len(sys.argv) < 5:
logger.info(u"Usage: %s <dbname> <user> <password> <howmany>" % sys.argv[0])
logger.info(u"Usage: %s <dbname> <user> <password> <howmany>" %
sys.argv[0])
else:
create_lots_of_account_moves(
sys.argv[1], sys.argv[2], sys.argv[3], int(sys.argv[4]))

View File

@@ -42,7 +42,8 @@ class wizard_renumber(orm.TransientModel):
help='Fiscal periods to renumber',
string="Periods", ondelete='null'),
'number_next': fields.integer('First Number', required=True,
help="Journal sequences will start counting on this number"),
help="Journal sequences will start "
"counting on this number"),
'state': fields.selection([('init', 'Initial'),
('renumber', 'Renumbering')], readonly=True)
}
@@ -112,9 +113,11 @@ class wizard_renumber(orm.TransientModel):
sequences_seen.append(sequence_id)
# Generate (using our own get_id) and write the new move number
c = {'fiscalyear_id': move.period_id.fiscalyear_id.id}
new_name = sequence_obj.next_by_id(cr, uid,
move.journal_id.sequence_id.id,
context=c)
new_name = sequence_obj.next_by_id(
cr, uid,
move.journal_id.sequence_id.id,
context=c
)
# Note: We can't just do a
# "move_obj.write(cr, uid, [move.id], {'name': new_name})"
# cause it might raise a

View File

@@ -48,7 +48,7 @@ during the Akretion-Camptocamp code sprint of June 2011.
'data': [
'account_view.xml',
'wizard/account_move_reverse_view.xml'
],
],
'installable': False,
'active': False,
}

View File

@@ -26,9 +26,11 @@ class account_tax_declaration_analysis(orm.TransientModel):
_description = 'Account Vat Declaration'
_columns = {
'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscalyear',
help='Fiscalyear to look on', required=True),
help='Fiscalyear to look on',
required=True),
'period_list': fields.many2many('account.period', 'account_tax_period_rel',
'period_list': fields.many2many('account.period',
'account_tax_period_rel',
'tax_analysis', 'period_id',
'Period _list', required=True),
}

View File

@@ -171,51 +171,81 @@ class UpdateTaxConfig(orm.Model):
cp_ref_tax_code_id = False
if config.duplicate_tax_code:
if line.source_tax_id.base_code_id:
cp_base_code_id = tax_code_pool.copy(cr, uid,
line.source_tax_id.base_code_id.id)
rename_old = '[%s] %s' % (config.name,
line.source_tax_id.base_code_id.name)
cp_base_code_id = tax_code_pool.copy(
cr, uid,
line.source_tax_id.base_code_id.id
)
rename_old = '[%s] %s' % (
config.name,
line.source_tax_id.base_code_id.name
)
tax_code_pool.write(cr, uid,
line.source_tax_id.base_code_id.id,
{'name': rename_old})
if line.source_tax_id.tax_code_id:
cp_tax_code_id = tax_code_pool.copy(cr, uid,
line.source_tax_id.tax_code_id.id)
rename_old = '[%s] %s' % (config.name,
line.source_tax_id.tax_code_id.name)
cp_tax_code_id = tax_code_pool.copy(
cr, uid,
line.source_tax_id.tax_code_id.id
)
rename_old = '[%s] %s' % (
config.name,
line.source_tax_id.tax_code_id.name
)
tax_code_pool.write(cr, uid,
line.source_tax_id.tax_code_id.id,
{'name': rename_old})
if line.source_tax_id.ref_base_code_id:
# Check if with have the same tax code for base_code_id
if line.source_tax_id.ref_base_code_id.id == line.source_tax_id.base_code_id.id:
if (line.source_tax_id.ref_base_code_id.id ==
line.source_tax_id.base_code_id.id):
cp_ref_base_code_id = cp_base_code_id
else:
cp_ref_base_code_id = tax_code_pool.copy(cr, uid,
line.source_tax_id.ref_base_code_id.id)
rename_old = '[%s] %s' % (config.name,
line.source_tax_id.ref_base_code_id.name)
tax_code_pool.write(cr, uid,
line.source_tax_id.ref_base_code_id.id,
{'name': rename_old})
cp_ref_base_code_id = tax_code_pool.copy(
cr, uid,
line.source_tax_id.ref_base_code_id.id
)
rename_old = '[%s] %s' % (
config.name,
line.source_tax_id.ref_base_code_id.name
)
tax_code_pool.write(
cr, uid,
line.source_tax_id.ref_base_code_id.id,
{'name': rename_old}
)
if line.source_tax_id.ref_tax_code_id:
if line.source_tax_id.ref_tax_code_id.id == line.source_tax_id.tax_code_id.id:
if (line.source_tax_id.ref_tax_code_id.id ==
line.source_tax_id.tax_code_id.id):
cp_ref_tax_code_id = cp_tax_code_id
else:
cp_ref_tax_code_id = tax_code_pool.copy(cr, uid,
line.source_tax_id.ref_tax_code_id.id)
rename_old = '[%s] %s' % (config.name,
line.source_tax_id.ref_tax_code_id.name)
tax_code_pool.write(cr, uid,
line.source_tax_id.ref_tax_code_id.id,
{'name': rename_old})
cp_ref_tax_code_id = tax_code_pool.copy(
cr, uid,
line.source_tax_id.ref_tax_code_id.id
)
rename_old = '[%s] %s' % (
config.name,
line.source_tax_id.ref_tax_code_id.name
)
tax_code_pool.write(
cr, uid,
line.source_tax_id.ref_tax_code_id.id,
{'name': rename_old}
)
else:
cp_base_code_id = line.source_tax_id.base_code_id and line.source_tax_id.base_code_id.id or False
cp_ref_base_code_id = (line.source_tax_id.ref_base_code_id and
line.source_tax_id.ref_base_code_id.id or False)
cp_tax_code_id = line.source_tax_id.tax_code_id and line.source_tax_id.tax_code_id.id or False
cp_base_code_id = (line.source_tax_id.base_code_id and
line.source_tax_id.base_code_id.id or
False)
cp_ref_base_code_id = (
line.source_tax_id.ref_base_code_id and
line.source_tax_id.ref_base_code_id.id or
False
)
cp_tax_code_id = (line.source_tax_id.tax_code_id and
line.source_tax_id.tax_code_id.id or
False)
cp_ref_tax_code_id = (line.source_tax_id.ref_tax_code_id and
line.source_tax_id.ref_tax_code_id.id or False)
line.source_tax_id.ref_tax_code_id.id or
False)
target_tax_id = tax_pool.copy(
cr, uid, line.source_tax_id.id,
@@ -352,7 +382,8 @@ class UpdateTaxConfig(orm.Model):
if model:
for field_name, column in model._columns.items():
log += update_defaults(model_name, field_name, column)
for field_name, field_tuple in model._inherit_fields.iteritems():
for field_name, field_tuple in \
model._inherit_fields.iteritems():
if len(field_tuple) >= 3:
column = field_tuple[2]
log += update_defaults(model_name, field_name, column)

View File

@@ -42,7 +42,8 @@ not support all CSV canvas.
- AA lines will only be created when moves are posted.
- Tax lines computation will be skipped until the move are posted.
This option should be used with caution and preferably in conjunction with provided canvas in tests/data
This option should be used with caution and preferably in conjunction with
provided canvas in tests/data
Then simply press import file button. The process will be run in background
and you will be able to continue your work.

View File

@@ -126,7 +126,8 @@ class account_move_line(orm.Model):
context = {}
if context.get('async_bypass_create'):
return self._bypass_create(cr, uid, vals, context=context)
return super(account_move_line, self).create(cr, uid, vals, context=context)
return super(account_move_line, self).create(cr, uid, vals,
context=context)
def _bypass_create(self, cr, uid, vals, context=None):
"""Create entries using cursor directly

View File

@@ -53,7 +53,8 @@ class move_line_importer(orm.Model):
if default is None:
default = {}
default.update(state='draft', report=False)
return super(move_line_importer, self).copy(cr, uid, id, default=default,
return super(move_line_importer, self).copy(cr, uid, id,
default=default,
context=context)
def track_success(sef, cr, uid, obj, context=None):
@@ -71,39 +72,58 @@ class move_line_importer(orm.Model):
},
}
_columns = {'name': fields.datetime('Name',
required=True,
readonly=True),
'state': fields.selection([('draft', 'New'),
('running', 'Running'),
('done', 'Success'),
('error', 'Error')],
readonly=True,
string='Status'),
'report': fields.text('Report',
readonly=True),
'file': fields.binary('File',
required=True),
'delimiter': fields.selection([(',', ','), (';', ';'), ('|', '|')],
string="CSV delimiter",
required=True),
'company_id': fields.many2one('res.company',
'Company'),
'bypass_orm': fields.boolean('Fast import (use with caution)',
help="When enabled import will be faster but"
" it will not use orm and may"
" not support all CSV canvas. \n"
"Entry posted option will be skipped. \n"
"AA lines will only be created when"
" moves are posted. \n"
"Tax lines computation will be skipped. \n"
"This option should be used with caution"
" and in conjonction with provided canvas."),
}
_columns = {
'name': fields.datetime(
'Name',
required=True,
readonly=True
),
'state': fields.selection(
[('draft', 'New'),
('running', 'Running'),
('done', 'Success'),
('error', 'Error')],
readonly=True,
string='Status'
),
'report': fields.text(
'Report',
readonly=True
),
'file': fields.binary(
'File',
required=True
),
'delimiter': fields.selection(
[(',', ','), (';', ';'), ('|', '|')],
string="CSV delimiter",
required=True
),
'company_id': fields.many2one(
'res.company',
'Company'
),
'bypass_orm': fields.boolean(
'Fast import (use with caution)',
help="When enabled import will be faster but"
" it will not use orm and may"
" not support all CSV canvas. \n"
"Entry posted option will be skipped. \n"
"AA lines will only be created when"
" moves are posted. \n"
"Tax lines computation will be skipped. \n"
"This option should be used with caution"
" and in conjonction with provided canvas."
),
}
def _get_current_company(self, cr, uid, context=None, model="move.line.importer"):
return self.pool.get('res.company')._company_default_get(cr, uid, model,
context=context)
def _get_current_company(self, cr, uid, context=None,
model="move.line.importer"):
return self.pool.get('res.company')._company_default_get(
cr, uid,
model,
context=context
)
_defaults = {'state': 'draft',
'name': fields.datetime.now(),
@@ -143,9 +163,11 @@ class move_line_importer(orm.Model):
try:
data = csv.reader(csv_file, delimiter=str(delimiter))
except csv.Error as error:
raise orm.except_orm(_('CSV file is malformed'),
_("Maybe you have not choose correct separator \n"
"the error detail is : \n %s") % repr(error))
raise orm.except_orm(
_('CSV file is malformed'),
_("Maybe you have not choose correct separator \n"
"the error detail is : \n %s") % repr(error)
)
head = data.next()
head = [x.replace(' ', '') for x in head]
# Generator does not work with orm.BaseModel.load
@@ -162,13 +184,16 @@ class move_line_importer(orm.Model):
res = []
for msg in messages:
rows = msg.get('rows', {})
res.append(_("%s. -- Field: %s -- rows %s to %s") % (msg.get('message', 'N/A'),
msg.get('field', 'N/A'),
rows.get('from', 'N/A'),
rows.get('to', 'N/A')))
res.append(_("%s. -- Field: %s -- rows %s to %s") % (
msg.get('message', 'N/A'),
msg.get('field', 'N/A'),
rows.get('from', 'N/A'),
rows.get('to', 'N/A'))
)
return "\n \n".join(res)
def _manage_load_results(self, cr, uid, imp_id, result, _do_commit=True, context=None):
def _manage_load_results(self, cr, uid, imp_id, result, _do_commit=True,
context=None):
"""Manage the BaseModel.load function output and store exception.
Will generate success/failure report and store it into report field.
@@ -176,8 +201,10 @@ class move_line_importer(orm.Model):
Savepoints.
:param imp_id: current importer id
:param result: BaseModel.laod return {ids: list(int)|False, messages: [Message]}
:param _do_commit: toggle commit management only used for testing purpose only
:param result: BaseModel.load returns
{ids: list(int)|False, messages: [Message]}
:param _do_commit: toggle commit management only used
for testing purpose only
:returns: current importer id
"""
@@ -216,15 +243,23 @@ class move_line_importer(orm.Model):
local_cr.commit()
# We handle concurrent error troubles
except psycopg2.OperationalError as pg_exc:
_logger.error('Can not write report. System will retry %s time(s)' % max_tries)
if pg_exc.pg_code in orm.PG_CONCURRENCY_ERRORS_TO_RETRY and max_tries >= 0:
_logger.error(
"Can not write report. "
"System will retry %s time(s)" % max_tries
)
if (pg_exc.pg_code in orm.PG_CONCURRENCY_ERRORS_TO_RETRY and
max_tries >= 0):
local_cr.rollback()
local_cr.close()
remaining_try = max_tries - 1
self._write_report(cr, uid, imp_id, cr, _do_commit=_do_commit,
max_tries=remaining_try, context=context)
self._write_report(cr, uid, imp_id, cr,
_do_commit=_do_commit,
max_tries=remaining_try,
context=context)
else:
_logger.exception('Can not log report - Operational update error')
_logger.exception(
'Can not log report - Operational update error'
)
raise
except Exception:
_logger.exception('Can not log report')
@@ -234,10 +269,13 @@ class move_line_importer(orm.Model):
if not local_cr.closed:
local_cr.close()
else:
self.write(cr, uid, [imp_id], {'state': state, 'report': msg}, context=context)
self.write(cr, uid, [imp_id],
{'state': state, 'report': msg},
context=context)
return imp_id
def _load_data(self, cr, uid, imp_id, head, data, _do_commit=True, context=None):
def _load_data(self, cr, uid, imp_id, head, data, _do_commit=True,
context=None):
"""Function that does the load of parsed CSV file.
If will log exception and susccess into the report fields.
@@ -245,13 +283,15 @@ class move_line_importer(orm.Model):
:param imp_id: current importer id
:param head: CSV file head (list of header)
:param data: CSV file content (list of data list)
:param _do_commit: toggle commit management only used for testing purpose only
:param _do_commit: toggle commit management
only used for testing purpose only
:returns: current importer id
"""
state = msg = None
try:
res = self.pool['account.move'].load(cr, uid, head, data, context=context)
res = self.pool['account.move'].load(cr, uid, head, data,
context=context)
r_id, state, msg = self._manage_load_results(cr, uid, imp_id, res,
_do_commit=_do_commit,
context=context)
@@ -285,8 +325,10 @@ class move_line_importer(orm.Model):
"""
for th in threading.enumerate():
if th.getName() == 'async_move_line_import_%s' % imp_id:
raise orm.except_orm(_('An import of this file is already running'),
_('Please try latter'))
raise orm.except_orm(
_('An import of this file is already running'),
_('Please try latter')
)
def _check_permissions(self, cr, uid, context=None):
"""Ensure that user is allowed to create move / move line"""
@@ -295,7 +337,8 @@ class move_line_importer(orm.Model):
move_obj.check_access_rule(cr, uid, [], 'create')
move_obj.check_access_rights(cr, uid, 'create', raise_exception=True)
move_line_obj.check_access_rule(cr, uid, [], 'create')
move_line_obj.check_access_rights(cr, uid, 'create', raise_exception=True)
move_line_obj.check_access_rights(cr, uid, 'create',
raise_exception=True)
def import_file(self, cr, uid, imp_id, context=None):
""" Will do an asynchronous load of a CSV file.

View File

@@ -48,22 +48,29 @@ class TestMoveLineImporter(test_common.SingleTransactionCase):
def test_01_one_line_without_orm_bypass(self):
"""Test one line import without bypassing orm"""
cr, uid = self.cr, self.uid
importer_id = self.importer_model.create(cr, uid,
{'file': self.get_file('one_move.csv'),
'delimiter': ';'})
importer_id = self.importer_model.create(
cr, uid,
{'file': self.get_file('one_move.csv'),
'delimiter': ';'}
)
importer = self.importer_model.browse(cr, uid, importer_id)
self.assertTrue(importer.company_id, 'Not default company set')
self.assertFalse(importer.bypass_orm, 'Bypass orm must not be active')
self.assertEqual(importer.state, 'draft')
head, data = self.importer_model._parse_csv(cr, uid, importer.id)
self.importer_model._load_data(cr, uid, importer.id, head, data, _do_commit=False, context={})
self.importer_model._load_data(
cr, uid, importer.id, head, data, _do_commit=False, context={})
importer = self.importer_model.browse(cr, uid, importer_id)
self.assertEquals(importer.state, 'done',
'Exception %s during import' % importer.report)
created_move_ids = self.move_model.search(cr, uid, [('ref', '=', 'éöüàè_test_1')])
created_move_ids = self.move_model.search(
cr, uid,
[('ref', '=', 'éöüàè_test_1')]
)
self.assertTrue(created_move_ids, 'No move imported')
created_move = self.move_model.browse(cr, uid, created_move_ids[0])
self.assertTrue(len(created_move.line_id) == 3, 'Wrong number of move line imported')
self.assertTrue(len(created_move.line_id) == 3,
'Wrong number of move line imported')
debit = credit = 0.0
for line in created_move.line_id:
debit += line.debit if line.debit else 0.0
@@ -75,10 +82,12 @@ class TestMoveLineImporter(test_common.SingleTransactionCase):
def test_02_one_line_using_orm_bypass(self):
"""Test one line import using orm bypass"""
cr, uid = self.cr, self.uid
importer_id = self.importer_model.create(cr, uid,
{'file': self.get_file('one_move2.csv'),
'delimiter': ';',
'bypass_orm': True})
importer_id = self.importer_model.create(
cr, uid,
{'file': self.get_file('one_move2.csv'),
'delimiter': ';',
'bypass_orm': True}
)
importer = self.importer_model.browse(cr, uid, importer_id)
self.assertTrue(importer.company_id, 'Not default company set')
self.assertTrue(importer.bypass_orm, 'Bypass orm must be active')
@@ -91,10 +100,12 @@ class TestMoveLineImporter(test_common.SingleTransactionCase):
importer = self.importer_model.browse(cr, uid, importer_id)
self.assertEquals(importer.state, 'done',
'Exception %s during import' % importer.report)
created_move_ids = self.move_model.search(cr, uid, [('ref', '=', 'test_2')])
created_move_ids = self.move_model.search(cr, uid,
[('ref', '=', 'test_2')])
self.assertTrue(created_move_ids, 'No move imported')
created_move = self.move_model.browse(cr, uid, created_move_ids[0])
self.assertTrue(len(created_move.line_id) == 3, 'Wrong number of move line imported')
self.assertTrue(len(created_move.line_id) == 3,
'Wrong number of move line imported')
debit = credit = 0.0
for line in created_move.line_id:
debit += line.debit if line.debit else 0.0
@@ -106,17 +117,22 @@ class TestMoveLineImporter(test_common.SingleTransactionCase):
def test_03_one_line_failing(self):
"""Test one line import with faulty CSV file"""
cr, uid = self.cr, self.uid
importer_id = self.importer_model.create(cr, uid,
{'file': self.get_file('faulty_moves.csv'),
'delimiter': ';'})
importer_id = self.importer_model.create(
cr, uid,
{'file': self.get_file('faulty_moves.csv'),
'delimiter': ';'}
)
importer = self.importer_model.browse(cr, uid, importer_id)
self.assertTrue(importer.company_id, 'Not default company set')
self.assertFalse(importer.bypass_orm, 'Bypass orm must not be active')
self.assertEqual(importer.state, 'draft')
head, data = self.importer_model._parse_csv(cr, uid, importer.id)
self.importer_model._load_data(cr, uid, importer.id, head, data, _do_commit=False, context={})
self.importer_model._load_data(cr, uid, importer.id, head, data,
_do_commit=False, context={})
importer = self.importer_model.browse(cr, uid, importer_id)
self.assertEquals(importer.state, 'error',
'No exception %s during import' % importer.report)
created_move_ids = self.move_model.search(cr, uid, [('ref', '=', 'test_3')])
self.assertFalse(created_move_ids, 'Move was imported but it should not be the case')
created_move_ids = self.move_model.search(cr, uid,
[('ref', '=', 'test_3')])
self.assertFalse(created_move_ids,
'Move was imported but it should not be the case')

View File

@@ -32,10 +32,12 @@ Currency Rate Date Check
========================
This module adds a check on dates when doing currency conversion in OpenERP.
It checks that the currency rate used to make the conversion is not more than N days away
It checks that the currency rate used to make the conversion
is not more than N days away
from the date of the amount to convert.
The maximum number of days of the interval can be configured on the company form.
The maximum number of days of the interval can be
configured on the company form.
Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com>
for any help or question about this module.
@@ -47,7 +49,7 @@ for any help or question about this module.
'images': [
'images/date_check_error_popup.jpg',
'images/date_check_company_config.jpg',
],
],
'installable': False,
'active': False,
}

View File

@@ -29,8 +29,10 @@ class res_company(orm.Model):
_columns = {
'currency_rate_max_delta': fields.integer(
'Max Time Delta in Days for Currency Rates',
help="This is the maximum interval in days between the date associated "
"with the amount to convert and the date of the nearest currency "
help="This is the maximum interval in days between "
"the date associated "
"with the amount to convert and the date "
"of the nearest currency "
"rate available in OpenERP."),
}
@@ -41,5 +43,6 @@ class res_company(orm.Model):
_sql_constraints = [
('currency_rate_max_delta_positive',
'CHECK (currency_rate_max_delta >= 0)',
"The value of the field 'Max Time Delta in Days for Currency Rates' must be positive or 0."),
"The value of the field 'Max Time Delta in Days for Currency Rates' "
"must be positive or 0."),
]

View File

@@ -26,27 +26,36 @@ from openerp.tools.translate import _
# Here are some explainations about the design of this module.
# In server/openerp/addons/base/res/res_currency.py :
# compute() -> _get_conversion_rate() -> _current_rate() -> _current_rate_computation()
# compute() -> _get_conversion_rate()
# -> _current_rate() -> _current_rate_computation()
# The date used for the rate is the one in the context
# compute() adds currency_rate_type_from and currency_rate_type_to to the context
# compute() adds currency_rate_type_from
# and currency_rate_type_to to the context
# _get_conversion_rate() adds currency_rate_type_id to context ;
# its value is currency_rate_type_to ;
# if it doesn't exist it's currency_rate_type_from ; if it doesn't exist either it's False
# if it doesn't exist it's currency_rate_type_from ;
# if it doesn't exist either it's False
# It already contains raise "No rate found for currency ... at the date ..."
# _current_rate() reads currency_rate_type_id from context and uses it in the SQL request
# This is the function used for the definition of the field.function 'rate' on res_currency
# _current_rate() reads currency_rate_type_id
# from context and uses it in the SQL request
# This is the function used for the definition of
# the field.function 'rate' on res_currency
# Which one of the 3 functions should we inherit ? Good question...
# It's probably better to inherit the lowest level function, i.e. _current_rate_computation()
# Advantage : by inheriting the lowest level function, we can be sure that the check
# always apply, even for scenarios where we read the field "rate" of the obj currency
# It's probably better to inherit the lowest level function,
# i.e. _current_rate_computation()
# Advantage : by inheriting the lowest level function,
# we can be sure that the check
# always apply, even for scenarios where we
# read the field "rate" of the obj currency
# => that's the solution I implement in the code below
class res_currency(orm.Model):
_inherit = 'res.currency'
def _current_rate_computation(self, cr, uid, ids, name, arg, raise_on_no_rate, context=None):
def _current_rate_computation(self, cr, uid, ids, name, arg,
raise_on_no_rate, context=None):
if context is None:
context = {}
# We only do the check if there is an explicit date in the context and
@@ -55,16 +64,18 @@ class res_currency(orm.Model):
not context.get('disable_rate_date_check'):
for currency_id in ids:
# We could get the company from the currency, but it's not a
# 'required' field, so we should probably continue to get it from
# the user, shouldn't we ?
user = self.pool['res.users'].browse(cr, uid, uid, context=context)
# 'required' field, so we should probably continue to get it
# from the user, shouldn't we ?
user = self.pool['res.users'].browse(cr, uid, uid,
context=context)
# if it's the company currency, don't do anything
# (there is just one old rate at 1.0)
if user.company_id.currency_id.id == currency_id:
continue
else:
# now we do the real work !
date = context.get('date', datetime.today().strftime('%Y-%m-%d'))
date = context.get('date',
datetime.today().strftime('%Y-%m-%d'))
date_datetime = datetime.strptime(date, '%Y-%m-%d')
rate_obj = self.pool['res.currency.rate']
selected_rate = rate_obj.search(cr, uid, [
@@ -75,17 +86,26 @@ class res_currency(orm.Model):
if not selected_rate:
continue
rate_date = rate_obj.read(cr, uid, selected_rate[0], ['name'], context=context)['name']
rate_date_datetime = datetime.strptime(rate_date, '%Y-%m-%d')
rate_date = rate_obj.read(cr, uid, selected_rate[0],
['name'],
context=context)['name']
rate_date_datetime = datetime.strptime(rate_date,
'%Y-%m-%d')
max_delta = user.company_id.currency_rate_max_delta
if (date_datetime - rate_date_datetime).days > max_delta:
currency_name = self.read(cr, uid, currency_id, ['name'], context=context)['name']
currency_name = self.read(cr, uid,
currency_id,
['name'],
context=context)['name']
raise orm.except_orm(
_('Error'),
_('You are requesting a rate conversion on %s for '
'currency %s but the nearest rate before that date is '
'dated %s and the maximum currency rate time delta for '
'your company is %s days') % (date, currency_name, rate_date, max_delta)
'currency %s but the nearest '
'rate before that date is '
'dated %s and the maximum currency '
'rate time delta for '
'your company is %s days') % (
date, currency_name, rate_date, max_delta)
)
# Now we call the regular function from the "base" module
return super(res_currency, self)._current_rate_computation(

View File

@@ -3,8 +3,9 @@
#
# Copyright (c) 2008 Camtocamp SA
# @author JB Aubort, Nicolas Bessi, Joel Grand-Guillaume
# European Central Bank and Polish National Bank invented by Grzegorz Grzelak
# Ported to OpenERP 7.0 by Lorenzo Battistini <lorenzo.battistini@agilebg.com>
# European Central Bank and Polish National Bank by Grzegorz Grzelak
# Ported to OpenERP 7.0 by Lorenzo Battistini
# <lorenzo.battistini@agilebg.com>
# Banxico implemented by Agustin Cruz openpyme.mx
#
# This program is free software: you can redistribute it and/or modify
@@ -35,7 +36,8 @@ The module is able to use 4 different sources:
Updated daily, source in CHF.
2. European Central Bank (ported by Grzegorz Grzelak)
The reference rates are based on the regular daily concertation procedure between
The reference rates are based on the regular
daily concertation procedure between
central banks within and outside the European System of Central Banks,
which normally takes place at 2.15 p.m. (14:15) ECB time. Source in EUR.
http://www.ecb.europa.eu/stats/exchange/eurofxref/html/index.en.html
@@ -43,10 +45,12 @@ The module is able to use 4 different sources:
3. Yahoo Finance
Updated daily
4. Polish National Bank (Narodowy Bank Polski) (contribution by Grzegorz Grzelak)
4. Polish National Bank (Narodowy Bank Polski)
(contribution by Grzegorz Grzelak)
Takes official rates from www.nbp.pl. Adds rate table symbol in log.
You should check when rates should apply to bookkeeping. If next day you should
change the update hour in schedule settings because in OpenERP they apply from
You should check when rates should apply to bookkeeping.
If next day you should change the update hour in schedule settings
because in OpenERP they apply from
date of update (date - no hours).
5. Banxico for USD & MXN (created by Agustín Cruz)
@@ -60,7 +64,8 @@ The update can be set under the company form.
You can set for each services which currency you want to update.
The logs of the update are visible under the service note.
You can active or deactivate the update.
The module uses internal ir_cron feature from OpenERP, so the job is launched once
The module uses internal ir_cron feature from OpenERP,
so the job is launched once
the server starts if the 'first execute date' is before the current day.
The module supports multi-company currency in two ways:
@@ -71,7 +76,8 @@ The module supports multi-company currency in two ways:
A function field lets you know your currency configuration.
If in multi-company mode, the base currency will be the first company's currency
If in multi-company mode, the base currency will
be the first company's currency
found in database.
Thanks to main contributors: Grzegorz Grzelak, Alexis de Lattre

View File

@@ -69,7 +69,8 @@ class res_company(orm.Model):
compagnies = self.search(cr, uid, [])
activate_cron = 'f'
if not value:
# this statement is here beacaus we do no want to save in case of error
# this statement is here beacaus we do no want to save #
# in case of error
self.write(cr, uid, id, {'auto_currency_up': value})
for comp in compagnies:
if self.browse(cr, uid, comp).auto_currency_up:
@@ -84,17 +85,19 @@ class res_company(orm.Model):
else:
for comp in compagnies:
if comp != id and not enable:
if self.browse(cr, uid, comp).multi_company_currency_enable:
current = self.browse(cr, uid, comp)
if current.multi_company_currency_enable:
# We ensure taht we did not have write a true value
self.write(cr, uid, id, {'auto_currency_up': False})
msg = ('You can not activate auto currency'
'update on more thant one company with this '
'multi company configuration')
return {
'value': {'auto_currency_up': False},
'warning': {
'title': "Warning",
'message': 'You can not activate auto currency '
'update on more thant one company with this '
'multi company configuration'
'message': msg,
}
}
self.write(cr, uid, id, {'auto_currency_up': value})

View File

@@ -3,17 +3,20 @@
#
# Copyright (c) 2009 Camptocamp SA
# @source JBA and AWST inpiration
# @contributor Grzegorz Grzelak (grzegorz.grzelak@birdglobe.com), Joel Grand-Guillaume
# @contributor Grzegorz Grzelak (grzegorz.grzelak@birdglobe.com),
# Joel Grand-Guillaume
# Copyright (c) 2010 Alexis de Lattre (alexis@via.ecp.fr)
# - ported XML-based webservices (Admin.ch, ECB, PL NBP) to new XML lib
# - rates given by ECB webservice is now correct even when main_cur <> EUR
# - rates given by PL_NBP webservice is now correct even when main_cur <> PLN
# - if company_currency <> CHF, you can now update CHF via Admin.ch webservice
# - rates given by PL_NBP webs. is now correct even when main_cur <> PLN
# - if company_currency <> CHF, you can now update CHF via Admin.ch
# (same for EUR with ECB webservice and PLN with NBP webservice)
# For more details, see Launchpad bug #645263
# - mecanism to check if rates given by the webservice are "fresh" enough to be
# written in OpenERP ('max_delta_days' parameter for each currency update service)
# Ported to OpenERP 7.0 by Lorenzo Battistini <lorenzo.battistini@agilebg.com>
# - mecanism to check if rates given by the webservice are "fresh"
# enough to be written in OpenERP
# ('max_delta_days' parameter for each currency update service)
# Ported to OpenERP 7.0 by Lorenzo Battistini
# <lorenzo.battistini@agilebg.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -37,6 +40,8 @@
import logging
import time
from datetime import datetime, timedelta
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
from openerp.osv import fields, osv, orm
from openerp.tools.translate import _
@@ -56,11 +61,15 @@ class Currency_rate_update_service(osv.Model):
('Admin_ch_getter', 'Admin.ch'),
('ECB_getter', 'European Central Bank'),
('Yahoo_getter', 'Yahoo Finance '),
('PL_NBP_getter', 'Narodowy Bank Polski'), # Added for polish rates
('Banxico_getter', 'Banco de México'), # Added for mexican rates
# Bank of Canada is using RSS-CB http://www.cbwiki.net/wiki/index.php/Specification_1.1 :
# This RSS format is used by other national banks (Thailand, Malaysia, Mexico...)
('CA_BOC_getter', 'Bank of Canada - noon rates'), # Added for canadian rates
# Added for polish rates
('PL_NBP_getter', 'Narodowy Bank Polski'),
# Added for mexican rates
('Banxico_getter', 'Banco de México'),
# Bank of Canada is using RSS-CB
# http://www.cbwiki.net/wiki/index.php/Specification_1.1
# This RSS format is used by other national banks
# (Thailand, Malaysia, Mexico...)
('CA_BOC_getter', 'Bank of Canada - noon rates'),
],
"Webservice to use",
required=True
@@ -119,13 +128,14 @@ class Currency_rate_update(osv.Model):
_name = "currency.rate.update"
_description = "Currency Rate Update"
# Dict that represent a cron object
nextcall_time = datetime.today() + timedelta(days=1)
nextcall = nextcall_time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
cron = {
'active': False,
'priority': 1,
'interval_number': 1,
'interval_type': 'weeks',
'nextcall': time.strftime("%Y-%m-%d %H:%M:%S",
datetime.today() + timedelta(days=1)).timetuple(),
'nextcall': nextcall,
'numbercall': -1,
'doall': True,
'model': 'currency.rate.update',
@@ -159,7 +169,9 @@ class Currency_rate_update(osv.Model):
cron_id = int(cron_id[0])
except Exception:
_logger.info('warning cron not found one will be created')
pass # Ignore if the cron is missing cause we are going to create it in db
# Ignore if the cron is missing cause we are
# going to create it in db
pass
if not cron_id:
self.cron['name'] = _('Currency Rate Update')
cron_id = cron_obj.create(cr, uid, self.cron, context)
@@ -213,13 +225,14 @@ class Currency_rate_update(osv.Model):
# We initalize the class that will handle the request
# and return a dict of rate
getter = factory.register(service.service)
curr_to_fetch = map(lambda x: x.name, service.currency_to_update)
curr_to_fetch = map(lambda x: x.name,
service.currency_to_update)
res, log_info = getter.get_updated_currency(
curr_to_fetch,
main_curr,
service.max_delta_days
)
rate_name = time.strftime('%Y-%m-%d')
rate_name = time.strftime(DEFAULT_SERVER_DATE_FORMAT)
for curr in service.currency_to_update:
if curr.name == main_curr:
continue
@@ -242,16 +255,22 @@ class Currency_rate_update(osv.Model):
)
# Show the most recent note at the top
msg = "%s \n%s currency updated. %s" % \
(log_info or '',
datetime.strftime(datetime.today(), '%Y-%m-%d %H:%M:%S'),
note)
msg = "%s \n%s currency updated. %s" % (
log_info or '',
datetime.today().strftime(
DEFAULT_SERVER_DATETIME_FORMAT
),
note
)
service.write({'note': msg})
except Exception as exc:
error_msg = "\n%s ERROR : %s %s" %\
(datetime.strftime(datetime.today(), '%Y-%m-%d %H:%M:%S'),
repr(exc),
note)
error_msg = "\n%s ERROR : %s %s" % (
datetime.today().strftime(
DEFAULT_SERVER_DATETIME_FORMAT
),
repr(exc),
note
)
_logger.info(repr(exc))
service.write({'note': error_msg})
@@ -321,40 +340,31 @@ class Curreny_getter_interface(object):
log_info = " "
supported_currency_array = [
'AFN', 'ALL', 'DZD', 'USD', 'USD', 'USD', 'EUR', 'AOA', 'XCD', 'XCD', 'ARS',
'AMD', 'AWG', 'AUD', 'EUR', 'AZN', 'EUR', 'BSD', 'BHD', 'EUR', 'BDT', 'BBD',
'XCD', 'BYR', 'EUR', 'BZD', 'XOF', 'BMD', 'BTN', 'INR', 'BOB', 'ANG', 'BAM',
'BWP', 'NOK', 'BRL', 'GBP', 'USD', 'USD', 'BND', 'BGN', 'XOF', 'MMK', 'BIF',
'XOF', 'USD', 'KHR', 'XAF', 'CAD', 'EUR', 'CVE', 'KYD', 'XAF', 'XAF', 'CLP',
'CNY', 'AUD', 'AUD', 'COP', 'XAF', 'KMF', 'XPF', 'XAF', 'CDF', 'NZD', 'CRC',
'HRK', 'CUP', 'ANG', 'EUR', 'CYP', 'CZK', 'DKK', 'DJF', 'XCD', 'DOP', 'EUR',
'XCD', 'IDR', 'USD', 'EGP', 'EUR', 'SVC', 'USD', 'GBP', 'XAF', 'ETB', 'ERN',
'EEK', 'ETB', 'EUR', 'FKP', 'DKK', 'FJD', 'EUR', 'EUR', 'EUR', 'XPF', 'XPF',
'EUR', 'XPF', 'XAF', 'GMD', 'GEL', 'EUR', 'GHS', 'GIP', 'XAU', 'GBP', 'EUR',
'DKK', 'XCD', 'XCD', 'EUR', 'USD', 'GTQ', 'GGP', 'GNF', 'XOF', 'GYD', 'HTG',
'USD', 'AUD', 'BAM', 'EUR', 'EUR', 'HNL', 'HKD', 'HUF', 'ISK', 'INR', 'IDR',
'XDR', 'IRR', 'IQD', 'EUR', 'IMP', 'ILS', 'EUR', 'JMD', 'NOK', 'JPY', 'JEP',
'JOD', 'KZT', 'AUD', 'KES', 'AUD', 'KPW', 'KRW', 'KWD', 'KGS', 'LAK', 'LVL',
'LBP', 'LSL', 'ZAR', 'LRD', 'LYD', 'CHF', 'LTL', 'EUR', 'MOP', 'MKD', 'MGA',
'EUR', 'MWK', 'MYR', 'MVR', 'XOF', 'EUR', 'MTL', 'FKP', 'USD', 'USD', 'EUR',
'MRO', 'MUR', 'EUR', 'AUD', 'MXN', 'USD', 'USD', 'EUR', 'MDL', 'EUR', 'MNT',
'EUR', 'XCD', 'MAD', 'MZN', 'MMK', 'NAD', 'ZAR', 'AUD', 'NPR', 'ANG', 'EUR',
'XCD', 'XPF', 'NZD', 'NIO', 'XOF', 'NGN', 'NZD', 'AUD', 'USD', 'NOK', 'OMR',
'PKR', 'USD', 'XPD', 'PAB', 'USD', 'PGK', 'PYG', 'PEN', 'PHP', 'NZD', 'XPT',
'PLN', 'EUR', 'STD', 'USD', 'QAR', 'EUR', 'RON', 'RUB', 'RWF', 'STD', 'ANG',
'MAD', 'XCD', 'SHP', 'XCD', 'XCD', 'EUR', 'XCD', 'EUR', 'USD', 'WST', 'EUR',
'SAR', 'SPL', 'XOF', 'RSD', 'SCR', 'SLL', 'XAG', 'SGD', 'ANG', 'ANG', 'EUR',
'EUR', 'SBD', 'SOS', 'ZAR', 'GBP', 'GBP', 'EUR', 'XDR', 'LKR', 'SDG', 'SRD',
'NOK', 'SZL', 'SEK', 'CHF', 'SYP', 'TWD', 'RUB', 'TJS', 'TZS', 'THB', 'IDR',
'TTD', 'XOF', 'NZD', 'TOP', 'TTD', 'TND', 'TRY', 'TMM', 'USD', 'TVD', 'UGX',
'UAH', 'AED', 'GBP', 'USD', 'USD', 'UYU', 'USD', 'UZS', 'VUV', 'EUR', 'VEB',
'VEF', 'VND', 'USD', 'USD', 'USD', 'XPF', 'MAD', 'YER', 'ZMK', 'ZWD'
'AED', 'AFN', 'ALL', 'AMD', 'ANG', 'AOA', 'ARS', 'AUD', 'AWG', 'AZN',
'BAM', 'BBD', 'BDT', 'BGN', 'BHD', 'BIF', 'BMD', 'BND', 'BOB', 'BRL',
'BSD', 'BTN', 'BWP', 'BYR', 'BZD', 'CAD', 'CDF', 'CHF', 'CLP', 'CNY',
'COP', 'CRC', 'CUP', 'CVE', 'CYP', 'CZK', 'DJF', 'DKK', 'DOP', 'DZD',
'EEK', 'EGP', 'ERN', 'ETB', 'EUR', 'FJD', 'FKP', 'GBP', 'GEL', 'GGP',
'GHS', 'GIP', 'GMD', 'GNF', 'GTQ', 'GYD', 'HKD', 'HNL', 'HRK', 'HTG',
'HUF', 'IDR', 'ILS', 'IMP', 'INR', 'IQD', 'IRR', 'ISK', 'JEP', 'JMD',
'JOD', 'JPY', 'KES', 'KGS', 'KHR', 'KMF', 'KPW', 'KRW', 'KWD', 'KYD',
'KZT', 'LAK', 'LBP', 'LKR', 'LRD', 'LSL', 'LTL', 'LVL', 'LYD', 'MAD',
'MDL', 'MGA', 'MKD', 'MMK', 'MNT', 'MOP', 'MRO', 'MTL', 'MUR', 'MVR',
'MWK', 'MXN', 'MYR', 'MZN', 'NAD', 'NGN', 'NIO', 'NOK', 'NPR', 'NZD',
'OMR', 'PAB', 'PEN', 'PGK', 'PHP', 'PKR', 'PLN', 'PYG', 'QAR', 'RON',
'RSD', 'RUB', 'RWF', 'SAR', 'SBD', 'SCR', 'SDG', 'SEK', 'SGD', 'SHP',
'SLL', 'SOS', 'SPL', 'SRD', 'STD', 'SVC', 'SYP', 'SZL', 'THB', 'TJS',
'TMM', 'TND', 'TOP', 'TRY', 'TTD', 'TVD', 'TWD', 'TZS', 'UAH', 'UGX',
'USD', 'UYU', 'UZS', 'VEB', 'VEF', 'VND', 'VUV', 'WST', 'XAF', 'XAG',
'XAU', 'XCD', 'XDR', 'XOF', 'XPD', 'XPF', 'XPT', 'YER', 'ZAR', 'ZMK',
'ZWD'
]
# Updated currency this arry will contain the final result
updated_currency = {}
def get_updated_currency(self, currency_array, main_currency, max_delta_days):
def get_updated_currency(self, currency_array, main_currency,
max_delta_days):
"""Interface method that will retrieve the currency
This function has to be reinplemented in child
"""
@@ -397,23 +407,26 @@ class Curreny_getter_interface(object):
)
# We always have a warning when rate_date != today
rate_date_str = datetime.strftime(rate_date, '%Y-%m-%d')
if rate_date_str != datetime.strftime(datetime.today(), '%Y-%m-%d'):
rate_date_str = datetime.strftime(rate_date,
DEFAULT_SERVER_DATE_FORMAT)
if rate_date.date() != datetime.today().date():
msg = "The rate timestamp (%s) is not today's date"
self.log_info = ("WARNING : %s %s") % (msg, rate_date_str)
_logger.warning(msg, rate_date_str)
# Yahoo ########################################################################
# Yahoo #######################################################################
class Yahoo_getter(Curreny_getter_interface):
"""Implementation of Currency_getter_factory interface
for Yahoo finance service
"""
def get_updated_currency(self, currency_array, main_currency, max_delta_days):
def get_updated_currency(self, currency_array, main_currency,
max_delta_days):
"""implementation of abstract method of curreny_getter_interface"""
self.validate_cur(main_currency)
url = 'http://download.finance.yahoo.com/d/quotes.txt?s="%s"=X&f=sl1c1abg'
url = ('http://download.finance.yahoo.com/d/'
'quotes.txt?s="%s"=X&f=sl1c1abg')
if main_currency in currency_array:
currency_array.remove(main_currency)
for curr in currency_array:
@@ -438,15 +451,23 @@ class Admin_ch_getter(Curreny_getter_interface):
def rate_retrieve(self, dom, ns, curr):
"""Parse a dom node to retrieve currencies data"""
res = {}
xpath_rate_currency = "/def:wechselkurse/def:devise[@code='%s']/def:kurs/text()" % (curr.lower())
xpath_rate_ref = "/def:wechselkurse/def:devise[@code='%s']/def:waehrung/text()" % (curr.lower())
res['rate_currency'] = float(dom.xpath(xpath_rate_currency, namespaces=ns)[0])
res['rate_ref'] = float((dom.xpath(xpath_rate_ref, namespaces=ns)[0]).split(' ')[0])
xpath_rate_currency = ("/def:wechselkurse/def:devise[@code='%s']/"
"def:kurs/text()") % (curr.lower())
xpath_rate_ref = ("/def:wechselkurse/def:devise[@code='%s']/"
"def:waehrung/text()") % (curr.lower())
res['rate_currency'] = float(
dom.xpath(xpath_rate_currency, namespaces=ns)[0]
)
res['rate_ref'] = float(
(dom.xpath(xpath_rate_ref, namespaces=ns)[0]).split(' ')[0]
)
return res
def get_updated_currency(self, currency_array, main_currency, max_delta_days):
def get_updated_currency(self, currency_array, main_currency,
max_delta_days):
"""Implementation of abstract method of Curreny_getter_interface"""
url = 'http://www.afd.admin.ch/publicdb/newdb/mwst_kurse/wechselkurse.php'
url = ('http://www.afd.admin.ch/publicdb/newdb/'
'mwst_kurse/wechselkurse.php')
# We do not want to update the main currency
if main_currency in currency_array:
currency_array.remove(main_currency)
@@ -456,21 +477,36 @@ class Admin_ch_getter(Curreny_getter_interface):
rawfile = self.get_url(url)
dom = etree.fromstring(rawfile)
_logger.debug("Admin.ch sent a valid XML file")
adminch_ns = {'def': 'http://www.afd.admin.ch/publicdb/newdb/mwst_kurse'}
rate_date = dom.xpath('/def:wechselkurse/def:datum/text()', namespaces=adminch_ns)[0]
rate_date_datetime = datetime.strptime(rate_date, '%Y-%m-%d')
adminch_ns = {
'def': 'http://www.afd.admin.ch/publicdb/newdb/mwst_kurse'
}
rate_date = dom.xpath(
'/def:wechselkurse/def:datum/text()',
namespaces=adminch_ns
)
rate_date = rate_date[0]
rate_date_datetime = datetime.strptime(rate_date,
DEFAULT_SERVER_DATE_FORMAT)
self.check_rate_date(rate_date_datetime, max_delta_days)
# we dynamically update supported currencies
self.supported_currency_array = dom.xpath("/def:wechselkurse/def:devise/@code", namespaces=adminch_ns)
self.supported_currency_array = [x.upper() for x in self.supported_currency_array]
self.supported_currency_array = dom.xpath(
"/def:wechselkurse/def:devise/@code",
namespaces=adminch_ns
)
self.supported_currency_array = [x.upper() for x
in self.supported_currency_array]
self.supported_currency_array.append('CHF')
_logger.debug("Supported currencies = " + str(self.supported_currency_array))
_logger.debug(
"Supported currencies = " + str(self.supported_currency_array)
)
self.validate_cur(main_currency)
if main_currency != 'CHF':
main_curr_data = self.rate_retrieve(dom, adminch_ns, main_currency)
# 1 MAIN_CURRENCY = main_rate CHF
main_rate = main_curr_data['rate_currency'] / main_curr_data['rate_ref']
rate_curr = main_curr_data['rate_currency']
rate_ref = main_curr_data['rate_ref']
main_rate = rate_curr / rate_ref
for curr in currency_array:
self.validate_cur(curr)
if curr == 'CHF':
@@ -481,7 +517,8 @@ class Admin_ch_getter(Curreny_getter_interface):
if main_currency == 'CHF':
rate = curr_data['rate_ref'] / curr_data['rate_currency']
else:
rate = main_rate * curr_data['rate_ref'] / curr_data['rate_currency']
rate = (main_rate * curr_data['rate_ref'] /
curr_data['rate_currency'])
self.updated_currency[curr] = rate
_logger.debug(
"Rate retrieved : 1 %s = %s %s" % (main_currency, rate, curr)
@@ -501,17 +538,21 @@ class ECB_getter(Curreny_getter_interface):
"""
res = {}
xpath_curr_rate = "/gesmes:Envelope/def:Cube/def:Cube/def:Cube[@currency='%s']/@rate" % (curr.upper())
res['rate_currency'] = float(dom.xpath(xpath_curr_rate, namespaces=ns)[0])
xpath_curr_rate = ("/gesmes:Envelope/def:Cube/def:Cube/"
"def:Cube[@currency='%s']/@rate") % (curr.upper())
res['rate_currency'] = float(
dom.xpath(xpath_curr_rate, namespaces=ns)[0]
)
return res
def get_updated_currency(self, currency_array, main_currency, max_delta_days):
def get_updated_currency(self, currency_array, main_currency,
max_delta_days):
"""implementation of abstract method of Curreny_getter_interface"""
url = 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'
# Important : as explained on the ECB web site, the currencies are
# at the beginning of the afternoon ; so, until 3 p.m. Paris time
# the currency rates are the ones of trading day N-1
# see http://www.ecb.europa.eu/stats/exchange/eurofxref/html/index.en.html
# http://www.ecb.europa.eu/stats/exchange/eurofxref/html/index.en.html
# We do not want to update the main currency
if main_currency in currency_array:
@@ -528,13 +569,17 @@ class ECB_getter(Curreny_getter_interface):
}
rate_date = dom.xpath('/gesmes:Envelope/def:Cube/def:Cube/@time',
namespaces=ecb_ns)[0]
rate_date_datetime = datetime.strptime(rate_date, '%Y-%m-%d')
rate_date_datetime = datetime.strptime(rate_date,
DEFAULT_SERVER_DATE_FORMAT)
self.check_rate_date(rate_date_datetime, max_delta_days)
# We dynamically update supported currencies
self.supported_currency_array = dom.xpath("/gesmes:Envelope/def:Cube/def:Cube/def:Cube/@currency",
namespaces=ecb_ns)
self.supported_currency_array = dom.xpath(
"/gesmes:Envelope/def:Cube/def:Cube/def:Cube/@currency",
namespaces=ecb_ns
)
self.supported_currency_array.append('EUR')
_logger.debug("Supported currencies = %s " % self.supported_currency_array)
_logger.debug("Supported currencies = %s " %
self.supported_currency_array)
self.validate_cur(main_currency)
if main_currency != 'EUR':
main_curr_data = self.rate_retrieve(dom, ecb_ns, main_currency)
@@ -547,9 +592,12 @@ class ECB_getter(Curreny_getter_interface):
if main_currency == 'EUR':
rate = curr_data['rate_currency']
else:
rate = curr_data['rate_currency'] / main_curr_data['rate_currency']
rate = (curr_data['rate_currency'] /
main_curr_data['rate_currency'])
self.updated_currency[curr] = rate
_logger.debug("Rate retrieved : 1 %s = %s %s" % (main_currency, rate, curr))
_logger.debug(
"Rate retrieved : 1 %s = %s %s" % (main_currency, rate, curr)
)
return self.updated_currency, self.log_info
@@ -564,13 +612,18 @@ class PL_NBP_getter(Curreny_getter_interface):
""" Parse a dom node to retrieve
currencies data"""
res = {}
xpath_rate_currency = "/tabela_kursow/pozycja[kod_waluty='%s']/kurs_sredni/text()" % (curr.upper())
xpath_rate_ref = "/tabela_kursow/pozycja[kod_waluty='%s']/przelicznik/text()" % (curr.upper())
res['rate_currency'] = float(dom.xpath(xpath_rate_currency, namespaces=ns)[0].replace(',', '.'))
xpath_rate_currency = ("/tabela_kursow/pozycja[kod_waluty='%s']/"
"kurs_sredni/text()") % (curr.upper())
xpath_rate_ref = ("/tabela_kursow/pozycja[kod_waluty='%s']/"
"przelicznik/text()") % (curr.upper())
res['rate_currency'] = float(
dom.xpath(xpath_rate_currency, namespaces=ns)[0].replace(',', '.')
)
res['rate_ref'] = float(dom.xpath(xpath_rate_ref, namespaces=ns)[0])
return res
def get_updated_currency(self, currency_array, main_currency, max_delta_days):
def get_updated_currency(self, currency_array, main_currency,
max_delta_days):
"""implementation of abstract method of Curreny_getter_interface"""
# LastA.xml is always the most recent one
url = 'http://www.nbp.pl/kursy/xml/LastA.xml'
@@ -584,18 +637,25 @@ class PL_NBP_getter(Curreny_getter_interface):
dom = etree.fromstring(rawfile)
ns = {} # Cool, there are no namespaces !
_logger.debug("NBP.pl sent a valid XML file")
rate_date = dom.xpath('/tabela_kursow/data_publikacji/text()', namespaces=ns)[0]
rate_date_datetime = datetime.strptime(rate_date, '%Y-%m-%d')
rate_date = dom.xpath('/tabela_kursow/data_publikacji/text()',
namespaces=ns)[0]
rate_date_datetime = datetime.strptime(rate_date,
DEFAULT_SERVER_DATE_FORMAT)
self.check_rate_date(rate_date_datetime, max_delta_days)
# We dynamically update supported currencies
self.supported_currency_array = dom.xpath('/tabela_kursow/pozycja/kod_waluty/text()', namespaces=ns)
self.supported_currency_array = dom.xpath(
'/tabela_kursow/pozycja/kod_waluty/text()',
namespaces=ns
)
self.supported_currency_array.append('PLN')
_logger.debug("Supported currencies = %s" % self.supported_currency_array)
_logger.debug("Supported currencies = %s" %
self.supported_currency_array)
self.validate_cur(main_currency)
if main_currency != 'PLN':
main_curr_data = self.rate_retrieve(dom, ns, main_currency)
# 1 MAIN_CURRENCY = main_rate PLN
main_rate = main_curr_data['rate_currency'] / main_curr_data['rate_ref']
main_rate = (main_curr_data['rate_currency'] /
main_curr_data['rate_ref'])
for curr in currency_array:
self.validate_cur(curr)
if curr == 'PLN':
@@ -606,9 +666,11 @@ class PL_NBP_getter(Curreny_getter_interface):
if main_currency == 'PLN':
rate = curr_data['rate_ref'] / curr_data['rate_currency']
else:
rate = main_rate * curr_data['rate_ref'] / curr_data['rate_currency']
rate = (main_rate * curr_data['rate_ref'] /
curr_data['rate_currency'])
self.updated_currency[curr] = rate
_logger.debug("Rate retrieved : %s = %s %s" % (main_currency, rate, curr))
_logger.debug("Rate retrieved : %s = %s %s" %
(main_currency, rate, curr))
return self.updated_currency, self.log_info
@@ -623,7 +685,8 @@ class Banxico_getter(Curreny_getter_interface):
""" Get currency exchange from Banxico.xml and proccess it
TODO: Get correct data from xml instead of process string
"""
url = 'http://www.banxico.org.mx/rsscb/rss?BMXC_canal=pagos&BMXC_idioma=es'
url = ('http://www.banxico.org.mx/rsscb/rss?'
'BMXC_canal=pagos&BMXC_idioma=es')
from xml.dom.minidom import parse
from StringIO import StringIO
@@ -640,7 +703,8 @@ class Banxico_getter(Curreny_getter_interface):
return float(rate)
def get_updated_currency(self, currency_array, main_currency, max_delta_days=1):
def get_updated_currency(self, currency_array, main_currency,
max_delta_days=1):
"""implementation of abstract method of Curreny_getter_interface"""
logger = logging.getLogger(__name__)
# we do not want to update the main currency
@@ -662,7 +726,8 @@ class Banxico_getter(Curreny_getter_interface):
continue
self.updated_currency[curr] = rate
logger.debug("Rate retrieved : %s = %s %s" % (main_currency, rate, curr))
logger.debug("Rate retrieved : %s = %s %s" %
(main_currency, rate, curr))
# CA BOC ##### Bank of Canada #############################################
@@ -672,11 +737,13 @@ class CA_BOC_getter(Curreny_getter_interface):
"""
def get_updated_currency(self, currency_array, main_currency, max_delta_days):
def get_updated_currency(self, currency_array, main_currency,
max_delta_days):
"""implementation of abstract method of Curreny_getter_interface"""
# as of Jan 2014 BOC is publishing noon rates for about 60 currencies
url = 'http://www.bankofcanada.ca/stats/assets/rates_rss/noon/en_%s.xml'
url = ('http://www.bankofcanada.ca/stats/assets/'
'rates_rss/noon/en_%s.xml')
# closing rates are available as well (please note there are only 12
# currencies reported):
# http://www.bankofcanada.ca/stats/assets/rates_rss/closing/en_%s.xml
@@ -718,11 +785,15 @@ class CA_BOC_getter(Curreny_getter_interface):
.astimezone(pytz.utc).replace(tzinfo=None)
self.check_rate_date(rate_date_datetime, max_delta_days)
self.updated_currency[curr] = rate
_logger.debug("BOC Rate retrieved : %s = %s %s" % (main_currency, rate, curr))
_logger.debug("BOC Rate retrieved : %s = %s %s" %
(main_currency, rate, curr))
else:
_logger.error("Exchange data format error for Bank of Canada -\
%s. Please check provider data format and/or source code." % curr)
raise osv.except_osv('Error !', 'Exchange data format error for\
Bank of Canada - %s !' % str(curr))
_logger.error(
"Exchange data format error for Bank of Canada -"
"%s. Please check provider data format "
"and/or source code." % curr)
raise osv.except_osv('Error !',
'Exchange data format error for\
Bank of Canada - %s !' % str(curr))
return self.updated_currency, self.log_info

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Vincent Renaville (Camptocamp)
# Copyright 2010-2014 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import account_move_line

View File

@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Vincent Renaville (Camptocamp)
# Copyright 2010-2014 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{'name': 'Balance on lines',
'summary': 'Display balance totals in move line view',
'version': '1.1',
'author': 'Camptocamp',
'maintainter': 'Camptocamp',
'category': 'Accounting',
'depends': ['account'],
'description': """
Balance for a line
==================
Add a balance total for grouped lines in move line view.
Balance field will only be shown when move lines are grouped by account
or filtered by account.
Contributors
------------
* Vincent revaville <vincent.renaville@camptocamp.com>
* Yannick Vaucher <yannick.vaucher@camptocamp.com>
""",
'website': 'http://www.camptocamp.com',
'data': ['account_move_line_view.xml'],
'tests': [],
'installable': True,
'auto_install': False,
'license': 'AGPL-3',
'application': False,
}

View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Vincent Renaville (Camptocamp)
# Copyright 2010-2014 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm, fields
class account_move_line(orm.Model):
_inherit = "account.move.line"
def _line_balance(self, cr, uid, ids, field, arg, context=None):
res = {}
move_lines = self.read(cr, uid, ids,
['debit', 'credit'],
context=context)
for line in move_lines:
res[line['id']] = line['debit'] - line['credit']
return res
_columns = {
'line_balance': fields.function(
_line_balance, method=True,
string='Balance',
store=True),
}

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_account_move_line_filter_balance" model="ir.ui.view">
<field name="name">Journal Items add visibilty for balance</field>
<field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_account_move_line_filter"/>
<field name="arch" type="xml">
<field name="account_id" position="attributes">
<attribute name="context">{'invisible_balance': False}</attribute>
</field>
<filter string="Account" position="attributes">
<attribute name="context">{'group_by': 'account_id', 'invisible_balance': False}</attribute>
</filter>
</field>
</record>
<record model="ir.ui.view" id="account_move_line_balance_custom">
<field name="name">account_move_line_balance_custom</field>
<field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_move_line_tree"/>
<field name="arch" type="xml">
<field name="credit" position="after" >
<field name="line_balance" sum="Total Balance" invisible="context.get('invisible_balance', True)"/>
</field>
</field>
</record>
</data>
</openerp>

View File

@@ -0,0 +1,53 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * account_balance_line
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 7.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-06-20 14:11+0000\n"
"PO-Revision-Date: 2014-06-20 14:11+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_balance_line
#: view:account.move.line:0
msgid "Account"
msgstr ""
#. module: account_balance_line
#: model:ir.model,name:account_balance_line.model_account_move_line
msgid "Entry lines"
msgstr ""
#. module: account_balance_line
#: code:_description:0
#, python-format
msgid "Journal Items"
msgstr ""
#. module: account_balance_line
#: field:account.move.line,line_balance:0
msgid "Balance"
msgstr ""
#. module: account_balance_line
#: view:account.move.line:0
msgid "Total Balance"
msgstr ""
#. module: account_balance_line
#: view:account.move.line:0
msgid "{'group_by': 'account_id', 'invisible_balance': False}"
msgstr ""
#. module: account_balance_line
#: view:account.move.line:0
msgid "{'invisible_balance': False}"
msgstr ""

View File

@@ -0,0 +1,53 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * account_balance_line
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 7.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-06-20 14:11+0000\n"
"PO-Revision-Date: 2014-06-20 14:11+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_balance_line
#: view:account.move.line:0
msgid "Account"
msgstr "Compte"
#. module: account_balance_line
#: model:ir.model,name:account_balance_line.model_account_move_line
msgid "Entry lines"
msgstr "Lignes d'écriture"
#. module: account_balance_line
#: code:_description:0
#, python-format
msgid "Journal Items"
msgstr "Écritures comptables"
#. module: account_balance_line
#: field:account.move.line,line_balance:0
msgid "Balance"
msgstr "Balance"
#. module: account_balance_line
#: view:account.move.line:0
msgid "Total Balance"
msgstr "Balance Totale"
#. module: account_balance_line
#: view:account.move.line:0
msgid "{'group_by': 'account_id', 'invisible_balance': False}"
msgstr ""
#. module: account_balance_line
#: view:account.move.line:0
msgid "{'invisible_balance': False}"
msgstr ""

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
#
#
# Authors: Adrien Peiffer
# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
# All Rights Reserved
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs.
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly advised to contact a Free Software
# Service Company.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#
from . import model
from . import tests

View File

@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
#
#
# Authors: Adrien Peiffer
# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
# All Rights Reserved
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs.
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly advised to contact a Free Software
# Service Company.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#
{
"name": "Account Journal Period Close",
"version": "1.0",
"author": "ACSONE SA/NV",
"maintainer": "ACSONE SA/NV",
"website": "http://www.acsone.eu",
"images": [],
"category": "Accounting",
"depends": [
"account"],
"description": """
Close period per journal
========================
This module allows fine grained control of period closing.
Each journal can be closed independently for any period
(using buttons on the fiscal period view).
A common use case is letting accountants close the sale
and purchase journals when the VAT declaration is done for
a given period, while leaving the miscellaneous journal open.
From a technical standpoint, the module leverages the
account.journal.period model that is present in Odoo core.
""",
"data": ['view/account_view.xml'],
"demo": [],
"test": [],
"licence": "AGPL-3",
"installable": True,
"auto_install": False,
"application": True,
}

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
#
#
# Authors: Adrien Peiffer
# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
# All Rights Reserved
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs.
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly advised to contact a Free Software
# Service Company.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#
from . import account

View File

@@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
#
#
# Authors: Adrien Peiffer
# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
# All Rights Reserved
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs.
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly advised to contact a Free Software
# Service Company.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#
from openerp.osv import orm, fields
from openerp.tools.translate import _
class account_period(orm.Model):
_inherit = 'account.period'
_columns = {
'journal_period_ids': fields.one2many('account.journal.period',
'period_id', 'Journal states'),
}
class account_journal_period(orm.Model):
_inherit = 'account.journal.period'
_order = "type,name"
_columns = {
'type': fields.related('journal_id', 'type', type='char',
relation='account.journal',
string='Journal Type',
store=True, readonly=True)
}
def _check(self, cr, uid, ids, context=None):
return True
def action_draft(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state': 'draft'})
def action_done(self, cr, uid, ids, context=None):
for journal_period in self.browse(cr, uid, ids, context=context):
draft_move_ids = self.pool.get('account.move')\
.search(cr, uid, [('period_id', '=',
journal_period.period_id.id),
('state', '=', "draft"),
('journal_id', '=',
journal_period.journal_id.id)],
context=context)
if draft_move_ids:
raise orm.except_orm(_('Invalid Action!'),
_('In order to close a journal,'
' you must first post related'
' journal entries.'))
return self.write(cr, uid, ids, {'state': 'done'})
def create(self, cr, uid, values, context=None):
if 'name' not in values:
if values.get('period_id') and values.get('journal_id'):
journal = self.pool.get('account.journal')\
.browse(cr, uid, values['journal_id'], context=context)
period = self.pool.get('account.period')\
.browse(cr, uid, values['period_id'], context=context)
values.update({'name': (journal.code or journal.name)+':' +
(period.name or '')}),
return super(account_journal_period, self).create(cr,
uid,
values,
context=context)

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
#
#
# Authors: Adrien Peiffer
# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
# All Rights Reserved
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs.
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly advised to contact a Free Software
# Service Company.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#
from . import test_account_journal_period_close

View File

@@ -0,0 +1,196 @@
# -*- coding: utf-8 -*-
#
#
# Authors: Adrien Peiffer
# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
# All Rights Reserved
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs.
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly advised to contact a Free Software
# Service Company.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#
import openerp.tests.common as common
from openerp.osv import orm
from datetime import datetime
DB = common.DB
ADMIN_USER_ID = common.ADMIN_USER_ID
def get_simple_account_move_values(self, period_id, journal_id):
sale_product_account_id = self.ref('account.a_sale')
cash_account_id = self.ref('account.cash')
partner_id = self.ref('base.res_partner_2')
year = datetime.now().strftime('%Y')
return {'partner_id': partner_id,
'period_id': period_id,
'date': year + '-01-01',
'journal_id': journal_id,
'line_id': [(0, 0, {'name': 'test',
'account_id': cash_account_id,
'debit': 50.0,
}),
(0, 0, {'name': 'test_conterpart',
'account_id': sale_product_account_id,
'credit': 50.0,
})
]
}
def close_period(self, period_id, context=None):
close_period_wizard_id =\
self.registry('account.period.close').create(self.cr,
self.uid,
{'sure': True
},
context=context)
context.update({'active_ids': [period_id]})
self.registry('account.period.close')\
.data_save(self.cr,
self.uid,
[close_period_wizard_id],
context=context)
def create_journal_period(self, period_id, journal_id, context):
jour_per_obj = self.registry('account.journal.period')
journal_period_id = jour_per_obj.create(self.cr,
self.uid,
{'period_id': period_id,
'journal_id': journal_id,
},
context=context)
return journal_period_id
def journal_period_done(self, journal_period_id, context):
jour_per_obj = self.registry('account.journal.period')
jour_per_obj.action_done(self.cr,
self.uid,
[journal_period_id],
context=context)
def journal_period_draft(self, journal_period_id, context):
jour_per_obj = self.registry('account.journal.period')
jour_per_obj.action_draft(self.cr,
self.uid,
[journal_period_id],
context=context)
class TestAccountConstraintChronology(common.TransactionCase):
def setUp(self):
super(TestAccountConstraintChronology, self).setUp()
def test_close_period_open_journal(self):
context = {}
journal_id = self.ref('account.sales_journal')
period_id = self.ref('account.period_1')
close_period(self, period_id, context)
journal_period_id = create_journal_period(self,
period_id,
journal_id,
context)
journal_period_draft(self, journal_period_id, context)
self.registry('account.move')\
.create(self.cr,
self.uid,
get_simple_account_move_values(self,
period_id,
journal_id),
context=context)
# Here, no exception should be raised because the journal's state is
# draft although the period is closed
def test_open_period_close_journal(self):
context = {}
journal_id = self.ref('account.sales_journal')
period_id = self.ref('account.period_1')
journal_period_id = create_journal_period(self,
period_id,
journal_id,
context)
journal_period_done(self, journal_period_id, context)
move_values = get_simple_account_move_values(self,
period_id,
journal_id)
# I check if the exception is correctly raised at create of an account
# move which is linked with a closed journal
self.assertRaises(orm.except_orm,
self.registry('account.move').create,
self.cr, self.uid, move_values, context=context)
def test_change_journal_on_move(self):
context = {}
journal_id = self.ref('account.sales_journal')
journal_cash_id = self.ref('account.cash_journal')
period_id = self.ref('account.period_1')
journal_period_id = create_journal_period(self,
period_id,
journal_id,
context)
journal_period_done(self, journal_period_id, context)
move_values = get_simple_account_move_values(self,
period_id,
journal_cash_id)
self.registry('account.move').create(self.cr,
self.uid,
move_values,
context=context)
# Standard of Odoo doesn't check account_journal_period at write on
# account_move
# issue on Odoo github : #1633
# I check if the exception is correctly raised
"""self.assertRaises(orm.except_orm,
self.registry('account.move').write,
self.cr, self.uid, [move_id],
{'journal_id': journal_id}, context=context)"""
def test_draft_move_close_journal(self):
context = {}
jour_per_obj = self.registry('account.journal.period')
journal_id = self.ref('account.sales_journal')
period_id = self.ref('account.period_1')
move_values = get_simple_account_move_values(self,
period_id,
journal_id)
self.registry('account.move').create(self.cr,
self.uid,
move_values,
context=context)
journal_period_ids =\
jour_per_obj.search(self.cr,
self.uid,
[('period_id', '=', period_id),
('journal_id', '=', journal_id),
],
context=context)
# I check if the exception is correctly raised at closing journal that
# contains some draft account move
self.assertRaises(orm.except_orm,
jour_per_obj.action_done,
self.cr, self.uid, journal_period_ids,
context=context)

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_account_period_form" model="ir.ui.view">
<field name="name">account.period.form
(account_journal_period_close)</field>
<field name="model">account.period</field>
<field name="inherit_id" ref="account.view_account_period_form" />
<field name="arch" type="xml">
<xpath expr="//sheet/group" position="after">
<notebook>
<page string="Journals">
<field name="journal_period_ids">
<tree editable="bottom">
<field name="journal_id" attrs="{'readonly': [('type', '!=', False)]}"/>
<field name="type"/>
<field name="state" readonly="0"
invisible="1" />
<button name="action_done"
type="object" icon="gtk-cancel"
states="draft" help="Close journal for this period"/>
<button name="action_draft"
type="object" icon="gtk-redo"
states="done" help="Reopen journal for this period"/>
</tree>
</field>
</page>
</notebook>
</xpath>
</field>
</record>
</data>
</openerp>

View File

@@ -0,0 +1,22 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * account_move_line_search_extension
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 7.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-06-27 13:15+0000\n"
"PO-Revision-Date: 2014-06-27 13:15+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_move_line_search_extension
#: model:ir.actions.act_window,name:account_move_line_search_extension.action_account_move_line_search_extension
#: model:ir.ui.menu,name:account_move_line_search_extension.menu_account_move_line_search_extension
msgid "Journal Items Search All"
msgstr "Écritures comptables"