Merge branch '8.0' into 8.0-add-account_constraint_chronology

This commit is contained in:
Stéphane Bidoul
2014-08-13 17:58:49 +02:00
76 changed files with 1492 additions and 1046 deletions

15
.coveragerc Normal file
View File

@@ -0,0 +1,15 @@
[report]
include =
*/account-financial-tools/*
omit =
*/tests/*
*__init__.py
# Regexes for lines to exclude from consideration
exclude_lines =
# Have to re-enable the standard pragma
pragma: no cover
# Don't complain about null context checking
if context is None:

25
.travis.yml Normal file
View File

@@ -0,0 +1,25 @@
# Config file .travis.yml
language: python
python:
- "2.7"
env:
- VERSION="8.0" ODOO_REPO="odoo/odoo"
- VERSION="8.0" ODOO_REPO="OCA/OCB"
virtualenv:
system_site_packages: true
install:
- git clone https://github.com/oca/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools
- export PATH=${HOME}/maintainer-quality-tools/travis:${PATH}
- travis_install_nightly ${VERSION}
script:
- travis_run_flake8
- travis_run_tests
after_success:
coveralls

View File

@@ -18,5 +18,3 @@
#
##############################################################################
from . import account_invoice
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -18,20 +18,18 @@
#
##############################################################################
{
"name" : "Cancel invoice, check on payment order",
"version" : "1.0",
"depends" : ["account",
"account_payment",
"account_cancel"
],
"author" : "Camptocamp",
"name": "Cancel invoice, check on payment order",
"version": "1.0",
"depends": ["account",
"account_payment",
"account_cancel"],
"author": "Camptocamp",
"description": """
Prevents to cancel an invoice which has already been imported in a
payment order.
""",
'website': 'http://www.camptocamp.com',
'data' : [],
'installable': False,
'data': [],
'installable': True,
'active': False,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -45,10 +45,11 @@ class account_invoice(orm.Model):
payment_orders = cr.dictfetchone()
if payment_orders:
raise osv.except_osv(
_('Error !'),
_("Invoice already imported in the payment "
"order (%s) at %s on line %s" %
(payment_orders['payment_name'],
payment_orders['payment_date'],
payment_orders['name'])))
return super(account_invoice,self).action_cancel(cr, uid, ids, *args)
_('Error !'),
_("Invoice already imported in the payment "
"order (%s) at %s on line %s" %
(payment_orders['payment_name'],
payment_orders['payment_date'],
payment_orders['name']))
)
return super(account_invoice, self).action_cancel(cr, uid, ids, *args)

View File

@@ -19,5 +19,3 @@
##############################################################################
from . import account_invoice
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -18,19 +18,19 @@
#
##############################################################################
{
"name" : "Cancel invoice, check on bank statement",
"version" : "1.0",
"depends" : ["base",
"account",
"account_voucher",
"account_cancel",
],
"author" : "Camptocamp",
"description": """Constraint forbidding to cancel an invoice already imported in bank statement with a voucher.
"name": "Cancel invoice, check on bank statement",
"version": "1.0",
"depends": ["base",
"account",
"account_voucher",
"account_cancel"],
"author": "Camptocamp",
"description": """
Constraint forbidding to cancel an invoice already
imported in bank statement with a voucher.
""",
'website': 'http://www.camptocamp.com',
'date' : [],
'installable': False,
'active': False,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -24,6 +24,7 @@
from openerp.tools.translate import _
from openerp.osv import osv, orm
class account_invoice(orm.Model):
_inherit = "account.invoice"
@@ -31,25 +32,26 @@ 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
# 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],))
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'
% (statement_lines['statement_name'],
statement_lines['statement_date'],
statement_lines['name'],)))
raise osv.except_osv(
_('Error!'),
_('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

@@ -96,23 +96,77 @@ class wizard_update_charts_accounts(orm.TransientModel):
('ready', 'Step 2'),
('done', 'Wizard completed')
], 'Status', readonly=True),
'company_id': fields.many2one('res.company', 'Company', required=True, ondelete='set null'),
'company_id': fields.many2one(
'res.company',
'Company',
required=True,
ondelete='set null'
),
'chart_template_id': fields.many2one(
'account.chart.template', 'Chart Template', ondelete='cascade',
required=True),
'code_digits': fields.integer('# of digits', required=True, help="No. of digits to use for account code. Make sure it is the same number as existing accounts."),
'lang': fields.selection(_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."),
'update_tax_code': fields.boolean('Update tax codes', help="Existing tax codes are updated. Tax codes are searched by name."),
'update_tax': fields.boolean('Update taxes', help="Existing taxes are updated. Taxes are searched by name."),
'update_account': fields.boolean('Update accounts', 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."),
'update_children_accounts_parent': fields.boolean("Update children accounts parent",
help="Update the parent of accounts that seem (based on the code) to be children of the newly created ones. If you had an account 430 with a child 4300000, and a 4300 account is created, the 4300000 parent will be set to 4300."),
'continue_on_errors': fields.boolean("Continue on errors", help="If set, the wizard will continue to the next step even if there are minor errors (for example the parent account of a new account couldn't be set)."),
'tax_code_ids': fields.one2many('wizard.update.charts.accounts.tax.code', 'update_chart_wizard_id', 'Tax codes', ondelete='cascade'),
'tax_ids': fields.one2many('wizard.update.charts.accounts.tax', 'update_chart_wizard_id', '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.chart.template',
'Chart Template',
ondelete='cascade',
required=True
),
'code_digits': fields.integer(
'# of digits',
required=True,
help="No. of digits to use for account code. "
"Make sure it is the same number as existing accounts."
),
'lang': fields.selection(
_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."
),
'update_tax_code': fields.boolean(
'Update tax codes',
help="Existing tax codes are updated."
" Tax codes are searched by name."
),
'update_tax': fields.boolean(
'Update taxes',
help="Existing taxes are updated. Taxes are searched by name."
),
'update_account': fields.boolean(
'Update accounts',
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."
),
'update_children_accounts_parent': fields.boolean(
"Update children accounts parent",
help="Update the parent of accounts that seem (based on the code)"
" to be children of the newly created ones."
" If you had an account 430 with a child 4300000, and a 4300 "
"account is created, the 4300000 parent will be set to 4300."
),
'continue_on_errors': fields.boolean(
"Continue on errors",
help="If set, the wizard will continue to the next step even if "
"there are minor errors (for example the parent account "
"of a new account couldn't be set)."
),
'tax_code_ids': fields.one2many(
'wizard.update.charts.accounts.tax.code',
'update_chart_wizard_id',
'Tax codes',
ondelete='cascade'
),
'tax_ids': fields.one2many(
'wizard.update.charts.accounts.tax',
'update_chart_wizard_id',
'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'),
'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),
@@ -208,7 +262,8 @@ 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,
@@ -316,10 +371,10 @@ class wizard_update_charts_accounts(orm.TransientModel):
if acc_templ.type != 'view':
if code and len(code) <= wizard.code_digits:
code = '%s%s' % (code, '0' * (wizard.code_digits - len(code)))
acc_ids = acc_obj.search(cr, uid, [
('code', '=', code),
('company_id', '=', wizard.company_id.id)
], context=context)
acc_ids = acc_obj.search(cr, uid,
[('code', '=', code),
('company_id', '=', wizard.company_id.id)],
context=context)
acc_templ_mapping[acc_templ.id] = acc_ids and acc_ids[0] or False
return acc_templ_mapping[acc_templ.id]
@@ -344,7 +399,7 @@ class wizard_update_charts_accounts(orm.TransientModel):
context=None):
"""
Search for, and load, tax code templates to create/update.
@param chart_template_ids: IDs of the chart templates to look on,
calculated once in the calling method.
"""
@@ -363,13 +418,16 @@ class wizard_update_charts_accounts(orm.TransientModel):
children_tax_code_template = tax_code_templ_obj.search(cr, uid, [(
'parent_id', 'child_of', [root_tax_code_id])], order='id',
context=context)
for tax_code_template in tax_code_templ_obj.browse(cr, uid,
children_tax_code_template, context=context):
for tax_code_template in tax_code_templ_obj.browse(
cr, uid,
children_tax_code_template, context=context):
# Ensure the tax code template is on the map (search for the mapped
# tax code id).
tax_code_id = self._map_tax_code_template(cr, uid, wizard,
tax_code_template_mapping,
tax_code_template, context=context)
tax_code_id = self._map_tax_code_template(
cr, uid, wizard,
tax_code_template_mapping,
tax_code_template, context=context
)
if not tax_code_id:
new_tax_codes += 1
wiz_tax_code_obj.create(cr, uid, {
@@ -413,7 +471,7 @@ class wizard_update_charts_accounts(orm.TransientModel):
def _find_taxes(self, cr, uid, wizard, chart_template_ids, context=None):
"""
Search for, and load, tax templates to create/update.
@param chart_template_ids: IDs of the chart templates to look on,
calculated once in the calling method.
"""
@@ -527,9 +585,11 @@ class wizard_update_charts_accounts(orm.TransientModel):
'=',
wizard.chart_template_id.id)]
if root_account_id:
acc_templ_criteria = (['|'] + acc_templ_criteria +
['&', ('parent_id', 'child_of', [root_account_id]),
('chart_template_id', '=', False)])
acc_templ_criteria = (
['|'] + acc_templ_criteria +
['&', ('parent_id', 'child_of', [root_account_id]),
('chart_template_id', '=', False)]
)
acc_ids = acc_templ_obj.search(cr, uid, acc_templ_criteria,
context=context)
acc_ids.sort()
@@ -577,17 +637,15 @@ class wizard_update_charts_accounts(orm.TransientModel):
'update_account_id': account_id,
'notes': notes,
}, context)
return {
'new': new_accounts,
return {'new': new_accounts,
'updated': updated_accounts,
'mapping': acc_templ_mapping
}
'mapping': acc_templ_mapping}
def _find_fiscal_positions(self, cr, uid, wizard, chart_template_ids,
context=None):
"""
Search for, and load, fiscal position templates to create/update.
@param chart_template_ids: IDs of the chart templates to look on,
calculated once in the calling method.
"""
@@ -608,7 +666,7 @@ class wizard_update_charts_accounts(orm.TransientModel):
chart_template_ids)],
context=context)
for fp_templ in fp_templ_obj.browse(cr, uid, fp_template_ids,
context=context):
context=context):
# Ensure the fiscal position template is on the map (search for the
# mapped fiscal position id).
fp_id = self._map_fp_template(cr, uid, wizard, fp_templ_mapping,
@@ -651,8 +709,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
break
if not found:
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'))
fp_tax_templ.tax_src_id.name,
fp_tax_templ.tax_dest_id and fp_tax_templ.tax_dest_id.name or _('None'))
modified = True
elif fp_templ.tax_ids and not fp.tax_ids:
notes += _("The template has taxes the fiscal position instance does not.\n")
@@ -673,10 +731,11 @@ class wizard_update_charts_accounts(orm.TransientModel):
found = True
break
if not found:
notes += _("Account mapping not found on the fiscal "
"position instance: %s -> %s.\n") % \
(fp_acc_templ.account_src_id.name,
fp_acc_templ.account_dest_id.name)
notes += _(
"Account mapping not found on the fiscal "
"position instance: %s -> %s.\n") \
% (fp_acc_templ.account_src_id.name,
fp_acc_templ.account_dest_id.name)
modified = True
elif fp_templ.account_ids and not fp.account_ids:
notes += _("The template has accounts the fiscal position "
@@ -755,7 +814,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
tax_code_template_mapping = {}
for wiz_tax_code in wizard.tax_code_ids:
tax_code_template = wiz_tax_code.tax_code_id
tax_code_name = (root_tax_code_id == tax_code_template.id) and wizard.company_id.name or tax_code_template.name
tax_code_name = ((root_tax_code_id == tax_code_template.id) and
wizard.company_id.name or tax_code_template.name)
# Ensure the parent tax code template is on the map.
self._map_tax_code_template(cr, uid, wizard,
tax_code_template_mapping,
@@ -766,7 +826,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
'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),
'parent_id': (tax_code_template.parent_id and
tax_code_template_mapping.get(tax_code_template.parent_id.id)),
'company_id': wizard.company_id.id,
'sign': tax_code_template.sign,
}
@@ -792,8 +853,13 @@ class wizard_update_charts_accounts(orm.TransientModel):
tax_code_template_mapping[tax_code_template.id] = tax_code_id
if modified:
# Detect errors
if tax_code_template.parent_id and not tax_code_template_mapping.get(tax_code_template.parent_id.id):
log.add(_("Tax code %s: The parent tax code %s can not be set.\n") % (tax_code_name, tax_code_template.parent_id.name), True)
if (tax_code_template.parent_id and
not tax_code_template_mapping.get(tax_code_template.parent_id.id)):
log.add(
_("Tax code %s: The parent tax code %s can not be set.\n") % (
tax_code_name, tax_code_template.parent_id.name),
True
)
return {
'new': new_tax_codes,
'updated': updated_tax_codes,
@@ -838,13 +904,16 @@ class wizard_update_charts_accounts(orm.TransientModel):
'python_compute': tax_template.python_compute,
'python_compute_inv': tax_template.python_compute_inv,
'python_applicable': tax_template.python_applicable,
#'tax_group': tax_template.tax_group,
'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,
@@ -874,20 +943,43 @@ 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):
log.add(_("Tax %s: The parent tax %s can not be set.\n") % (tax_template.name, tax_template.parent_id.name), True)
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):
log.add(_("Tax %s: The tax code for the base %s can not be set.\n") % (tax_template.name, tax_template.base_code_id.name), True)
log.add(
_("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):
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), True)
if tax_template.ref_base_code_id and 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), True)
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),
True
)
if (tax_template.ref_base_code_id and
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),
True
)
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), True)
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),
True
)
return {
'new': new_taxes,
'updated': updated_taxes,
@@ -920,7 +1012,11 @@ class wizard_update_charts_accounts(orm.TransientModel):
try:
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), True)
log.add(
_("Exception setting the parent of account %s children: %s - %s.\n") % (
parent_account.code, ex.name, ex.value),
True
)
return True
@@ -945,7 +1041,9 @@ class wizard_update_charts_accounts(orm.TransientModel):
self._map_tax_template(cr, uid, wizard, tax_template_mapping,
tax_template, context)
# Get the tax ids
tax_ids = [tax_template_mapping[tax_template.id] for tax_template in account_template.tax_ids if tax_template_mapping[tax_template.id]]
tax_ids = [tax_template_mapping[tax_template.id]
for tax_template in account_template.tax_ids
if tax_template_mapping[tax_template.id]]
# Calculate the account code (we need to add zeros to non-view
# account codes)
code = account_template.code or ''
@@ -956,7 +1054,6 @@ class wizard_update_charts_accounts(orm.TransientModel):
# Values
vals = {
'name': (root_account_id == account_template.id) and wizard.company_id.name or account_template.name,
#'sign': account_template.sign,
'currency_id': account_template.currency_id and account_template.currency_id.id or False,
'code': code,
'type': account_template.type,
@@ -964,7 +1061,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
'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(account_template.parent_id.id) or False),
'tax_ids': [(6, 0, tax_ids)],
'company_id': wizard.company_id.id,
}
@@ -998,7 +1096,11 @@ class wizard_update_charts_accounts(orm.TransientModel):
if modified:
# Detect errors
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), True)
log.add(
_("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
# be its children (brothers starting with the same code).
if wizard.update_children_accounts_parent:
@@ -1037,7 +1139,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
self._map_account_template(cr, uid, wizard, acc_templ_mapping,
acc_templ, context=context)
if value['account_collected_id'] or value['account_paid_id']:
if acc_templ_mapping.get(value['account_collected_id']) and acc_templ_mapping.get(value['account_paid_id']):
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']],
@@ -1108,9 +1211,17 @@ class wizard_update_charts_accounts(orm.TransientModel):
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), True)
log.add(
_("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):
log.add(_("Fiscal position %s: The destination tax %s can not be set.\n") % (fp_template.name, fp_tax.tax_dest_id.name), True)
log.add(
_("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
for fp_account in fp_template.account_ids:
# Ensure the related account templates are on the map.
@@ -1126,15 +1237,24 @@ class wizard_update_charts_accounts(orm.TransientModel):
# 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_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), True)
log.add(
_("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):
log.add(_("Fiscal position %s: The destination account %s can not be set.\n") % (fp_template.name, fp_account.account_dest_id.code), True)
log.add(
_("Fiscal position %s: The destination account %s can not be set.\n") % (
fp_template.name, fp_account.account_dest_id.code),
True
)
log.add(_("Created or updated fiscal position %s.\n")
% fp_template.name)
@@ -1207,13 +1327,28 @@ class wizard_update_charts_accounts_tax_code(orm.TransientModel):
"""
_name = 'wizard.update.charts.accounts.tax.code'
_columns = {
'tax_code_id': fields.many2one('account.tax.code.template', 'Tax code template', required=True, ondelete='set null'),
'update_chart_wizard_id': fields.many2one('wizard.update.charts.accounts', 'Update chart wizard', required=True, ondelete='cascade'),
'tax_code_id': fields.many2one(
'account.tax.code.template',
'Tax code template',
required=True,
ondelete='set null'
),
'update_chart_wizard_id': fields.many2one(
'wizard.update.charts.accounts',
'Update chart wizard',
required=True,
ondelete='cascade'
),
'type': fields.selection([
('new', 'New template'),
('updated', 'Updated template'),
], 'Type'),
'update_tax_code_id': fields.many2one('account.tax.code', 'Tax code to update', required=False, ondelete='set null'),
'update_tax_code_id': fields.many2one(
'account.tax.code',
'Tax code to update',
required=False,
ondelete='set null'
),
'notes': fields.text('Notes'),
}
_defaults = {
@@ -1229,12 +1364,14 @@ 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'),
'update_chart_wizard_id': fields.many2one('wizard.update.charts.accounts', 'Update chart wizard', required=True, ondelete='cascade'),
'update_chart_wizard_id': fields.many2one(
'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'),
'update_tax_id': fields.many2one(
'account.tax', 'Tax to update', required=False, ondelete='set null'),
'notes': fields.text('Notes'),
}
@@ -1253,13 +1390,28 @@ class wizard_update_charts_accounts_account(orm.TransientModel):
# limit for the objects in memory to let the wizard create all the items
# at once.
_columns = {
'account_id': fields.many2one('account.account.template', 'Account template', required=True, ondelete='set null'),
'update_chart_wizard_id': fields.many2one('wizard.update.charts.accounts', 'Update chart wizard', required=True, ondelete='cascade'),
'account_id': fields.many2one(
'account.account.template',
'Account template',
required=True,
ondelete='set null'
),
'update_chart_wizard_id': fields.many2one(
'wizard.update.charts.accounts',
'Update chart wizard',
required=True,
ondelete='cascade'
),
'type': fields.selection([
('new', 'New template'),
('updated', 'Updated template'),
], 'Type'),
'update_account_id': fields.many2one('account.account', 'Account to update', required=False, ondelete='set null'),
'update_account_id': fields.many2one(
'account.account',
'Account to update',
required=False,
ondelete='set null'
),
'notes': fields.text('Notes'),
}
@@ -1275,13 +1427,28 @@ class wizard_update_charts_accounts_fiscal_position(orm.TransientModel):
"""
_name = 'wizard.update.charts.accounts.fiscal.position'
_columns = {
'fiscal_position_id': fields.many2one('account.fiscal.position.template', 'Fiscal position template', required=True, ondelete='set null'),
'update_chart_wizard_id': fields.many2one('wizard.update.charts.accounts', 'Update chart wizard', required=True, ondelete='cascade'),
'fiscal_position_id': fields.many2one(
'account.fiscal.position.template',
'Fiscal position template',
required=True,
ondelete='set null'
),
'update_chart_wizard_id': fields.many2one(
'wizard.update.charts.accounts',
'Update chart wizard',
required=True,
ondelete='cascade'
),
'type': fields.selection([
('new', 'New template'),
('updated', 'Updated template'),
], 'Type'),
'update_fiscal_position_id': fields.many2one('account.fiscal.position', 'Fiscal position to update', required=False, ondelete='set null'),
'update_fiscal_position_id': fields.many2one(
'account.fiscal.position',
'Fiscal position to update',
required=False,
ondelete='set null'
),
'notes': fields.text('Notes'),
}
_defaults = {

View File

@@ -19,5 +19,3 @@
##############################################################################
from . import account_move_line
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -18,16 +18,14 @@
#
##############################################################################
{
"name" : "Recompute tax_amount",
"version" : "1.0",
"depends" : ["base",
"account",
],
"author" : "Camptocamp",
"name": "Recompute tax_amount",
"version": "1.0",
"depends": ["base",
"account"],
"author": "Camptocamp",
"description": """Recompute tax_amount to avoid sign problem""",
'website': 'http://www.camptocamp.com',
'data' : [],
'installable': False,
'active': False,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -56,10 +56,10 @@ class account_move_line(orm.Model):
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)
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

@@ -17,4 +17,4 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import account_constraints
import account_constraints

View File

@@ -18,12 +18,10 @@
#
##############################################################################
{
'name' : 'Account Constraints',
'version' : '1.1',
'depends' : [
'account',
],
'author' : 'Camptocamp',
'name': 'Account Constraints',
'version': '1.1',
'depends': ['account'],
'author': 'Camptocamp',
'license': 'AGPL-3',
'category': 'Generic Modules/Accounting',
'description': """
@@ -48,12 +46,12 @@ Summary of constraints are:
* Add a check on entries that user cannot provide a secondary currency
if the same than the company one.
* Remove the possibility to modify or delete a move line related to an
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
that the user cannot make mistakes even in draft state, he must pass through the
parent object to make his modification.
Contributors

View File

@@ -82,7 +82,7 @@ class AccountMoveLine(orm.Model):
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)
'change the related invoice directly.\n%s.') % err_msg)
return True
def _check_statement_related_move(self, cr, uid, ids, vals=None, context=None):
@@ -95,7 +95,7 @@ class AccountMoveLine(orm.Model):
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 must change the related bank statement directly.\n%s.') % err_msg)
return True
def unlink(self, cr, uid, ids, context=None, check=True):
@@ -134,8 +134,10 @@ class AccountMoveLine(orm.Model):
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)
return super(AccountMoveLine, self).write(cr, uid, ids, vals,
context=context, check=check, update_check=update_check)
return super(AccountMoveLine, self).write(
cr, uid, ids, vals,
context=context, check=check, update_check=update_check
)
def _check_currency_and_amount(self, cr, uid, ids, context=None):
for l in self.browse(cr, uid, ids, context=context):
@@ -161,23 +163,22 @@ class AccountMoveLine(orm.Model):
return True
_constraints = [
(_check_currency_and_amount,
"You cannot create journal items with a secondary currency "
"without recording both 'currency' and 'amount currency' field.",
['currency_id','amount_currency']
),
(_check_currency_amount,
"The amount expressed in the secondary currency must be positive "
"when journal item are debit and negatif when journal item are "
"credit.",
['amount_currency']
),
(_check_currency_company,
"You can't provide a secondary currency if "
"the same than the company one.",
['currency_id']
),
]
(_check_currency_and_amount,
"You cannot create journal items with a secondary currency "
"without recording both 'currency' and 'amount currency' field.",
['currency_id', 'amount_currency']),
(_check_currency_amount,
"The amount expressed in the secondary currency must be positive "
"when journal item are debit and negatif when journal item are "
"credit.",
['amount_currency']),
(_check_currency_company,
"You can't provide a secondary currency if "
"the same than the company one.",
['currency_id']),
]
class AccountInvoice(orm.Model):
@@ -203,7 +204,7 @@ 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):
@@ -232,5 +233,6 @@ class AccountBankStatement(orm.Model):
context = context.copy()
context['from_parent_object'] = True
return super(AccountBankStatement, self).create_move_from_st_line(
cr, uid, st_line_id, company_currency_id,
st_line_number, context=context)
cr, uid, st_line_id, company_currency_id,
st_line_number, context=context
)

View File

@@ -32,7 +32,7 @@ class AccountAccount(orm.Model):
'account_id',
string='Credit Lines',
readonly=True),
}
}
def copy_data(self, cr, uid, id, default=None, context=None):
if default is None:

View File

@@ -25,14 +25,17 @@ class ResCompany(orm.Model):
"""Add credit control parameters"""
_inherit = 'res.company'
_columns = {'credit_control_tolerance': fields.float('Credit Control Tolerance'),
# This is not a property on the partner because we cannot search
# on fields.property (subclass fields.function).
'credit_policy_id': fields.many2one('credit.control.policy',
'Credit Control Policy',
help=("The Credit Control Policy used on partners"
" by default. This setting can be overridden"
" on partners or invoices.")),
}
_columns = {
'credit_control_tolerance': fields.float('Credit Control Tolerance'),
# This is not a property on the partner because we cannot search
# on fields.property (subclass fields.function).
'credit_policy_id': fields.many2one(
'credit.control.policy',
'Credit Control Policy',
help=("The Credit Control Policy used on partners"
" by default. This setting can be overridden"
" on partners or invoices.")
),
}
_defaults = {"credit_control_tolerance": 0.1}

View File

@@ -27,21 +27,22 @@ class AccountInvoice(orm.Model):
_inherit = 'account.invoice'
_columns = {
'credit_policy_id':
fields.many2one('credit.control.policy',
'Credit Control Policy',
help=("The Credit Control Policy used for this "
"invoice. If nothing is defined, it will "
"use the account setting or the partner "
"setting."),
readonly=True,
),
'credit_control_line_ids':
fields.one2many('credit.control.line',
'invoice_id',
string='Credit Lines',
readonly=True),
}
'credit_policy_id': fields.many2one(
'credit.control.policy',
'Credit Control Policy',
help=("The Credit Control Policy used for this "
"invoice. If nothing is defined, it will "
"use the account setting or the partner "
"setting."),
readonly=True,
),
'credit_control_line_ids': fields.one2many(
'credit.control.line',
'invoice_id',
string='Credit Lines',
readonly=True
),
}
def copy_data(self, cr, uid, id, default=None, context=None):
"""Ensure that credit lines and policy are not copied"""
@@ -62,15 +63,17 @@ class AccountInvoice(orm.Model):
cc_nondraft_line_ids = cc_line_obj.search(
cr, uid,
[('invoice_id', '=', invoice_id),
('state', '<>', 'draft')],
('state', '!=', 'draft')],
context=context)
if cc_nondraft_line_ids:
raise orm.except_orm(_('Error!'),
_('You cannot cancel this invoice.\n'
'A payment reminder has already been '
'sent to the customer.\n'
'You must create a credit note and '
'issue a new invoice.'))
raise orm.except_orm(
_('Error!'),
_('You cannot cancel this invoice.\n'
'A payment reminder has already been '
'sent to the customer.\n'
'You must create a credit note and '
'issue a new invoice.')
)
cc_draft_line_ids = cc_line_obj.search(
cr, uid,
[('invoice_id', '=', invoice_id),

View File

@@ -21,7 +21,6 @@
import logging
from openerp.osv import orm, fields
from openerp.osv import osv
from openerp.tools.translate import _
logger = logging.getLogger('credit.line.control')
@@ -40,88 +39,152 @@ class CreditControlLine(orm.Model):
_rec_name = "id"
_order = "date DESC"
_columns = {
'date': fields.date('Controlling date',
required=True,
select=True),
'date': fields.date(
'Controlling date',
required=True,
select=True
),
# 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',
required=True,
readonly=True,
states={'draft': [('readonly', False)]}),
'date_due': fields.date(
'Due date',
required=True,
readonly=True,
states={'draft': [('readonly', False)]}
),
'date_entry': fields.related('move_line_id', 'date', type='date',
string='Entry date',
store=True, readonly=True),
'date_entry': fields.related(
'move_line_id', 'date',
type='date',
string='Entry date',
store=True, readonly=True
),
'date_sent': fields.date('Sent date',
readonly=True,
states={'draft': [('readonly', False)]}),
'date_sent': fields.date(
'Sent date',
readonly=True,
states={'draft': [('readonly', False)]}
),
'state': fields.selection([('draft', 'Draft'),
('ignored', 'Ignored'),
('to_be_sent', 'Ready To Send'),
('sent', 'Done'),
('error', 'Error'),
('email_error', 'Emailing Error')],
'State', required=True, readonly=True,
help=("Draft lines need to be triaged.\n"
"Ignored lines are lines for which we do "
"not want to send something.\n"
"Draft and ignored lines will be "
"generated again on the next run.")),
'state': fields.selection(
[('draft', 'Draft'),
('ignored', 'Ignored'),
('to_be_sent', 'Ready To Send'),
('sent', 'Done'),
('error', 'Error'),
('email_error', 'Emailing Error')],
'State', required=True, readonly=True,
help=("Draft lines need to be triaged.\n"
"Ignored lines are lines for which we do "
"not want to send something.\n"
"Draft and ignored lines will be "
"generated again on the next run.")
),
'channel': fields.selection([('letter', 'Letter'),
('email', 'Email')],
'Channel', required=True,
readonly=True,
states={'draft': [('readonly', False)]}),
'channel': fields.selection(
[('letter', 'Letter'),
('email', 'Email')],
'Channel', required=True,
readonly=True,
states={'draft': [('readonly', False)]}
),
'invoice_id': fields.many2one('account.invoice', 'Invoice', readonly=True),
'partner_id': fields.many2one('res.partner', "Partner", required=True),
'amount_due': fields.float('Due Amount Tax incl.', required=True, readonly=True),
'balance_due': fields.float('Due balance', required=True, readonly=True),
'mail_message_id': fields.many2one('mail.mail', 'Sent Email', readonly=True),
'invoice_id': fields.many2one(
'account.invoice',
'Invoice',
readonly=True
),
'move_line_id': fields.many2one('account.move.line', 'Move line',
required=True, readonly=True),
'partner_id': fields.many2one(
'res.partner',
"Partner",
required=True
),
'account_id': fields.related('move_line_id', 'account_id', type='many2one',
relation='account.account', string='Account',
store=True, readonly=True),
'amount_due': fields.float(
'Due Amount Tax incl.',
required=True,
readonly=True
),
'currency_id': fields.related('move_line_id', 'currency_id', type='many2one',
relation='res.currency', string='Currency',
store=True, readonly=True),
'balance_due': fields.float(
'Due balance', required=True,
readonly=True
),
'company_id': fields.related('move_line_id', 'company_id', type='many2one',
relation='res.company', string='Company',
store=True, readonly=True),
'mail_message_id': fields.many2one(
'mail.mail',
'Sent Email',
readonly=True
),
'move_line_id': fields.many2one(
'account.move.line',
'Move line',
required=True,
readonly=True
),
'account_id': fields.related(
'move_line_id',
'account_id',
type='many2one',
relation='account.account',
string='Account',
store=True,
readonly=True
),
'currency_id': fields.related(
'move_line_id',
'currency_id',
type='many2one',
relation='res.currency',
string='Currency',
store=True,
readonly=True
),
'company_id': fields.related(
'move_line_id', 'company_id',
type='many2one',
relation='res.company',
string='Company',
store=True, readonly=True
),
# we can allow a manual change of policy in draft state
'policy_level_id':fields.many2one('credit.control.policy.level',
'Overdue Level', required=True, readonly=True,
states={'draft': [('readonly', False)]}),
'policy_level_id': fields.many2one(
'credit.control.policy.level',
'Overdue Level',
required=True,
readonly=True,
states={'draft': [('readonly', False)]}
),
'policy_id': fields.related('policy_level_id',
'policy_id',
type='many2one',
relation='credit.control.policy',
string='Policy',
store=True,
readonly=True),
'policy_id': fields.related(
'policy_level_id',
'policy_id',
type='many2one',
relation='credit.control.policy',
string='Policy',
store=True,
readonly=True
),
'level': fields.related(
'policy_level_id',
'level',
type='integer',
relation='credit.control.policy',
string='Level',
store=True,
readonly=True
),
'level': fields.related('policy_level_id',
'level',
type='integer',
relation='credit.control.policy',
string='Level',
store=True,
readonly=True),
'manually_overridden': fields.boolean('Manually overridden')
}
_defaults = {'state': 'draft'}
def _prepare_from_move_line(self, cr, uid, move_line,

View File

@@ -20,9 +20,12 @@
##############################################################################
from openerp.osv import orm, fields
class Mail(orm.Model):
_inherit = 'mail.mail'
# use HTML fields instead of text
_columns = {'body_html': fields.html('Rich-text Contents',
help="Rich-text/HTML message"),}
_columns = {
'body_html': fields.html('Rich-text Contents',
help="Rich-text/HTML message"),
}

View File

@@ -19,7 +19,6 @@
#
##############################################################################
from openerp.osv import orm, fields
from openerp.tools.translate import _
class ResPartner(orm.Model):

View File

@@ -27,26 +27,37 @@ class CreditControlPolicy(orm.Model):
_name = "credit.control.policy"
_description = """Define a reminder policy"""
_columns = {'name': fields.char('Name', required=True, size=128),
_columns = {
'name': fields.char(
'Name',
required=True,
size=128
),
'level_ids': fields.one2many('credit.control.policy.level',
'policy_id',
'Policy Levels'),
'level_ids': fields.one2many(
'credit.control.policy.level',
'policy_id',
'Policy Levels'
),
'do_nothing': fields.boolean('Do nothing',
help='For policies which should not '
'generate lines or are obsolete'),
'do_nothing': fields.boolean(
'Do nothing',
help='For policies which should not '
'generate lines or are obsolete'
),
'company_id': fields.many2one('res.company', 'Company'),
'company_id': fields.many2one('res.company', 'Company'),
'account_ids': fields.many2many('account.account',
string='Accounts',
required=True,
domain="[('type', '=', 'receivable')]",
help="This policy will be active only"
" for the selected accounts"),
'active': fields.boolean('Active'),
}
'account_ids': fields.many2many(
'account.account',
string='Accounts',
required=True,
domain="[('type', '=', 'receivable')]",
help="This policy will be active only"
" for the selected accounts"
),
'active': fields.boolean('Active'),
}
_defaults = {
'active': True,

View File

@@ -22,16 +22,19 @@ import time
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)
self.localcontext.update({
'time': time,
'cr':cr,
'cr': cr,
'uid': uid,
})
report_sxw.report_sxw('report.credit_control_summary',
'credit.control.communication',
'addons/account_credit_control/report/credit_control_summary.html.mako',
parser=CreditSummaryReport)
report_sxw.report_sxw(
'report.credit_control_summary',
'credit.control.communication',
'addons/account_credit_control/report/credit_control_summary.html.mako',
parser=CreditSummaryReport
)

View File

@@ -34,13 +34,14 @@ class CreditControlRun(orm.Model):
_description = """Credit control line generator"""
_columns = {
'date': fields.date('Controlling Date', required=True),
'policy_ids':
fields.many2many('credit.control.policy',
rel="credit_run_policy_rel",
id1='run_id', id2='policy_id',
string='Policies',
readonly=True,
states={'draft': [('readonly', False)]}),
'policy_ids': fields.many2many(
'credit.control.policy',
rel="credit_run_policy_rel",
id1='run_id', id2='policy_id',
string='Policies',
readonly=True,
states={'draft': [('readonly', False)]}
),
'report': fields.text('Report', readonly=True),
@@ -50,16 +51,17 @@ class CreditControlRun(orm.Model):
required=True,
readonly=True),
'manual_ids':
fields.many2many('account.move.line',
rel="credit_runreject_rel",
string='Lines to handle manually',
help=('If a credit control line has been generated'
'on a policy and the policy has been changed '
'in the meantime, it has to be handled '
'manually'),
readonly=True),
}
'manual_ids': fields.many2many(
'account.move.line',
rel="credit_runreject_rel",
string='Lines to handle manually',
help=('If a credit control line has been generated'
'on a policy and the policy has been changed '
'in the meantime, it has to be handled '
'manually'),
readonly=True
),
}
def copy_data(self, cr, uid, id, default=None, context=None):
if default is None:
@@ -138,11 +140,11 @@ class CreditControlRun(orm.Model):
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))
(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)
policy.name)
vals = {'state': 'done',
'report': report,
@@ -159,8 +161,8 @@ class CreditControlRun(orm.Model):
try:
cr.execute('SELECT id FROM credit_control_run'
' LIMIT 1 FOR UPDATE NOWAIT')
except Exception as exc:
# in case of exception openerp will do a rollback for us and free the lock
except Exception:
# 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

@@ -37,17 +37,19 @@ class CreditCommunication(TransientModel):
'Level', required=True),
'credit_control_line_ids': fields.many2many('credit.control.line',
rel='comm_credit_rel',
string='Credit Lines'),
rel='comm_credit_rel',
string='Credit Lines'),
'company_id': fields.many2one('res.company', 'Company',
required=True),
'user_id': fields.many2one('res.users', 'User')}
_defaults = {'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(
cr, uid, 'credit.control.policy', context=c),
'user_id': lambda s, cr, uid, c: uid}
_defaults = {
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(
cr, uid, 'credit.control.policy', context=c),
'user_id': lambda s, cr, uid, c: uid
}
def get_email(self, cr, uid, com_id, context=None):
"""Return a valid email for customer"""
@@ -97,11 +99,16 @@ class CreditCommunication(TransientModel):
res = cr.dictfetchall()
for level_assoc in res:
data = {}
data['credit_control_line_ids'] = \
[(6, 0, self._get_credit_lines(cr, uid, line_ids,
level_assoc['partner_id'],
level_assoc['policy_level_id'],
context=context))]
data['credit_control_line_ids'] = [
(
6, 0, self._get_credit_lines(
cr, uid, line_ids,
level_assoc['partner_id'],
level_assoc['policy_level_id'],
context=context
)
)
]
data['partner_id'] = level_assoc['partner_id']
data['current_policy_level'] = level_assoc['policy_level_id']
comm_id = self.create(cr, uid, data, context=context)
@@ -144,10 +151,11 @@ class CreditCommunication(TransientModel):
state = 'email_error'
cr_line_obj.write(
cr, uid, cl_ids,
{'mail_message_id': email_id,
'state': state},
context=context)
cr, uid, cl_ids,
{'mail_message_id': email_id,
'state': state},
context=context
)
att_ids = []
for att in email_values.get('attachments', []):
attach_fname = att[0]

View File

@@ -37,9 +37,10 @@ class CreditControlEmailer(orm.TransientModel):
if (context.get('active_model') == 'credit.control.line' and
context.get('active_ids')):
res = self._filter_line_ids(
cr, uid,
context['active_ids'],
context=context)
cr, uid,
context['active_ids'],
context=context
)
return res
_columns = {
@@ -63,7 +64,7 @@ class CreditControlEmailer(orm.TransientModel):
def email_lines(self, cr, uid, wiz_id, context=None):
assert not (isinstance(wiz_id, list) and len(wiz_id) > 1), \
"wiz_id: only one id expected"
"wiz_id: only one id expected"
comm_obj = self.pool.get('credit.control.communication')
if isinstance(wiz_id, list):
wiz_id = wiz_id[0]
@@ -74,9 +75,10 @@ class CreditControlEmailer(orm.TransientModel):
line_ids = [l.id for l in form.line_ids]
filtered_ids = self._filter_line_ids(
cr, uid, line_ids, context)
cr, uid, line_ids, context
)
comms = comm_obj._generate_comm_from_credit_line_ids(
cr, uid, filtered_ids, context=context)
cr, uid, filtered_ids, context=context
)
comm_obj._generate_emails(cr, uid, comms, context=context)
return {}

View File

@@ -35,16 +35,18 @@ class CreditControlMarker(orm.TransientModel):
if (context.get('active_model') == 'credit.control.line' and
context.get('active_ids')):
res = self._filter_line_ids(
cr, uid,
context['active_ids'],
context=context)
cr, uid,
context['active_ids'],
context=context
)
return res
_columns = {
'name': fields.selection([('ignored', 'Ignored'),
('to_be_sent', 'Ready To Send'),
('sent', 'Done')],
'Mark as', required=True),
'Mark as',
required=True),
'line_ids': fields.many2many(
'credit.control.line',
string='Credit Control Lines',
@@ -74,7 +76,7 @@ class CreditControlMarker(orm.TransientModel):
"""Write state of selected credit lines to the one in entry
done credit line will be ignored"""
assert not (isinstance(wiz_id, list) and len(wiz_id) > 1), \
"wiz_id: only one id expected"
"wiz_id: only one id expected"
if isinstance(wiz_id, list):
wiz_id = wiz_id[0]
form = self.browse(cr, uid, wiz_id, context)
@@ -86,15 +88,16 @@ class CreditControlMarker(orm.TransientModel):
filtered_ids = self._filter_line_ids(cr, uid, line_ids, context)
if not filtered_ids:
raise except_osv(_('Information'),
_('No lines will be changed. All the selected lines are already done.'))
raise orm.except_orm(
_('Information'),
_('No lines will be changed. All the selected lines are already done.')
)
self._mark_lines(cr, uid, filtered_ids, form.name, context)
return {'domain': unicode([('id', 'in', filtered_ids)]),
'view_type': 'form',
'view_mode': 'tree,form',
'view_id': False,
'res_model': 'credit.control.line',
'type': 'ir.actions.act_window'}
return {'domain': unicode([('id', 'in', filtered_ids)]),
'view_type': 'form',
'view_mode': 'tree,form',
'view_id': False,
'res_model': 'credit.control.line',
'type': 'ir.actions.act_window'}

View File

@@ -145,10 +145,12 @@ class credit_control_policy_changer(orm.TransientModel):
uid,
wizard.move_line_ids,
wizard.new_policy_id)
self._mark_as_overridden(cr,
uid,
wizard.move_line_ids,
context=context)
self._mark_as_overridden(
cr,
uid,
wizard.move_line_ids,
context=context
)
# As disscused with business expert
# draft lines should be passed to ignored
# if same level as the new one
@@ -173,6 +175,6 @@ class credit_control_policy_changer(orm.TransientModel):
"account_credit_control",
"credit_control_line_action")
assert view_id, 'No view found'
action = ui_act_model.read(cr, uid, view_id[1], context=context)
action = ui_act_model.read(cr, uid, view_id[1], context=context)
action['domain'] = [('id', 'in', generated_ids)]
return action

View File

@@ -67,5 +67,4 @@ Support of fees price list
'installable': False,
'auto_install': False,
'license': 'AGPL-3',
'application': False,
}
'application': False}

View File

@@ -18,7 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm, fields
from openerp.osv import orm
class credit_control_run(orm.Model):
@@ -26,7 +26,7 @@ class credit_control_run(orm.Model):
_inherit = "credit.control.run"
def _generate_credit_lines(self, cr, uid, run_id, context=None):
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,

View File

@@ -17,7 +17,5 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import account
from . import account_bank_statement
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -17,10 +17,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
##############################################################################
{
"name" : "Move in draft state by default",
"version" : "1.0",
"depends" : ["base", "account", "account_constraints"],
"author" : "Camptocamp",
"name": "Move in draft state by default",
"version": "1.0",
"depends": ["base", "account", "account_constraints"],
"author": "Camptocamp",
'license': 'AGPL-3',
"description": """
Let the generated move in draft on invoice and bank statement
@@ -48,7 +48,7 @@ need to make a refund).
""",
'website': 'http://www.camptocamp.com',
'data' : ['account_view.xml',
'data': ['account_view.xml',
'invoice_view.xml'],
'installable': False,
'active': False,

View File

@@ -17,37 +17,42 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
##############################################################################
from openerp.osv import fields, orm, osv
from openerp.osv import orm, osv
from tools.translate import _
class AccountInvoice(orm.Model):
_inherit = 'account.invoice'
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)
return res
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 """
for line in self.browse(cr, uid, ids, context=context):
if line.state == 'draft':
continue
continue
else:
if not line.journal_id.update_posted:
raise osv.except_osv(_('Error!'), _('You cannot modify a posted entry of this journal.\nFirst you should set the journal to allow cancelling entries.'))
raise osv.except_osv(
_('Error!'),
_('You cannot modify a posted entry of this journal.'
'First you should set the journal to allow cancelling entries.')
)
if ids:
cr.execute('UPDATE account_move '\
'SET state=%s '\
cr.execute('UPDATE account_move '
'SET state=%s '
'WHERE id IN %s', ('draft', tuple(ids),))
return True

View File

@@ -18,7 +18,6 @@
##############################################################################
from openerp.osv import orm
from openerp.tools.translate import _
class AccountBankStatement(orm.Model):
@@ -26,9 +25,10 @@ class AccountBankStatement(orm.Model):
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,
st_line_number, context)
move_ids = super(AccountBankStatement, self).create_move_from_st_line(
cr, uid, st_line_id, company_currency_id,
st_line_number, context
)
# If a bank statement line is already linked to a voucher
# we received boolean instead of voucher move ids in move_ids
bank_st_line_obj = self.pool.get('account.bank.statement.line')

View File

@@ -31,13 +31,21 @@
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 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.
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
that have this option, OpenERP will check that the customer has a VAT number.
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.
If it doesn't, OpenERP will block the validation of the invoice and display an error message.
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.
In the European Union (EU), when an EU company sends an invoice to another EU company in another country,
Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com> for any help or question about this module.
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.
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',
@@ -45,11 +53,11 @@ Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com> for
'data': [
'account_fiscal_position_view.xml',
'partner_view.xml',
],
],
'images': [
'images/fiscal_position_form.jpg',
'images/vat_check_invoice_validation.jpg',
],
],
'installable': False,
'active': False,
'application': True,

View File

@@ -30,7 +30,9 @@ 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 when the user validates a customer invoice/refund."),
help="If enabled, OpenERP will check that the customer has a VAT number "
"when the user validates a customer invoice/refund."
),
}
@@ -51,7 +53,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' that require the customer to have a VAT number. 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.")
_("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."
"Please add the VAT number of this Customer in OpenERP "
" and try to validate again.")
% (type_label, invoice.fiscal_position.name,
invoice.partner_id.name))
return super(account_invoice, self).action_move_create(

View File

@@ -34,7 +34,14 @@ class res_partner(orm.Model):
fp = self.pool['account.fiscal.position'].read(
cr, uid, account_position, ['customer_must_have_vat', 'name'])
if fp['customer_must_have_vat']:
return {'warning': {
'title': _('Missing VAT number:'),
'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.") % fp['name']}}
return {
'warning': {
'title': _('Missing VAT number:'),
'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."
) % fp['name']
}
}
return True

View File

@@ -23,7 +23,6 @@
from openerp.osv import fields
from openerp.osv import orm
from openerp.tools.translate import _
import openerp.addons.decimal_precision as dp
@@ -36,19 +35,15 @@ class account_invoice(orm.Model):
def _get_invoice_line2(self, cr, uid, ids, context=None):
result = {}
for line in self.pool.get('account.invoice.line').browse(cr,
uid,
ids,
context=context):
for line in self.pool.get('account.invoice.line').browse(
cr, uid, ids, context=context):
result[line.invoice_id.id] = True
return result.keys()
def _get_invoice_tax2(self, cr, uid, ids, context=None):
result = {}
for tax in self.pool.get('account.invoice.tax').browse(cr,
uid,
ids,
context=context):
for tax in self.pool.get('account.invoice.tax').browse(
cr, uid, ids, context=context):
result[tax.invoice_id.id] = True
return result.keys()
@@ -68,21 +63,21 @@ 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
# Accounts to compute amount_untaxed
line_account = []
for line in invoice.invoice_line:
if line.account_id.id not in line_account:
line_account.append(line.account_id.id)
## Accounts to compute amount_tax
# 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:
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
@@ -91,16 +86,18 @@ class account_invoice(orm.Model):
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_total'] = (res[invoice.id]['cc_amount_tax'] +
res[invoice.id]['cc_amount_untaxed'])
return res
_columns = {
'cc_amount_untaxed': fields.function(_cc_amount_all,
method=True,
digits_compute=dp.get_precision('Account'),
'cc_amount_untaxed': fields.function(
_cc_amount_all,
method=True,
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).",
help="Invoice untaxed amount in the 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',
@@ -109,14 +106,17 @@ class account_invoice(orm.Model):
'account.invoice.line': (_get_invoice_line2, ['price_unit',
'invoice_line_tax_id',
'quantity', 'discount'], 20),
},
multi='cc_all'),
'cc_amount_tax': fields.function(_cc_amount_all,
method=True,
digits_compute=dp.get_precision('Account'),
},
multi='cc_all'
),
'cc_amount_tax': fields.function(
_cc_amount_all,
method=True,
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).",
help="Invoice tax amount in the 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',
@@ -125,13 +125,16 @@ class account_invoice(orm.Model):
'account.invoice.line': (_get_invoice_line2, ['price_unit',
'invoice_line_tax_id',
'quantity', 'discount'], 20),
},
multi='cc_all'),
'cc_amount_total': fields.function(_cc_amount_all,
method=True,
digits_compute=dp.get_precision('Account'),
},
multi='cc_all'
),
'cc_amount_total': fields.function(
_cc_amount_all,
method=True,
digits_compute=dp.get_precision('Account'),
string='Company Cur. Total',
help="Invoice total amount in the company currency "\
help="Invoice total amount in the company currency "
"(useful when invoice currency is different from company currency).",
store={
'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line',
@@ -141,8 +144,6 @@ class account_invoice(orm.Model):
'account.invoice.line': (_get_invoice_line2, ['price_unit',
'invoice_line_tax_id',
'quantity', 'discount'], 20),
},
},
multi='cc_all'),
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -36,7 +36,7 @@ class account_journal(orm.Model):
_defaults = {
'allow_date': True,
}
}
def _allow_date_always_active(self, cr, uid, ids):
for journal in self.browse(cr, uid, ids):
@@ -44,8 +44,9 @@ class account_journal(orm.Model):
raise orm.except_orm(
_('Error:'),
_("The option 'Check Date in Period' must be active "
"on journal '%s'.")
% journal.name)
"on journal '%s'.")
% journal.name
)
return True
_constraints = [

View File

@@ -19,9 +19,9 @@
#
##############################################################################
{
"name" : "Move line search view - disable defaults for period and journal",
"version" : "0.1",
"author" : "Therp BV",
"name": "Move line search view - disable defaults for period and journal",
"version": "0.1",
"author": "Therp BV",
"category": 'Accounting & Finance',
'description': """
OpenERP 7.0 implements a custom javascript search view for move lines. This
@@ -36,13 +36,7 @@ have to disable these before entering your own search queries.
""",
'website': 'http://therp.nl',
'depends' : [
'account',
],
'js': [
'static/src/js/move_line_search_view.js',
],
'images': [
'static/src/img/move_line_search_view.png',
],
'depends': ['account'],
'js': ['static/src/js/move_line_search_view.js'],
'images': ['static/src/img/move_line_search_view.png'],
}

View File

@@ -19,4 +19,3 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

View File

@@ -25,7 +25,7 @@
'version': '0.1',
'license': 'AGPL-3',
'author': 'Noviat',
'category' : 'Generic Modules',
'category': 'Generic Modules',
'description': """
Journal Items Search Extension
==============================
@@ -37,16 +37,16 @@ These fields can be used in combination with the Search window.
""",
'depends': ['account'],
'data' : [
'data': [
'account_view.xml',
],
'js': [
'static/src/js/account_move_line_search_extension.js',
],
'qweb' : [
'qweb': [
'static/src/xml/account_move_line_search_extension.xml',
],
'css':[
'css': [
'static/src/css/account_move_line_search_extension.css',
],
'installable': False,

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
##############################################################################
#
#
# Copyright (C) 2011 Agile Business Group sagl (<http://www.agilebg.com>)
# Copyright (C) 2011 Domsense srl (<http://www.domsense.com>)
#

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
##############################################################################
#
#
# Copyright (C) 2011 Agile Business Group sagl (<http://www.agilebg.com>)
# Copyright (C) 2011 Domsense srl (<http://www.domsense.com>)
#
@@ -27,21 +27,23 @@
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 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',
'website': 'http://www.agilebg.com',
'license': 'AGPL-3',
'depends' : ['account_accountant', 'analytic'],
'data' : [
'depends': ['account_accountant', 'analytic'],
'data': [
'move_template.xml',
'wizard/select_template.xml',
'security/ir.model.access.csv',
],
],
'test': [
'test/generate_move.yml',
],
],
'active': False,
'installable': False,
}

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
##############################################################################
#
#
# Copyright (C) 2011 Agile Business Group sagl (<http://www.agilebg.com>)
# Copyright (C) 2011 Domsense srl (<http://www.domsense.com>)
#
@@ -23,6 +23,7 @@ from openerp.osv import fields, orm
from openerp.tools.translate import _
import re
class account_document_template(orm.Model):
_computed_lines = {}
@@ -33,7 +34,7 @@ class account_document_template(orm.Model):
_columns = {
'name': fields.char('Name', size=64, required=True),
}
}
def _input_lines(self, cr, uid, template):
count = 0
@@ -58,13 +59,16 @@ class account_document_template(orm.Model):
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)
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))
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'))
except KeyError:
raise orm.except_orm(_('Error'),
raise orm.except_orm(
_('Error'),
_('Code "%s" refers to non existing line') % line.python_code)
return self._computed_lines[line_number]
@@ -73,8 +77,10 @@ class account_document_template(orm.Model):
# 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)
raise orm.except_orm(
_('Error'),
_('Inconsistency between input lines and filled lines for template %s') % template.name
)
self._current_template_id = template.id
self._cr = cr
self._uid = uid
@@ -92,6 +98,7 @@ class account_document_template(orm.Model):
return True
return False
class account_document_template_line(orm.Model):
_name = 'account.document.template.line'
@@ -99,6 +106,6 @@ 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),
'python_code':fields.text('Python Code'),
}
'type': fields.selection([('computed', 'Computed'), ('input', 'User input')], 'Type', required=True),
'python_code': fields.text('Python Code'),
}

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
##############################################################################
#
#
# Copyright (C) 2011 Agile Business Group sagl (<http://www.agilebg.com>)
# Copyright (C) 2011 Domsense srl (<http://www.domsense.com>)
#
@@ -20,7 +20,7 @@
##############################################################################
from openerp.osv import fields, orm
from openerp.tools.translate import _
class account_move_template(orm.Model):
@@ -32,50 +32,58 @@ class account_move_template(orm.Model):
'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),
}
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(
cr, uid, 'account.move.template', context=c
),
}
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
journal_ids=[]
all_journal_ids=[]
move_template=self.pool.get('account.move.template').browse(cr,uid,ids)[0]
# 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]
if not move_template.template_line_ids:
return True
for template_line in move_template.template_line_ids:
all_journal_ids.append(template_line.journal_id.id)
if template_line.journal_id.id not in journal_ids :
if template_line.journal_id.id not in journal_ids:
journal_ids.append(template_line.journal_id.id)
if move_template.cross_journals:
return len(all_journal_ids)==len(journal_ids)
return len(all_journal_ids) == len(journal_ids)
else:
return len(journal_ids)==1
return len(journal_ids) == 1
_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!',
['journal_id'])
]
_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!',
['journal_id'])]
class account_move_template_line(orm.Model):
_name = 'account.move.template.line'
_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"),
'move_line_type':fields.selection([
('cr','Credit'),
('dr','Debit'),
], 'Move Line Type', 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"),
'template_id': fields.many2one('account.move.template', 'Template'),
'account_tax_id':fields.many2one('account.tax', 'Tax'),
}
'account_tax_id': fields.many2one('account.tax', 'Tax'),
}
_sql_constraints = [
('sequence_template_uniq', 'unique (template_id,sequence)', 'The sequence of the line must be unique per template !')
('sequence_template_uniq', 'unique (template_id,sequence)',
'The sequence of the line must be unique per template !')
]

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
##############################################################################
#
#
# Copyright (C) 2011 Agile Business Group sagl (<http://www.agilebg.com>)
# Copyright (C) 2011 Domsense srl (<http://www.domsense.com>)
#

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
##############################################################################
#
#
# Copyright (C) 2011 Agile Business Group sagl (<http://www.agilebg.com>)
# Copyright (C) 2011 Domsense srl (<http://www.domsense.com>)
#
@@ -19,20 +19,34 @@
#
##############################################################################
from openerp.osv import fields,orm
from openerp.osv import fields, orm
import time
from openerp.tools.translate import _
class wizard_select_template(orm.TransientModel):
_name = "wizard.select.move.template"
_columns = {
'template_id': fields.many2one('account.move.template', 'Move Template', required=True),
'template_id': fields.many2one(
'account.move.template',
'Move Template',
required=True
),
'partner_id': fields.many2one('res.partner', 'Partner'),
'line_ids': fields.one2many('wizard.select.move.template.line', 'template_id', 'Lines'),
'state': fields.selection([
('template_selected','Template selected'),
], 'State'),
'line_ids': fields.one2many(
'wizard.select.move.template.line',
'template_id',
'Lines'
),
'state': fields.selection(
[
('template_selected', 'Template selected'),
],
'State'
),
}
def on_change_template_id(self, cr, uid, ids, template_id):
@@ -48,11 +62,11 @@ class wizard_select_template(orm.TransientModel):
'name': line.name,
'account_id': line.account_id.id,
'move_line_type': line.move_line_type,
})
})
return res
def load_lines(self, cr, uid, ids, context=None):
wizard = self.browse(cr, uid, ids, context=context)[0]
wizard = self.browse(cr, uid, ids, context=context)[0]
template_pool = self.pool.get('account.move.template')
wizard_line_pool = self.pool.get('wizard.select.move.template.line')
model_data_obj = self.pool.get('ir.model.data')
@@ -60,14 +74,14 @@ class wizard_select_template(orm.TransientModel):
template = template_pool.browse(cr, uid, wizard.template_id.id)
for line in template.template_line_ids:
if line.type == 'input':
wizard_line_pool.create(cr, uid,{
wizard_line_pool.create(cr, uid, {
'template_id': wizard.id,
'sequence': line.sequence,
'name': line.name,
'amount': 0.0,
'account_id': line.account_id.id,
'move_line_type': line.move_line_type,
})
})
if not wizard.line_ids:
return self.load_template(cr, uid, ids)
wizard.write({'state': 'template_selected'})
@@ -76,24 +90,21 @@ class wizard_select_template(orm.TransientModel):
view_id = view_rec and view_rec[1] or False
return {
'view_type': 'form',
'view_id' : [view_id],
'view_mode': 'form',
'res_model': 'wizard.select.move.template',
'res_id': wizard.id,
'type': 'ir.actions.act_window',
'target': 'new',
'context': context,
'view_type': 'form',
'view_id': [view_id],
'view_mode': 'form',
'res_model': 'wizard.select.move.template',
'res_id': wizard.id,
'type': 'ir.actions.act_window',
'target': 'new',
'context': context,
}
def load_template(self, cr, uid, ids, context=None):
template_obj = self.pool.get('account.move.template')
template_line_obj = self.pool.get('account.move.template.line')
account_period_obj = self.pool.get('account.period')
mod_obj = self.pool.get('ir.model.data')
wizard = self.browse(cr, uid, ids, context=context)[0]
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!'))
input_lines = {}
@@ -108,21 +119,40 @@ class wizard_select_template(orm.TransientModel):
computed_lines = template_obj.compute_lines(cr, uid, wizard.template_id.id, input_lines)
moves={}
moves = {}
for line in wizard.template_id.template_line_ids:
if line.journal_id.id not in moves:
moves[line.journal_id.id]=self._make_move(
cr,uid,wizard.template_id.name,period_id,line.journal_id.id, wizard.partner_id.id)
id_line=self._make_move_line(cr,uid,line,computed_lines,moves[line.journal_id.id],period_id,
wizard.partner_id.id)
if wizard.template_id.cross_journals :
trans_account_id=wizard.template_id.transitory_acc_id.id
id_trans_line=self._make_transitory_move_line(cr,uid,line,computed_lines,
moves[line.journal_id.id],period_id,trans_account_id, wizard.partner_id.id)
moves[line.journal_id.id] = self._make_move(
cr, uid,
wizard.template_id.name,
period_id,
line.journal_id.id,
wizard.partner_id.id
)
self._make_move_line(
cr, uid,
line,
computed_lines,
moves[line.journal_id.id],
period_id,
wizard.partner_id.id
)
if wizard.template_id.cross_journals:
trans_account_id = wizard.template_id.transitory_acc_id.id
self._make_transitory_move_line(
cr,
uid,
line,
computed_lines,
moves[line.journal_id.id],
period_id,
trans_account_id,
wizard.partner_id.id
)
return {
'domain': "[('id','in', "+str(moves.values())+")]",
'domain': "[('id','in', " + str(moves.values()) + ")]",
'name': 'Entries',
'view_type': 'form',
'view_mode': 'tree,form',
@@ -130,8 +160,8 @@ class wizard_select_template(orm.TransientModel):
'type': 'ir.actions.act_window',
'target': 'current',
}
#'res_id': moves.values() or False,
def _make_move(self, cr, uid,ref,period_id,journal_id, partner_id):
def _make_move(self, cr, uid, ref, period_id, journal_id, partner_id):
account_move_obj = self.pool.get('account.move')
move_id = account_move_obj.create(cr, uid, {
'ref': ref,
@@ -140,15 +170,18 @@ class wizard_select_template(orm.TransientModel):
'partner_id': partner_id,
})
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 define an analytic journal on the '%s' journal!")
% (line.journal_id.name,))
raise orm.except_orm(
_('No Analytic Journal !'),
_("You have to dfine an analytic journal on the '%s' journal!")
% (line.journal_id.name,)
)
analytic_account_id = line.analytic_account_id.id
val = {
'name': line.name,
@@ -169,16 +202,19 @@ class wizard_select_template(orm.TransientModel):
val['debit'] = computed_lines[line.sequence]
id_line = account_move_line_obj.create(cr, uid, val)
return id_line
def _make_transitory_move_line(
self,cr,uid,line,computed_lines,move_id,period_id,trans_account_id, partner_id):
def _make_transitory_move_line(self, cr, uid, line,
computed_lines, move_id, period_id,
trans_account_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 !'),
raise orm.except_orm(
_('No Analytic Journal !'),
_("You have to define an analytic journal on the '%s' journal!")
% (wizard.template_id.journal_id.name,))
% (line.template_id.journal_id.name,)
)
analytic_account_id = line.analytic_account_id.id
val = {
'name': 'transitory',
@@ -197,6 +233,7 @@ class wizard_select_template(orm.TransientModel):
id_line = account_move_line_obj.create(cr, uid, val)
return id_line
class wizard_select_template_line(orm.TransientModel):
_description = 'Template Lines'
_name = "wizard.select.move.template.line"
@@ -205,9 +242,12 @@ class wizard_select_template_line(orm.TransientModel):
'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),
'move_line_type':fields.selection([
('cr','Credit'),
('dr','Debit'),
], 'Move Line Type', required=True,readonly=True),
'move_line_type': fields.selection(
[('cr', 'Credit'),
('dr', 'Debit')],
'Move Line Type',
required=True,
readonly=True
),
'amount': fields.float('Amount', required=True),
}

View File

@@ -19,4 +19,3 @@
##############################################################################
from . import wizard
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -17,10 +17,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
##############################################################################
{
"name" : "Wizard to validate multiple moves",
"version" : "1.0",
"depends" : ["base", "account", "account_constraints"],
"author" : "Camptocamp",
"name": "Wizard to validate multiple moves",
"version": "1.0",
"depends": ["base", "account", "account_constraints"],
"author": "Camptocamp",
'license': 'AGPL-3',
"description": """
Re-defining a base wizard (validate all moves in a period for a journal),
@@ -32,4 +32,3 @@ base one defined in addons/account/wizard.
'installable': False,
'active': False,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -19,4 +19,3 @@
##############################################################################
from . import account_validate_move
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -49,12 +49,12 @@ class ValidateAccountMove(orm.TransientModel):
ids_move = obj_move.search(cr, uid, [('state', '=', 'draft'),
('journal_id', 'in', journal_ids),
('period_id', '=', period_ids)],
context=context)
context=context)
if not ids_move:
raise osv.except_osv(_('Warning!'),
('Specified journal does not have any account move entries in draft state for this period.'))
raise osv.except_osv(
_('Warning!'),
_('Specified journal does not have any account move entries '
'in draft state for this period.')
)
obj_move.button_validate(cr, uid, ids_move, context=context)
return {'type': 'ir.actions.act_window_close'}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -28,11 +28,11 @@ class account_account_type(orm.Model):
_inherit = "account.account.type"
_columns = {
'partner_policy': fields.selection([
('optional', 'Optional'),
('always', 'Always'),
('never', 'Never')
], 'Policy for partner field',
'partner_policy': fields.selection(
[('optional', 'Optional'),
('always', 'Always'),
('never', 'Never')],
'Policy for partner field',
help="Set the policy for the partner field : if you select "
"'Optional', the accountant is free to put a partner "
"on an account move line with this type of account ; "

View File

@@ -19,7 +19,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
"""
Account renumber wizard
"""

View File

@@ -29,7 +29,7 @@
'author': "Pexego",
'website': "http://www.pexego.es",
'category': "Enterprise Specific Modules",
'contributors' : ['Pedro M. Baeza', 'Joaquín Gutierrez'],
'contributors': ['Pedro M. Baeza', 'Joaquín Gutierrez'],
'description': """
This module adds a wizard to renumber account moves by date only for admin users.
=================================================================================
@@ -43,11 +43,11 @@ It will recreate the sequence number of each account move using their journal se
- Sequences per journal are supported.
- Sequences with prefixes and sufixes based on the move date are also supported.
""",
"license" : "AGPL-3",
"depends" : [
"license": "AGPL-3",
"depends": [
'account',
],
"demo" : [],
"demo": [],
"data": [
'wizard/wizard_renumber_view.xml',
],

View File

@@ -27,9 +27,7 @@ that can be used later for testing the renumber wizard.
__author__ = "Borja López Soilán (Pexego)"
import sys
import re
import xmlrpclib
import socket
import logging
logger = logging.getLogger("create_lots_of_account_moves")

View File

@@ -19,11 +19,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
"""
Account renumber wizard
"""
import wizard_renumber

View File

@@ -24,6 +24,7 @@ from openerp.tools.translate import _
from openerp import SUPERUSER_ID
import logging
class wizard_renumber(orm.TransientModel):
_name = "wizard.renumber"
_description = "Account renumber wizard"
@@ -41,7 +42,7 @@ 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)
}
@@ -94,28 +95,30 @@ class wizard_renumber(orm.TransientModel):
[('journal_id', 'in', journal_ids),
('period_id', '=', period),
('state', '=', 'posted')],
limit=0, order='date,id',
context=context)
limit=0, order='date,id',
context=context)
if not move_ids:
continue
logger.debug("Renumbering %d account moves." % len(move_ids))
for move in move_obj.browse(cr, uid, move_ids, context=context):
sequence_id = self.get_sequence_id_for_fiscalyear_id(
cr, uid,
sequence_id=move.journal_id.sequence_id.id,
fiscalyear_id=move.period_id.fiscalyear_id.id)
if not sequence_id in sequences_seen:
cr, uid,
sequence_id=move.journal_id.sequence_id.id,
fiscalyear_id=move.period_id.fiscalyear_id.id
)
if sequence_id not in sequences_seen:
sequence_obj.write(cr, SUPERUSER_ID, [sequence_id],
{'number_next': number_next})
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
#"You can't do this modification on a confirmed entry"
# ``You can't do this modification on a confirmed entry``
# exception.
cr.execute('UPDATE account_move SET name=%s WHERE id=%s',
(new_name, move.id))

View File

@@ -20,6 +20,5 @@
#
##############################################################################
import account_reversal
import wizard

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

@@ -39,7 +39,7 @@ class account_move(orm.Model):
'Reversal Entry',
ondelete='set null',
readonly=True),
}
}
def _move_reversal(self, cr, uid, move, reversal_date,
reversal_period_id=False, reversal_journal_id=False,
@@ -69,7 +69,7 @@ class account_move(orm.Model):
if not reversal_period_id:
reversal_period_id = period_obj.find(
cr, uid, reversal_date, context=period_ctx)[0]
cr, uid, reversal_date, context=period_ctx)[0]
if not reversal_journal_id:
reversal_journal_id = move.journal_id.id
@@ -93,9 +93,10 @@ class account_move(orm.Model):
reversal_move = self.browse(cr, uid, reversal_move_id, context=context)
for reversal_move_line in reversal_move.line_id:
reversal_ml_name = ' '.join(
[x for x
in [move_line_prefix, reversal_move_line.name]
if x])
[x for x
in [move_line_prefix, reversal_move_line.name]
if x]
)
move_line_obj.write(
cr,
uid,
@@ -137,14 +138,15 @@ class account_move(orm.Model):
continue # skip the reversal creation if already done
reversal_move_id = self._move_reversal(
cr, uid,
src_move,
reversal_date,
reversal_period_id=reversal_period_id,
reversal_journal_id=reversal_journal_id,
move_prefix=move_prefix,
move_line_prefix=move_line_prefix,
context=context)
cr, uid,
src_move,
reversal_date,
reversal_period_id=reversal_period_id,
reversal_journal_id=reversal_journal_id,
move_prefix=move_prefix,
move_line_prefix=move_line_prefix,
context=context
)
if reversal_move_id:
reversed_move_ids.append(reversal_move_id)

View File

@@ -59,7 +59,7 @@ class account_move_reversal(orm.TransientModel):
help="Prefix that will be added to the name of the journal "
"item to be reversed to create the name of the reversal "
"journal item (a space is added after the prefix)."),
}
}
def _next_period_first_date(self, cr, uid, context=None):
if context is None:
@@ -83,7 +83,7 @@ class account_move_reversal(orm.TransientModel):
_defaults = {
'date': _next_period_first_date,
'move_line_prefix': 'REV -',
}
}
def action_reverse(self, cr, uid, ids, context=None):
if context is None:
@@ -110,7 +110,7 @@ class account_move_reversal(orm.TransientModel):
context=context)
__, action_id = mod_obj.get_object_reference(
cr, uid, 'account', 'action_move_journal_line')
cr, uid, 'account', 'action_move_journal_line')
action = act_obj.read(cr, uid, [action_id], context=context)[0]
action['domain'] = unicode([('id', 'in', reversed_move_ids)])
action['name'] = _('Reversal Entries')

View File

@@ -20,4 +20,4 @@
#
##############################################################################
from . import model
from . import model

View File

@@ -41,7 +41,7 @@ default values on accounts and products on demand. Defaults for purchase
and sales taxes can be set at independent times. During the transition,
the old taxes can still be selected manually on invoice lines etc.
You can select to also duplicate linked tax code
You can select to also duplicate linked tax code
After the transition, the old taxes can be made inactive.

View File

@@ -52,11 +52,9 @@ class SelectTaxes(orm.TransientModel):
result += add_tree(child)
return result
covered = [
x.source_tax_id.id for x in
(wiz.config_id.sale_line_ids +
wiz.config_id.purchase_line_ids)
]
covered = [x.source_tax_id.id for x in
(wiz.config_id.sale_line_ids +
wiz.config_id.purchase_line_ids)]
taxes = []
for tax in list(set(map(get_root_node, wiz.tax_ids))):
taxes += add_tree(tax)
@@ -83,4 +81,4 @@ class SelectTaxes(orm.TransientModel):
'account.tax', 'update_tax_select_covered_taxes_rel',
'tax_select_id', 'tax_id',
string='Covered taxes'),
}
}

View File

@@ -79,15 +79,15 @@ class UpdateTaxConfig(orm.Model):
readonly=True),
'duplicate_tax_code': fields.boolean(
'Duplicate Tax code linked'),
}
}
_defaults = {
'state': 'draft',
}
}
_sql_constraints = [
('name_uniq', 'unique(name)', 'Name must be unique.'),
]
]
def add_lines(self, cr, uid, ids, context=None):
"""
@@ -106,14 +106,15 @@ class UpdateTaxConfig(orm.Model):
covered_tax_ids = [
x.source_tax_id.id
for x in config['purchase_line_ids'] + config['sale_line_ids']
]
]
res_id = wizard_obj.create(
cr, uid, {
'config_id': ids[0],
'type_tax_use': context['type_tax_use'],
'covered_tax_ids': [(6, 0, covered_tax_ids)],
}, context=context)
},
context=context)
local_context = context.copy()
local_context['active_id'] = res_id
@@ -128,7 +129,7 @@ class UpdateTaxConfig(orm.Model):
'target': 'new',
'res_id': res_id,
'nodestroy': True,
}
}
def confirm(self, cr, uid, ids, context=None):
"""
@@ -149,7 +150,7 @@ class UpdateTaxConfig(orm.Model):
log += " - %s (%s)\n" % (
line.source_tax_id.name,
line.source_tax_id.description
)
)
# Switch names around, not violating the uniqueness constraint
tax_old_name = line.source_tax_id.name
tax_pool.write(
@@ -163,7 +164,7 @@ class UpdateTaxConfig(orm.Model):
# 6.0 messes up the name change with copy + write, while
# 6.1 throws name uniqueness constraint violation
# So jumping some hoops with rewriting the new name
## We will check if we need to dupliace
# We will check if we need to dupliace
cp_base_code_id = False
cp_ref_base_code_id = False
cp_tax_code_id = False
@@ -186,10 +187,10 @@ class UpdateTaxConfig(orm.Model):
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
# 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:
cp_ref_base_code_id = cp_base_code_id
else:
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,
@@ -210,25 +211,31 @@ class UpdateTaxConfig(orm.Model):
{'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_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
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)
target_tax_id = tax_pool.copy(
cr, uid, line.source_tax_id.id,
{'name': '[update, %s] %s' % (config.name, tax_old_name),
'amount': amount_new,
'parent_id': False,
'child_ids': [(6, 0, [])],
}, context=context)
{
'name': '[update, %s] %s' % (config.name, tax_old_name),
'amount': amount_new,
'parent_id': False,
'child_ids': [(6, 0, [])],
},
context=context
)
tax_pool.write(
cr, uid, target_tax_id, {'name': tax_old_name,
'base_code_id': cp_base_code_id,
'ref_base_code_id': cp_ref_base_code_id,
'tax_code_id': cp_tax_code_id,
'ref_tax_code_id': cp_ref_tax_code_id
}, context=context
)
cr, uid, target_tax_id,
{'name': tax_old_name,
'base_code_id': cp_base_code_id,
'ref_base_code_id': cp_ref_base_code_id,
'tax_code_id': cp_tax_code_id,
'ref_tax_code_id': cp_ref_tax_code_id},
context=context
)
tax_map[line.source_tax_id.id] = target_tax_id
line_pool.write(
cr, uid, line.id,
@@ -253,18 +260,18 @@ class UpdateTaxConfig(orm.Model):
cr, uid, fp_tax.id,
{'tax_src_id': tax_map[fp_tax.tax_src_id.id],
'tax_dest_id': tax_map.get(
fp_tax.tax_dest_id.id, fp_tax.tax_dest_id.id)},
fp_tax.tax_dest_id.id, fp_tax.tax_dest_id.id)},
context=context)
new_fp_tax = fp_tax_pool.browse(
cr, uid, new_fp_tax_id, context=context)
log += ("\nCreate new tax mapping on position %s:\n"
"%s (%s)\n"
"=> %s (%s)\n" % (
new_fp_tax.position_id.name,
new_fp_tax.tax_src_id.name,
new_fp_tax.tax_src_id.description,
new_fp_tax.tax_dest_id.name,
new_fp_tax.tax_dest_id.description,
new_fp_tax.position_id.name,
new_fp_tax.tax_src_id.name,
new_fp_tax.tax_src_id.description,
new_fp_tax.tax_dest_id.name,
new_fp_tax.tax_dest_id.description,
))
self.write(
cr, uid, ids[0],
@@ -279,7 +286,7 @@ class UpdateTaxConfig(orm.Model):
'type': 'ir.actions.act_window',
'res_id': ids[0],
'nodestroy': True,
}
}
def set_defaults(self, cr, uid, ids, context=None):
if not context or not context.get('type_tax_use'):
@@ -308,7 +315,7 @@ class UpdateTaxConfig(orm.Model):
('name', '=', field_name)],
context=local_context)
for value in ir_values_pool.browse(
cr, uid, values_ids, context=context):
cr, uid, values_ids, context=context):
val = False
write = False
try:
@@ -337,9 +344,9 @@ class UpdateTaxConfig(orm.Model):
model_pool = self.pool.get('ir.model')
model_ids = model_pool.search(cr, uid, [], context=context)
models = model_pool.read(
cr, uid, model_ids, ['model'], context=context)
cr, uid, model_ids, ['model'], context=context)
pool_models_items = [(x['model'], self.pool.get(
x['model'])) for x in models]
x['model'])) for x in models]
# 6.1: self.pool.models.items():
for model_name, model in pool_models_items:
if model:
@@ -354,17 +361,17 @@ class UpdateTaxConfig(orm.Model):
context['type_tax_use'])
for (model, field) in [
# make this a configurable list of ir_model_fields one day?
('account.account', 'tax_ids'),
('product.product', 'supplier_taxes_id'),
('product.product', 'taxes_id'),
('product.template', 'supplier_taxes_id'),
('product.template', 'taxes_id')]:
('account.account', 'tax_ids'),
('product.product', 'supplier_taxes_id'),
('product.product', 'taxes_id'),
('product.template', 'supplier_taxes_id'),
('product.template', 'taxes_id')]:
pool = self.pool.get(model)
obj_ids = pool.search(
cr, uid, [(field, 'in', tax_map.keys())],
context=local_context)
for obj in pool.read(
cr, uid, obj_ids, [field], context=context):
cr, uid, obj_ids, [field], context=context):
new_val = []
write = False
for i in obj[field]:
@@ -393,7 +400,7 @@ class UpdateTaxConfig(orm.Model):
'type': 'ir.actions.act_window',
'res_id': ids[0],
'nodestroy': True,
}
}
def set_inactive(self, cr, uid, ids, context=None):
if not context or not context.get('type_tax_use'):
@@ -430,7 +437,7 @@ class UpdateTaxConfig(orm.Model):
'type': 'ir.actions.act_window',
'res_id': ids[0],
'nodestroy': True,
}
}
class UpdateTaxConfigLine(orm.Model):
@@ -439,7 +446,7 @@ class UpdateTaxConfigLine(orm.Model):
_rec_name = 'source_tax_id' # Wha'evuh
def _get_config_field(
self, cr, uid, ids, field, args, context=None):
self, cr, uid, ids, field, args, context=None):
# Retrieve values of the associated config_id
# either sale or purchase
result = dict([(x, False) for x in ids or []])
@@ -480,4 +487,4 @@ class UpdateTaxConfigLine(orm.Model):
'state': fields.function(
_get_config_field, 'state', method=True,
type='char', size=16, string='State'),
}
}

View File

@@ -1,7 +1,7 @@
ref;date;period_id;journal_id;line_id / account_id;line_id / partner_id;line_id / name;line_id / debit;line_id / credit;line_id/tax_code_id
test_3;2013-10-01;X 01/2013;MISC;X2001;Camptocamp;TEST C2C;;1000;Tax Received
;;;;X11003;Camptocamp;TEST C2C;;200;Tax Paid
test_3;2014-01-01;X 01/2014;Sales Journal - (test);X2001;Camptocamp;TEST C2C1;;1000;
;;;;X11003;Camptocamp;TEST C2C;;200;;
;;;;X11002;Camptocamp;TEST C2C;1200;;
test_3b;2013-10-01;X 01/2013;MISC;X2001;Camptocamp;TEST C2C;;1000;Tax Received
test_3b;2014-01-01;X 01/2014;Sales Journal - (test);X2001;Camptocamp;TEST C2C2;;1000;
;;;;X11003;Camptocamp;TEST C2C;;200;Faulty code
;;;;X11002;Camptocamp;TEST C2C;1200;;
;;;;X11002;Camptocamp;TEST C2C;1200;;
1 ref ref;date;period_id;journal_id;line_id / account_id;line_id / partner_id;line_id / name;line_id / debit;line_id / credit;line_id/tax_code_id date period_id journal_id line_id / account_id line_id / partner_id line_id / name line_id / debit line_id / credit line_id/tax_code_id
2 test_3 test_3;2014-01-01;X 01/2014;Sales Journal - (test);X2001;Camptocamp;TEST C2C1;;1000; 2013-10-01 X 01/2013 MISC X2001 Camptocamp TEST C2C 1000 Tax Received
3 ;;;;X11003;Camptocamp;TEST C2C;;200;; X11003 Camptocamp TEST C2C 200 Tax Paid
4 ;;;;X11002;Camptocamp;TEST C2C;1200;; X11002 Camptocamp TEST C2C 1200
5 test_3b test_3b;2014-01-01;X 01/2014;Sales Journal - (test);X2001;Camptocamp;TEST C2C2;;1000; 2013-10-01 X 01/2013 MISC X2001 Camptocamp TEST C2C 1000 Tax Received
6 ;;;;X11003;Camptocamp;TEST C2C;;200;Faulty code X11003 Camptocamp TEST C2C 200 Faulty code
7 ;;;;X11002;Camptocamp;TEST C2C;1200;; X11002 Camptocamp TEST C2C 1200

View File

@@ -1,4 +1,4 @@
ref;date;period_id;journal_id;line_id / account_id;line_id / partner_id;line_id / name;line_id / debit;line_id / credit;line_id/tax_code_id
éöüàè_test_1;2013-10-01;X 01/2013;MISC;X2001;Camptocamp;TEST C2C;;1000;Tax Received
;;;;X11003;Camptocamp;TEST C2C;;200;Tax Paid
;;;;X11002;Camptocamp;TEST C2C;1200;;
éöüàè_test_1;2014-01-01;X 01/2014;Sales Journal - (test);X2001;Camptocamp;TEST C2C4;;1000;
;;;;X11003;Camptocamp;TEST C2C5;;200;;
;;;;X11002;Camptocamp;TEST C2C6;1200;;
1 ref ref;date;period_id;journal_id;line_id / account_id;line_id / partner_id;line_id / name;line_id / debit;line_id / credit;line_id/tax_code_id date period_id journal_id line_id / account_id line_id / partner_id line_id / name line_id / debit line_id / credit line_id/tax_code_id
2 éöüàè_test_1 éöüàè_test_1;2014-01-01;X 01/2014;Sales Journal - (test);X2001;Camptocamp;TEST C2C4;;1000; 2013-10-01 X 01/2013 MISC X2001 Camptocamp TEST C2C 1000 Tax Received
3 ;;;;X11003;Camptocamp;TEST C2C5;;200;; X11003 Camptocamp TEST C2C 200 Tax Paid
4 ;;;;X11002;Camptocamp;TEST C2C6;1200;; X11002 Camptocamp TEST C2C 1200

View File

@@ -1,4 +1,4 @@
ref;date;period_id;journal_id;line_id / account_id;line_id / partner_id;line_id / name;line_id / debit;line_id / credit;line_id/tax_code_id
test_2;2013-10-01;X 01/2013;MISC;X2001;Camptocamp;TEST C2C;;1000;Tax Received
;;;;X11003;Camptocamp;TEST C2C;;200;Tax Paid
;;;;X11002;Camptocamp;TEST C2C;1200;;
test_2;2014-01-01;X 01/2014;Sales Journal - (test);X2001;Camptocamp;TEST C2C8;;1000;
;;;;X11003;Camptocamp;TEST C2C9;;200;;
;;;;X11002;Camptocamp;TEST C2C10;1200;;
1 ref ref;date;period_id;journal_id;line_id / account_id;line_id / partner_id;line_id / name;line_id / debit;line_id / credit;line_id/tax_code_id date period_id journal_id line_id / account_id line_id / partner_id line_id / name line_id / debit line_id / credit line_id/tax_code_id
2 test_2 test_2;2014-01-01;X 01/2014;Sales Journal - (test);X2001;Camptocamp;TEST C2C8;;1000; 2013-10-01 X 01/2013 MISC X2001 Camptocamp TEST C2C 1000 Tax Received
3 ;;;;X11003;Camptocamp;TEST C2C9;;200;; X11003 Camptocamp TEST C2C 200 Tax Paid
4 ;;;;X11002;Camptocamp;TEST C2C10;1200;; X11002 Camptocamp TEST C2C 1200

View File

@@ -22,4 +22,3 @@
from . import company
from . import currency_rate_date_check

View File

@@ -31,9 +31,14 @@
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 from the date of the amount to convert. The maximum number of days of the interval can be configured on the company form.
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
from the date of the amount to convert.
Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com> for any help or question about this module.
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.
""",
'author': 'Akretion',
'website': 'http://www.akretion.com',
@@ -42,7 +47,7 @@ Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com> for
'images': [
'images/date_check_error_popup.jpg',
'images/date_check_company_config.jpg',
],
],
'installable': False,
'active': False,
}

View File

@@ -22,13 +22,16 @@
from openerp.osv import orm, fields
class res_company(orm.Model):
_inherit = 'res.company'
_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 rate available in OpenERP."),
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."),
}
_defaults = {
@@ -39,4 +42,4 @@ class res_company(orm.Model):
('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."),
]
]

View File

@@ -20,8 +20,8 @@
#
##############################################################################
from openerp.osv import fields, orm
from datetime import datetime, timedelta
from openerp.osv import orm
from datetime import datetime
from openerp.tools.translate import _
# Here are some explainations about the design of this module.
@@ -29,7 +29,9 @@ from openerp.tools.translate import _
# 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
# _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
# _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
# 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
@@ -45,10 +47,12 @@ class res_currency(orm.Model):
_inherit = 'res.currency'
def _current_rate_computation(self, cr, uid, ids, name, arg, raise_on_no_rate, context=None):
if context is None: context = {}
if context is None:
context = {}
# We only do the check if there is an explicit date in the context and
# there is no specific currency_rate_type_id
if context.get('date') and not context.get('currency_rate_type_id') and not context.get('disable_rate_date_check'):
if context.get('date') and not context.get('currency_rate_type_id') and\
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
@@ -67,7 +71,7 @@ class res_currency(orm.Model):
('currency_id', '=', currency_id),
('name', '<=', date),
('currency_rate_type_id', '=', None)
], order='name desc', limit=1, context=context)
], order='name desc', limit=1, context=context)
if not selected_rate:
continue
@@ -76,7 +80,13 @@ class res_currency(orm.Model):
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']
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))
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)
)
# Now we call the regular function from the "base" module
return super(res_currency, self)._current_rate_computation(cr, uid, ids, name, arg, raise_on_no_rate, context=context)
return super(res_currency, self)._current_rate_computation(
cr, uid, ids, name, arg, raise_on_no_rate, context=context)

View File

@@ -22,11 +22,11 @@
#
##############################################################################
{
"name" : "Currency Rate Update",
"version" : "0.7",
"author" : "Camptocamp",
"website" : "http://camptocamp.com",
"category" : "Financial Management/Configuration",
"name": "Currency Rate Update",
"version": "0.7",
"author": "Camptocamp",
"website": "http://camptocamp.com",
"category": "Financial Management/Configuration",
"description": """Import exchange rates from the Internet.
The module is able to use 4 different sources:
@@ -48,7 +48,7 @@ The module is able to use 4 different sources:
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)
Updated daily
@@ -64,7 +64,7 @@ The module uses internal ir_cron feature from OpenERP, so the job is launched on
the server starts if the 'first execute date' is before the current day.
The module supports multi-company currency in two ways:
* the currencies are shared, you can set currency update only on one
* the currencies are shared, you can set currency update only on one
company
* the currency are separated, you can set currency on every company
separately
@@ -76,16 +76,16 @@ found in database.
Thanks to main contributors: Grzegorz Grzelak, Alexis de Lattre
""",
"depends" : [
"depends": [
"base",
"account", #Added to ensure account security groups are present
],
"data" : [
"account", # Added to ensure account security groups are present
],
"data": [
"currency_rate_update.xml",
"company_view.xml",
"security/security.xml",
],
"demo" : [],
],
"demo": [],
"active": False,
'installable': False
}

View File

@@ -18,17 +18,21 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp import netsvc
from openerp.osv import fields, orm
class res_company(orm.Model):
"""override company to add currency update"""
def _multi_curr_enable(self, cr, uid, ids, field_name, arg, context={}):
"check if multi company currency is enabled"
result = {}
if self.pool.get('ir.model.fields').search(cr, uid, [('name', '=', 'company_id'), ('model', '=', 'res.currency')])==[]:
fields = self.pool.get('ir.model.fields').search(
cr, uid,
[('name', '=', 'company_id'),
('model', '=', 'res.currency')]
)
if not fields:
enable = 0
else:
enable = 1
@@ -36,18 +40,15 @@ class res_company(orm.Model):
result[id] = enable
return result
def button_refresh_currency(self, cr, uid, ids, context=None):
"""Refrech the currency !!for all the company
now"""
"""Refrech the currency for all the company now"""
currency_updater_obj = self.pool.get('currency.rate.update')
try:
currency_updater_obj.run_currency_update(cr, uid)
except Exception, e:
except Exception:
return False
return True
def on_change_auto_currency_up(self, cr, uid, id, value):
"""handle the activation of the currecny update on compagnies.
There are two ways of implementing multi_company currency,
@@ -56,102 +57,100 @@ class res_company(orm.Model):
auto update on one company, this will avoid to have unusefull cron
object running.
If yours currency are not share you will be able to activate the
auto update on each separated company"""
auto update on each separated company
if len(id) :
"""
if len(id):
id = id[0]
else :
else:
return {}
enable = self.browse(cr, uid, id).multi_company_currency_enable
compagnies = self.search(cr, uid, [])
compagnies = self.search(cr, uid, [])
activate_cron = 'f'
if not value :
if not value:
# 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 :
self.write(cr, uid, id, {'auto_currency_up': value})
for comp in compagnies:
if self.browse(cr, uid, comp).auto_currency_up:
activate_cron = 't'
break
self.pool.get('currency.rate.update').save_cron(
cr,
uid,
{'active':activate_cron}
)
cr,
uid,
{'active': activate_cron}
)
return {}
else :
for comp in compagnies :
else:
for comp in compagnies:
if comp != id and not enable:
if self.browse(cr, uid, comp).multi_company_currency_enable:
#we ensure taht we did not have write a true value
self.write(cr, uid, id,{'auto_currency_up':False})
# We ensure taht we did not have write a true value
self.write(cr, uid, id, {'auto_currency_up': False})
return {
'value':{
'auto_currency_up':False
},
'value': {'auto_currency_up': False},
'warning':{
'title':"Warning",
'message': 'Yon can not activate auto currency '+\
'update on more thant one company with this '+
'multi company configuration'
}
}
self.write(cr, uid, id,{'auto_currency_up':value})
for comp in compagnies :
'warning': {
'title': "Warning",
'message': 'You can not activate auto currency '
'update on more thant one company with this '
'multi company configuration'
}
}
self.write(cr, uid, id, {'auto_currency_up': value})
for comp in compagnies:
if self.browse(cr, uid, comp).auto_currency_up:
activate_cron = 't'
self.pool.get('currency.rate.update').save_cron(
cr,
uid,
{'active':activate_cron}
)
cr,
uid,
{'active': activate_cron}
)
break
return {}
def on_change_intervall(self, cr, uid, id, interval) :
###Function that will update the cron
###freqeuence
def on_change_intervall(self, cr, uid, id, interval):
# Function that will update the cron freqeuence
self.pool.get('currency.rate.update').save_cron(
cr,
uid,
{'interval_type':interval}
)
compagnies = self.search(cr, uid, [])
for comp in compagnies :
self.write(cr, uid, comp,{'interval_type':interval})
cr,
uid,
{'interval_type': interval}
)
compagnies = self.search(cr, uid, [])
for comp in compagnies:
self.write(cr, uid, comp, {'interval_type': interval})
return {}
_inherit = "res.company"
_columns = {
### activate the currency update
'auto_currency_up': fields.boolean('Automatical update of the currency this company'),
'services_to_use' : fields.one2many(
'currency.rate.update.service',
'company_id',
'Currency update services'
),
###predifine cron frequence
# Activate the currency update
'auto_currency_up': fields.boolean(
'Automatical update of the currency this company'
),
'services_to_use': fields.one2many(
'currency.rate.update.service',
'company_id',
'Currency update services'
),
# Predifine cron frequence
'interval_type': fields.selection(
[
('days','Day(s)'),
('weeks', 'Week(s)'),
('months', 'Month(s)')
],
'Currency update frequence',
help="""changing this value will
also affect other compagnies"""
),
###function field that allows to know the
###mutli company currency implementation
'multi_company_currency_enable' : fields.function(
_multi_curr_enable,
method=True,
type='boolean',
string="Multi company currency",
help='if this case is not check you can'+\
' not set currency is active on two company'
),
[
('days', 'Day(s)'),
('weeks', 'Week(s)'),
('months', 'Month(s)')
],
'Currency update frequence',
help="Changing this value will "
"also affect other compagnies"
),
# Function field that allows to know the
# mutli company currency implementation
'multi_company_currency_enable': fields.function(
_multi_curr_enable,
method=True,
type='boolean',
string="Multi company currency",
help="If this case is not check you can"
" not set currency is active on two company"
),
}

View File

@@ -2,7 +2,6 @@
##############################################################################
#
# Copyright (c) 2009 Camptocamp SA
# @author Nicolas Bessi
# @source JBA and AWST inpiration
# @contributor Grzegorz Grzelak (grzegorz.grzelak@birdglobe.com), Joel Grand-Guillaume
# Copyright (c) 2010 Alexis de Lattre (alexis@via.ecp.fr)
@@ -31,68 +30,73 @@
#
##############################################################################
# TODO "nice to have" : restain the list of currencies that can be added for
# TODO "nice to have" : restrain the list of currencies that can be added for
# a webservice to the list of currencies supported by the Webservice
# TODO : implement max_delta_days for Yahoo webservice
from openerp.osv import fields, osv, orm
import logging
import time
from datetime import datetime, timedelta
import logging
from openerp.osv import fields, osv, orm
from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
class Currency_rate_update_service(osv.Model):
"""Class thats tell for wich services wich currencies
have to be updated"""
"""Class that tells for wich services wich currencies have to be updated
"""
_name = "currency.rate.update.service"
_description = "Currency Rate Update"
_columns = {
##list of webservicies the value sould be a class name
'service' : fields.selection(
[
('Admin_ch_getter','Admin.ch'),
('ECB_getter','European Central Bank'),
#('NYFB_getter','Federal Reserve Bank of NY'),
#('Google_getter','Google Finance'),
('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
],
"Webservice to use",
required = True
),
##list of currency to update
'currency_to_update' : fields.many2many(
'res.currency',
'res_curreny_auto_udate_rel',
'service_id',
'currency_id',
'currency to update with this service',
),
#back ref
'company_id' : fields.many2one(
'res.company',
'linked company',
),
##note fileds that will be used as a logger
'note':fields.text('update notice'),
'max_delta_days': fields.integer('Max delta days', required=True, help="If the time delta between the rate date given by the webservice and the current date exeeds this value, then the currency rate is not updated in OpenERP."),
}
_defaults = {
'max_delta_days': lambda *a: 4,
# List of webservicies the value sould be a class name
'service': fields.selection(
[
('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
],
"Webservice to use",
required=True
),
# List of currency to update
'currency_to_update': fields.many2many(
'res.currency',
'res_curreny_auto_udate_rel',
'service_id',
'currency_id',
'currency to update with this service',
),
# Back ref
'company_id': fields.many2one(
'res.company',
'linked company',
),
# Note fileds that will be used as a logger
'note': fields.text('update notice'),
'max_delta_days': fields.integer(
'Max delta days',
required=True,
help="If the time delta between the "
"rate date given by the webservice and "
"the current date exeeds this value, "
"then the currency rate is not updated in OpenERP."
),
}
_defaults = {'max_delta_days': lambda *a: 4}
_sql_constraints = [
(
'curr_service_unique',
'unique (service, company_id)',
_('You can use a service one time per company !')
)
]
(
'curr_service_unique',
'unique (service, company_id)',
_('You can use a service one time per company !')
)
]
def _check_max_delta_days(self, cr, uid, ids):
for company in self.read(cr, uid, ids, ['max_delta_days']):
@@ -103,7 +107,9 @@ class Currency_rate_update_service(osv.Model):
return True
_constraints = [
(_check_max_delta_days, "'Max delta days' must be >= 0", ['max_delta_days']),
(_check_max_delta_days,
"'Max delta days' must be >= 0",
['max_delta_days']),
]
@@ -112,58 +118,57 @@ class Currency_rate_update(osv.Model):
update currencies based on a web url"""
_name = "currency.rate.update"
_description = "Currency Rate Update"
##dict that represent a cron object
# Dict that represent a cron object
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() ), #tomorrow same time
'numbercall' : -1,
'doall' : True,
'model' : 'currency.rate.update',
'function' : 'run_currency_update',
'args' : '()',
'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(),
'numbercall': -1,
'doall': True,
'model': 'currency.rate.update',
'function': 'run_currency_update',
'args': '()',
}
LOG_NAME = 'cron-rates'
MOD_NAME = 'currency_rate_update: '
def get_cron_id(self, cr, uid, context):
"""return the updater cron's id. Create one if the cron does not exists """
"""Returns the updater cron's id.
Create one if the cron does not exists
"""
cron_id = 0
cron_obj = self.pool.get('ir.cron')
try:
#find the cron that send messages
# Finds the cron that send messages
cron_id = cron_obj.search(
cr,
uid,
[
('function', 'ilike', self.cron['function']),
('model', 'ilike', self.cron['model'])
],
context={
'active_test': False
}
)
cr,
uid,
[
('function', 'ilike', self.cron['function']),
('model', 'ilike', self.cron['model'])
],
context={
'active_test': False
}
)
cron_id = int(cron_id[0])
except Exception,e :
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
#the cron does not exists
if not cron_id :
#translate
pass # Ignore if the cron is missing cause we are going to create it in db
if not cron_id:
self.cron['name'] = _('Currency Rate Update')
cron_id = cron_obj.create(cr, uid, self.cron, context)
return cron_id
def save_cron(self, cr, uid, datas, context={}):
"""save the cron config data should be a dict"""
#modify the cron
cron_id = self.get_cron_id(cr, uid, context)
result = self.pool.get('ir.cron').write(cr, uid, [cron_id], datas)
return self.pool.get('ir.cron').write(cr, uid, [cron_id], datas)
def run_currency_update(self, cr, uid):
"update currency at the given frequence"
@@ -172,172 +177,192 @@ class Currency_rate_update(osv.Model):
rate_obj = self.pool.get('res.currency.rate')
companies = self.pool.get('res.company').search(cr, uid, [])
for comp in self.pool.get('res.company').browse(cr, uid, companies):
##the multi company currency can beset or no so we handle
##the two case
if not comp.auto_currency_up :
# The multi company currency can beset or no so we handle
# The two case
if not comp.auto_currency_up:
continue
#we initialise the multi compnay search filter or not serach filter
search_filter = []
if comp.multi_company_currency_enable :
search_filter = [('company_id','=',comp.id)]
#we fetch the main currency looking for currency with base = true. The main rate should be set at 1.00
main_curr_ids = curr_obj.search(cr, uid, [('base','=',True),('company_id','=',comp.id)])
# We fetch the main currency looking for currency with base = true.
# The main rate should be set at 1.00
main_curr_ids = curr_obj.search(
cr, uid,
[('base', '=', True), ('company_id', '=', comp.id)]
)
if not main_curr_ids:
# If we can not find a base currency for this company we look for one with no company set
main_curr_ids = curr_obj.search(cr, uid, [('base','=',True),('company_id','=', False)])
# If we can not find a base currency for this company
# we look for one with no company set
main_curr_ids = curr_obj.search(
cr, uid,
[('base', '=', True), ('company_id', '=', False)]
)
if main_curr_ids:
main_curr_rec = curr_obj.browse(cr, uid, main_curr_ids[0])
else:
raise orm.except_orm(_('Error!'),('There is no base currency set!'))
raise orm.except_orm(
_('Error!'),
('There is no base currency set!')
)
if main_curr_rec.rate != 1:
raise orm.except_orm(_('Error!'),('Base currency rate should be 1.00!'))
raise orm.except_orm(
_('Error!'),
('Base currency rate should be 1.00!')
)
main_curr = main_curr_rec.name
for service in comp.services_to_use :
print "comp.services_to_use =", comp.services_to_use
for service in comp.services_to_use:
note = service.note or ''
try :
## we initalize the class that will handle the request
## and return a dict of rate
try:
# We initalize the class that will handle the request
# and return a dict of rate
getter = factory.register(service.service)
print "getter =", getter
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)
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')
for curr in service.currency_to_update :
if curr.name == main_curr :
for curr in service.currency_to_update:
if curr.name == main_curr:
continue
do_create = True
for rate in curr.rate_ids :
if rate.name == rate_name :
rate.write({'rate':res[curr.name]})
for rate in curr.rate_ids:
if rate.name == rate_name:
rate.write({'rate': res[curr.name]})
do_create = False
break
if do_create :
if do_create:
vals = {
'currency_id': curr.id,
'rate':res[curr.name],
'name': rate_name
}
'currency_id': curr.id,
'rate': res[curr.name],
'name': rate_name
}
rate_obj.create(
cr,
uid,
vals,
)
cr,
uid,
vals,
)
# show the most recent note at the top
note = "\n%s currency updated. "\
%(datetime.strftime(datetime.today(), '%Y-%m-%d %H:%M:%S'))\
+ note
note = (log_info or '') + note
service.write({'note':note})
except Exception, e:
error_msg = "\n%s ERROR : %s"\
%(datetime.strftime(datetime.today(), '%Y-%m-%d %H:%M:%S'), str(e))\
+ note
_logger.info(str(e))
service.write({'note':error_msg})
# 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)
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)
_logger.info(repr(exc))
service.write({'note': error_msg})
### Error Definition as specified in python 2.6 PEP
class AbstractClassError(Exception):
def __str__(self):
return 'Abstract Class'
def __repr__(self):
return 'Abstract Class'
return 'Abstract Class'
class AbstractMethodError(Exception):
def __str__(self):
return 'Abstract Method'
def __repr__(self):
return 'Abstract Method'
class UnknowClassError(Exception):
def __str__(self):
return 'Unknown Class'
def __repr__(self):
return 'Unknown Class'
class UnsuportedCurrencyError(Exception):
def __init__(self, value):
self.curr = value
def __str__(self):
return 'Unsupported currency '+self.curr
def __repr__(self):
return 'Unsupported currency '+self.curr
self.curr = value
def __str__(self):
return 'Unsupported currency %s' % self.curr
def __repr__(self):
return 'Unsupported currency %s' % self.curr
### end of error definition
class Currency_getter_factory():
"""Factory pattern class that will return
a currency getter class base on the name passed
to the register method"""
to the register method
"""
def register(self, class_name):
allowed = [
'Admin_ch_getter',
'PL_NBP_getter',
'ECB_getter',
'NYFB_getter',
'Google_getter',
'Yahoo_getter',
'Banxico_getter',
'CA_BOC_getter',
]
'Admin_ch_getter',
'PL_NBP_getter',
'ECB_getter',
'NYFB_getter',
'Google_getter',
'Yahoo_getter',
'Banxico_getter',
'CA_BOC_getter',
]
if class_name in allowed:
class_def = eval(class_name)
return class_def()
else :
else:
raise UnknowClassError
class Curreny_getter_interface(object) :
class Curreny_getter_interface(object):
"Abstract class of currency getter"
#remove in order to have a dryer code
# def __init__(self):
# raise AbstractClassError
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']
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'
]
##updated currency this arry will contain the final result
# 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"""
This function has to be reinplemented in child
"""
raise AbstractMethodError
def validate_cur(self, currency) :
def validate_cur(self, currency):
"""Validate if the currency to update is supported"""
if currency not in self.supported_currency_array :
if currency not in self.supported_currency_array:
raise UnsuportedCurrencyError(currency)
def get_url(self, url):
@@ -349,63 +374,81 @@ class Curreny_getter_interface(object) :
objfile.close()
return rawfile
except ImportError:
raise osv.except_osv('Error !', self.MOD_NAME+'Unable to import urllib !')
raise osv.except_osv(
'Error !',
self.MOD_NAME + 'Unable to import urllib !'
)
except IOError:
raise osv.except_osv('Error !', self.MOD_NAME+'Web Service does not exist !')
raise osv.except_osv(
'Error !',
self.MOD_NAME + 'Web Service does not exist !'
)
def check_rate_date(self, rate_date, max_delta_days):
"""Check date constrains. WARN : rate_date must be of datetime type"""
"""Check date constrains. rate_date must be of datetime type"""
days_delta = (datetime.today() - rate_date).days
if days_delta > max_delta_days:
raise Exception('The rate timestamp (%s) is %d days away from today, which is over the limit (%d days). Rate not updated in OpenERP.'%(rate_date, days_delta, max_delta_days))
# We always have a warning when rate_date <> today
raise Exception(
'The rate timestamp (%s) is %d days away from today, '
'which is over the limit (%d days). '
'Rate not updated in OpenERP.' % (rate_date,
days_delta,
max_delta_days)
)
# 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'):
self.log_info = "WARNING : the rate timestamp (%s) is not today's date" % rate_date_str
_logger.warning("the rate timestamp (%s) is not today's date", rate_date_str)
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 ###################################################################################
class Yahoo_getter(Curreny_getter_interface) :
# Yahoo ########################################################################
class Yahoo_getter(Curreny_getter_interface):
"""Implementation of Currency_getter_factory interface
for Yahoo finance service"""
for Yahoo finance service
"""
def get_updated_currency(self, currency_array, main_currency, max_delta_days):
"""implementation of abstract method of Curreny_getter_interface"""
"""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'
if main_currency in currency_array :
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 :
for curr in currency_array:
self.validate_cur(curr)
res = self.get_url(url%(main_currency+curr))
res = self.get_url(url % (main_currency + curr))
val = res.split(',')[1]
if val :
if val:
self.updated_currency[curr] = val
else :
raise Exception('Could not update the %s'%(curr))
else:
raise Exception('Could not update the %s' % (curr))
return self.updated_currency, self.log_info # empty string added by polish changes
##Admin CH ############################################################################
class Admin_ch_getter(Curreny_getter_interface) :
return self.updated_currency, self.log_info
# Admin CH ####################################################################
class Admin_ch_getter(Curreny_getter_interface):
"""Implementation of Currency_getter_factory interface
for Admin.ch service"""
for Admin.ch service
def rate_retrieve(self, dom, ns, curr) :
""" Parse a dom node to retrieve-
currencies data"""
"""
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())
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):
"""implementation of abstract method of Curreny_getter_interface"""
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 :
"""Implementation of abstract method of Curreny_getter_interface"""
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)
# Move to new XML lib cf Launchpad bug #645263
from lxml import etree
@@ -417,7 +460,7 @@ class Admin_ch_getter(Curreny_getter_interface) :
rate_date = dom.xpath('/def:wechselkurse/def:datum/text()', namespaces=adminch_ns)[0]
rate_date_datetime = datetime.strptime(rate_date, '%Y-%m-%d')
self.check_rate_date(rate_date_datetime, max_delta_days)
#we dynamically update supported currencies
# 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.append('CHF')
@@ -428,45 +471,50 @@ class Admin_ch_getter(Curreny_getter_interface) :
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']
for curr in currency_array :
for curr in currency_array:
self.validate_cur(curr)
if curr == 'CHF':
rate = main_rate
else:
curr_data = self.rate_retrieve(dom, adminch_ns, curr)
# 1 MAIN_CURRENCY = rate CURR
if main_currency == 'CHF' :
if main_currency == 'CHF':
rate = curr_data['rate_ref'] / curr_data['rate_currency']
else :
else:
rate = main_rate * curr_data['rate_ref'] / curr_data['rate_currency']
self.updated_currency[curr] = rate
_logger.debug("Rate retrieved : 1 " + main_currency + ' = ' + str(rate) + ' ' + curr)
_logger.debug(
"Rate retrieved : 1 %s = %s %s" % (main_currency, rate, curr)
)
return self.updated_currency, self.log_info
## ECB getter ############################################################################
class ECB_getter(Curreny_getter_interface) :
# ECB getter #################################################################
class ECB_getter(Curreny_getter_interface):
"""Implementation of Currency_getter_factory interface
for ECB service"""
for ECB service
"""
def rate_retrieve(self, dom, ns, curr) :
""" Parse a dom node to retrieve-
currencies data"""
def rate_retrieve(self, dom, ns, curr):
"""Parse a dom node to retrieve-
currencies data
"""
res = {}
xpath_curr_rate = "/gesmes:Envelope/def:Cube/def:Cube/def:Cube[@currency='%s']/@rate"%(curr.upper())
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):
"""implementation of abstract method of Curreny_getter_interface"""
url='http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'
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
#we do not want to update the main currency
if main_currency in currency_array :
# We do not want to update the main currency
if main_currency in currency_array:
currency_array.remove(main_currency)
# Move to new XML lib cf Launchpad bug #645263
from lxml import etree
@@ -474,14 +522,19 @@ class ECB_getter(Curreny_getter_interface) :
rawfile = self.get_url(url)
dom = etree.fromstring(rawfile)
_logger.debug("ECB sent a valid XML file")
ecb_ns = {'gesmes': 'http://www.gesmes.org/xml/2002-08-01', 'def': 'http://www.ecb.int/vocabulary/2002-08-01/eurofxref'}
rate_date = dom.xpath('/gesmes:Envelope/def:Cube/def:Cube/@time', namespaces=ecb_ns)[0]
ecb_ns = {
'gesmes': 'http://www.gesmes.org/xml/2002-08-01',
'def': 'http://www.ecb.int/vocabulary/2002-08-01/eurofxref'
}
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')
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)
# 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.append('EUR')
_logger.debug("Supported currencies = " + str(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)
@@ -496,56 +549,54 @@ class ECB_getter(Curreny_getter_interface) :
else:
rate = curr_data['rate_currency'] / main_curr_data['rate_currency']
self.updated_currency[curr] = rate
_logger.debug("Rate retrieved : 1 " + main_currency + ' = ' + str(rate) + ' ' + curr)
_logger.debug("Rate retrieved : 1 %s = %s %s" % (main_currency, rate, curr))
return self.updated_currency, self.log_info
##PL NBP ############################################################################
class PL_NBP_getter(Curreny_getter_interface) : # class added according to polish needs = based on class Admin_ch_getter
"""Implementation of Currency_getter_factory interface
for PL NBP service"""
def rate_retrieve(self, dom, ns, curr) :
# PL NBP ######################################################################
class PL_NBP_getter(Curreny_getter_interface):
"""Implementation of Currency_getter_factory interface
for PL NBP service
"""
def rate_retrieve(self, dom, ns, curr):
""" 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):
"""implementation of abstract method of Curreny_getter_interface"""
url='http://www.nbp.pl/kursy/xml/LastA.xml' # LastA.xml is always the most recent one
#we do not want to update the main currency
if main_currency in currency_array :
# LastA.xml is always the most recent one
url = 'http://www.nbp.pl/kursy/xml/LastA.xml'
# We do not want to update the main currency
if main_currency in currency_array:
currency_array.remove(main_currency)
# Move to new XML lib cf Launchpad bug #645263
from lxml import etree
_logger.debug("NBP.pl currency rate service : connecting...")
rawfile = self.get_url(url)
dom = etree.fromstring(rawfile) # If rawfile is not XML, it crashes here
ns = {} # Cool, there are no namespaces !
dom = etree.fromstring(rawfile)
ns = {} # Cool, there are no namespaces !
_logger.debug("NBP.pl sent a valid XML file")
#node = xpath.Evaluate("/tabela_kursow", dom) # BEGIN Polish - rates table name
#if isinstance(node, list) :
# node = node[0]
#self.log_info = node.getElementsByTagName('numer_tabeli')[0].childNodes[0].data
#self.log_info = self.log_info + " " + node.getElementsByTagName('data_publikacji')[0].childNodes[0].data # END Polish - rates table name
rate_date = dom.xpath('/tabela_kursow/data_publikacji/text()', namespaces=ns)[0]
rate_date_datetime = datetime.strptime(rate_date, '%Y-%m-%d')
self.check_rate_date(rate_date_datetime, max_delta_days)
#we dynamically update supported currencies
# We dynamically update supported currencies
self.supported_currency_array = dom.xpath('/tabela_kursow/pozycja/kod_waluty/text()', namespaces=ns)
self.supported_currency_array.append('PLN')
_logger.debug("Supported currencies = " + str(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']
for curr in currency_array :
for curr in currency_array:
self.validate_cur(curr)
if curr == 'PLN':
rate = main_rate
@@ -557,14 +608,16 @@ class PL_NBP_getter(Curreny_getter_interface) : # class added according to pol
else:
rate = main_rate * curr_data['rate_ref'] / curr_data['rate_currency']
self.updated_currency[curr] = rate
_logger.debug("Rate retrieved : 1 " + main_currency + ' = ' + str(rate) + ' ' + curr)
_logger.debug("Rate retrieved : %s = %s %s" % (main_currency, rate, curr))
return self.updated_currency, self.log_info
##Banco de México ############################################################################
class Banxico_getter(Curreny_getter_interface) : # class added for Mexico rates
# Banco de México #############################################################
class Banxico_getter(Curreny_getter_interface):
"""Implementation of Currency_getter_factory interface
for Banco de México service"""
for Banco de México service
"""
def rate_retrieve(self):
""" Get currency exchange from Banxico.xml and proccess it
@@ -587,17 +640,16 @@ class Banxico_getter(Curreny_getter_interface) : # class added for Mexico rates
return float(rate)
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
if main_currency in currency_array :
if main_currency in currency_array:
currency_array.remove(main_currency)
# Suported currencies
suported = ['MXN', 'USD']
for curr in currency_array :
for curr in currency_array:
if curr in suported:
# Get currency data
main_rate = self.rate_retrieve()
@@ -606,17 +658,19 @@ class Banxico_getter(Curreny_getter_interface) : # class added for Mexico rates
else:
rate = main_rate
else:
""" No other currency supported
"""
# No other currency supported
continue
self.updated_currency[curr] = rate
logger.debug("Rate retrieved : " + main_currency + ' = ' + str(rate) + ' ' + curr)
logger.debug("Rate retrieved : %s = %s %s" % (main_currency, rate, curr))
##CA BOC ##### Bank of Canada ############################################################
class CA_BOC_getter(Curreny_getter_interface) :
"""Implementation of Curreny_getter_factory interface for Bank of Canada RSS service"""
# CA BOC ##### Bank of Canada #############################################
class CA_BOC_getter(Curreny_getter_interface):
"""Implementation of Curreny_getter_factory interface
for Bank of Canada RSS service
"""
def get_updated_currency(self, currency_array, main_currency, max_delta_days):
"""implementation of abstract method of Curreny_getter_interface"""
@@ -627,7 +681,7 @@ class CA_BOC_getter(Curreny_getter_interface) :
# currencies reported):
# http://www.bankofcanada.ca/stats/assets/rates_rss/closing/en_%s.xml
#we do not want to update the main currency
# We do not want to update the main currency
if main_currency in currency_array:
currency_array.remove(main_currency)
@@ -643,7 +697,7 @@ class CA_BOC_getter(Curreny_getter_interface) :
self.validate_cur(curr)
# check if BOC service is running
if dom.bozo and dom.status <> 404:
if dom.bozo and dom.status != 404:
_logger.error("Bank of Canada - service is down - try again\
later...")
@@ -664,8 +718,7 @@ 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 : 1 " + main_currency +
' = ' + str(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)