mirror of
https://github.com/OCA/account-financial-tools.git
synced 2025-02-02 12:47:26 +02:00
Merge branch '8.0'
Conflicts: README.md __unported__/account_cancel_invoice_check_payment_order/__openerp__.py __unported__/account_cancel_invoice_check_voucher/__openerp__.py __unported__/account_compute_tax_amount/__openerp__.py __unported__/account_fiscal_position_vat_check/__openerp__.py __unported__/account_move_validation_improvement/__openerp__.py __unported__/account_reversal/__openerp__.py __unported__/currency_rate_date_check/__openerp__.py
This commit is contained in:
34
.travis.yml
34
.travis.yml
@@ -1,27 +1,33 @@
|
||||
# Config file .travis.yml
|
||||
|
||||
language: python
|
||||
|
||||
python:
|
||||
- "2.7"
|
||||
|
||||
env:
|
||||
- VERSION="8.0" ODOO_REPO="odoo/odoo" EXCLUDE="account_constraints,account_partner_required,async_move_line_importer"
|
||||
- VERSION="8.0" ODOO_REPO="odoo/odoo" INCLUDE="account_constraints"
|
||||
- VERSION="8.0" ODOO_REPO="odoo/odoo" INCLUDE="account_partner_required"
|
||||
- VERSION="8.0" ODOO_REPO="odoo/odoo" INCLUDE="async_move_line_importer"
|
||||
|
||||
- VERSION="8.0" ODOO_REPO="OCA/OCB" EXCLUDE="account_constraints, account_partner_required,async_move_line_importer"
|
||||
- VERSION="8.0" ODOO_REPO="OCA/OCB" INCLUDE="account_constraints"
|
||||
- VERSION="8.0" ODOO_REPO="OCA/OCB" INCLUDE="account_partner_required"
|
||||
- VERSION="8.0" ODOO_REPO="odoo/odoo" INCLUDE="async_move_line_importer"
|
||||
|
||||
virtualenv:
|
||||
system_site_packages: true
|
||||
|
||||
before_install:
|
||||
- git clone https://github.com/nbessi/maintainer-quality-tools.git $HOME/maintainer-quality-tools -b consolidated
|
||||
- git clone https://github.com/OCA/connector $HOME/connector -b 7.0
|
||||
|
||||
env:
|
||||
- PATH=$HOME/maintainer-quality-tools/travis:$PATH
|
||||
|
||||
services:
|
||||
- postgresql
|
||||
|
||||
install:
|
||||
- $HOME/maintainer-quality-tools/travis/travis_install_nightly 7.0
|
||||
- pip install coveralls flake8
|
||||
- git clone https://github.com/oca/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools
|
||||
- git clone https://github.com/OCA/connector $HOME/connector -b ${VERSION}
|
||||
- export PATH=${HOME}/maintainer-quality-tools/travis:${PATH}
|
||||
- travis_install_nightly ${VERSION}
|
||||
|
||||
script:
|
||||
- travis_run_flake8 steps
|
||||
- travis_run_tests_encapsulated 7.0 account $HOME/connector
|
||||
# - travis_run_flake8
|
||||
- travis_run_tests
|
||||
|
||||
after_success:
|
||||
coveralls
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
[](https://travis-ci.org/OCA/account-financial-tools)
|
||||
[](https://coveralls.io/r/OCA/account-financial-tools?branch=8.0)
|
||||
|
||||
Account financial Tools for Odoo/OpenERP
|
||||
========================================
|
||||
|
||||
|
||||
@@ -30,6 +30,6 @@ payment order.
|
||||
""",
|
||||
'website': 'http://www.camptocamp.com',
|
||||
'data': [],
|
||||
'installable': True,
|
||||
'installable': False,
|
||||
'active': False,
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ Constraint forbidding to cancel an invoice already
|
||||
imported in bank statement with a voucher.
|
||||
""",
|
||||
'website': 'http://www.camptocamp.com',
|
||||
'date' : [],
|
||||
'date': [],
|
||||
'installable': False,
|
||||
'active': False,
|
||||
}
|
||||
|
||||
@@ -32,26 +32,33 @@ class account_invoice(orm.Model):
|
||||
invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
|
||||
for invoice in invoices:
|
||||
if invoice['move_id']:
|
||||
# This invoice have a move line, we search move_line concerned by this move
|
||||
cr.execute("""SELECT abs.name AS statement_name,
|
||||
abs.date AS statement_date,
|
||||
absl.name
|
||||
FROM account_bank_statement_line AS absl
|
||||
INNER JOIN account_bank_statement AS abs
|
||||
ON absl.statement_id = abs.id
|
||||
WHERE EXISTS (SELECT 1
|
||||
FROM account_voucher_line JOIN account_move_line ON
|
||||
(account_voucher_line.move_line_id = account_move_line.id)
|
||||
WHERE voucher_id=absl.voucher_id
|
||||
AND account_move_line.move_id = %s )""",
|
||||
(invoice['move_id'][0],))
|
||||
# This invoice have a move line, we search move_line concerned
|
||||
# by this move
|
||||
cr.execute(
|
||||
"""
|
||||
SELECT abs.name AS statement_name,
|
||||
abs.date AS statement_date,
|
||||
absl.name
|
||||
FROM account_bank_statement_line AS absl
|
||||
INNER JOIN account_bank_statement AS abs
|
||||
ON absl.statement_id = abs.id
|
||||
WHERE EXISTS (SELECT 1
|
||||
FROM account_voucher_line JOIN account_move_line ON
|
||||
(account_voucher_line.move_line_id = account_move_line.id)
|
||||
WHERE voucher_id=absl.voucher_id
|
||||
AND account_move_line.move_id = %s )
|
||||
""",
|
||||
(invoice['move_id'][0],)
|
||||
)
|
||||
statement_lines = cr.dictfetchone()
|
||||
if statement_lines:
|
||||
raise osv.except_osv(
|
||||
_('Error!'),
|
||||
_('Invoice already imported in bank statment (%s) at %s on line %s'
|
||||
_('Invoice already imported in bank statment (%s) '
|
||||
'at %s on line %s'
|
||||
% (statement_lines['statement_name'],
|
||||
statement_lines['statement_date'],
|
||||
statement_lines['name'],)))
|
||||
|
||||
return super(account_invoice, self).action_cancel(cr, uid, ids, context=context)
|
||||
return super(account_invoice, self).action_cancel(cr, uid, ids,
|
||||
context=context)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (c) 2010 Zikzakmedia S.L. (http://www.zikzakmedia.com)
|
||||
# Copyright (c) 2010 Pexego Sistemas Informáticos S.L. (http://www.pexego.es)
|
||||
# Copyright (c) 2010 Pexego Sistemas Informáticos S.L.(http://www.pexego.es)
|
||||
# Copyright (c) 2013 Joaquin Gutierrez (http://www.gutierrezweb.es)
|
||||
# Pedro Manuel Baeza <pedro.baeza@serviciosbaeza.com>
|
||||
# $Id$
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (c) 2010 Zikzakmedia S.L. (http://www.zikzakmedia.com)
|
||||
# Copyright (c) 2010 Pexego Sistemas Informáticos S.L. (http://www.pexego.es)
|
||||
# Copyright (c) 2010 Pexego Sistemas Informáticos S.L.(http://www.pexego.es)
|
||||
# @authors: Jordi Esteve (Zikzakmedia), Borja López Soilán (Pexego)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -32,7 +32,8 @@ def _reopen(self, res_id, model):
|
||||
'res_id': res_id,
|
||||
'res_model': self._name,
|
||||
'target': 'new',
|
||||
# save original model in context, because selecting the list of available
|
||||
# save original model in context,
|
||||
# because selecting the list of available
|
||||
# templates requires a model in context
|
||||
'context': {
|
||||
'default_model': model,
|
||||
@@ -118,8 +119,10 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
_get_lang_selection_options,
|
||||
'Language',
|
||||
size=5,
|
||||
help="For records searched by name (taxes, tax codes, fiscal positions), "
|
||||
"the template name will be matched against the record name on this language."
|
||||
help="For records searched by name "
|
||||
"(taxes, tax codes, fiscal positions), "
|
||||
"the template name will be matched against the record "
|
||||
"name on this language."
|
||||
),
|
||||
'update_tax_code': fields.boolean(
|
||||
'Update tax codes',
|
||||
@@ -132,11 +135,13 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
),
|
||||
'update_account': fields.boolean(
|
||||
'Update accounts',
|
||||
help="Existing accounts are updated. Accounts are searched by code."
|
||||
help="Existing accounts are updated. "
|
||||
"Accounts are searched by code."
|
||||
),
|
||||
'update_fiscal_position': fields.boolean(
|
||||
'Update fiscal positions',
|
||||
help="Existing fiscal positions are updated. Fiscal positions are searched by name."
|
||||
help="Existing fiscal positions are updated. "
|
||||
"Fiscal positions are searched by name."
|
||||
),
|
||||
'update_children_accounts_parent': fields.boolean(
|
||||
"Update children accounts parent",
|
||||
@@ -163,18 +168,32 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
'Taxes',
|
||||
ondelete='cascade'
|
||||
),
|
||||
'account_ids': fields.one2many('wizard.update.charts.accounts.account',
|
||||
'update_chart_wizard_id', 'Accounts', ondelete='cascade'),
|
||||
'fiscal_position_ids': fields.one2many('wizard.update.charts.accounts.fiscal.position',
|
||||
'update_chart_wizard_id', 'Fiscal positions', ondelete='cascade'),
|
||||
'account_ids': fields.one2many(
|
||||
'wizard.update.charts.accounts.account',
|
||||
'update_chart_wizard_id',
|
||||
'Accounts',
|
||||
ondelete='cascade'
|
||||
),
|
||||
'fiscal_position_ids': fields.one2many(
|
||||
'wizard.update.charts.accounts.fiscal.position',
|
||||
'update_chart_wizard_id',
|
||||
'Fiscal positions',
|
||||
ondelete='cascade'
|
||||
),
|
||||
'new_tax_codes': fields.integer('New tax codes', readonly=True),
|
||||
'new_taxes': fields.integer('New taxes', readonly=True),
|
||||
'new_accounts': fields.integer('New accounts', readonly=True),
|
||||
'new_fps': fields.integer('New fiscal positions', readonly=True),
|
||||
'updated_tax_codes': fields.integer('Updated tax codes', readonly=True),
|
||||
'updated_tax_codes': fields.integer(
|
||||
'Updated tax codes',
|
||||
readonly=True
|
||||
),
|
||||
'updated_taxes': fields.integer('Updated taxes', readonly=True),
|
||||
'updated_accounts': fields.integer('Updated accounts', readonly=True),
|
||||
'updated_fps': fields.integer('Updated fiscal positions', readonly=True),
|
||||
'updated_fps': fields.integer(
|
||||
'Updated fiscal positions',
|
||||
readonly=True
|
||||
),
|
||||
'log': fields.text('Messages and Errors', readonly=True)
|
||||
}
|
||||
|
||||
@@ -262,8 +281,9 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
|
||||
_defaults = {
|
||||
'state': 'init',
|
||||
'company_id': lambda self, cr, uid, context: self.pool.get('res.users').browse(
|
||||
cr, uid, [uid], context)[0].company_id.id,
|
||||
'company_id': lambda self, cr, uid, context:
|
||||
self.pool.get('res.users').browse(
|
||||
cr, uid, [uid], context)[0].company_id.id,
|
||||
'chart_template_id': _get_chart,
|
||||
'update_tax_code': True,
|
||||
'update_tax': True,
|
||||
@@ -271,7 +291,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
'update_fiscal_position': True,
|
||||
'update_children_accounts_parent': True,
|
||||
'continue_on_errors': False,
|
||||
'lang': lambda self, cr, uid, context: context and context.get('lang') or None,
|
||||
'lang': lambda self, cr, uid, context:
|
||||
context and context.get('lang') or None,
|
||||
}
|
||||
|
||||
def onchange_company_id(self, cr, uid, ids, company_id, context=None):
|
||||
@@ -280,7 +301,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
"""
|
||||
res = {
|
||||
'value': {
|
||||
'code_digits': self._get_code_digits(cr, uid, context=context, company_id=company_id),
|
||||
'code_digits': self._get_code_digits(
|
||||
cr, uid, context=context, company_id=company_id),
|
||||
}
|
||||
}
|
||||
return res
|
||||
@@ -378,7 +400,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
acc_templ_mapping[acc_templ.id] = acc_ids and acc_ids[0] or False
|
||||
return acc_templ_mapping[acc_templ.id]
|
||||
|
||||
def _map_fp_template(self, cr, uid, wizard, fp_templ_mapping, fp_template, context=None):
|
||||
def _map_fp_template(self, cr, uid, wizard, fp_templ_mapping,
|
||||
fp_template, context=None):
|
||||
"""
|
||||
Adds a fiscal position template -> fiscal position id to the mapping.
|
||||
"""
|
||||
@@ -562,7 +585,9 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
for delay_vals_wiz in delay_wiz_tax:
|
||||
wiz_taxes_obj.create(cr, uid, delay_vals_wiz, context)
|
||||
|
||||
return {'new': new_taxes, 'updated': updated_taxes, 'mapping': tax_templ_mapping}
|
||||
return {'new': new_taxes,
|
||||
'updated': updated_taxes,
|
||||
'mapping': tax_templ_mapping}
|
||||
|
||||
def _find_accounts(self, cr, uid, wizard, context=None):
|
||||
"""
|
||||
@@ -614,7 +639,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
modified = False
|
||||
notes = ""
|
||||
account = acc_obj.browse(cr, uid, account_id, context=context)
|
||||
if account.name != acc_templ.name and account.name != wizard.company_id.name:
|
||||
if (account.name != acc_templ.name and
|
||||
account.name != wizard.company_id.name):
|
||||
notes += _("The name is different.\n")
|
||||
modified = True
|
||||
if account.type != acc_templ.type:
|
||||
@@ -689,14 +715,18 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
if fp_templ.tax_ids and fp.tax_ids:
|
||||
for fp_tax_templ in fp_templ.tax_ids:
|
||||
found = False
|
||||
tax_src_id = self._map_tax_template(cr, uid, wizard,
|
||||
tax_templ_mapping,
|
||||
fp_tax_templ.tax_src_id,
|
||||
context=None)
|
||||
tax_dest_id = self._map_tax_template(cr, uid, wizard,
|
||||
tax_templ_mapping,
|
||||
fp_tax_templ.tax_dest_id,
|
||||
context=None)
|
||||
tax_src_id = self._map_tax_template(
|
||||
cr, uid, wizard,
|
||||
tax_templ_mapping,
|
||||
fp_tax_templ.tax_src_id,
|
||||
context=None
|
||||
)
|
||||
tax_dest_id = self._map_tax_template(
|
||||
cr, uid, wizard,
|
||||
tax_templ_mapping,
|
||||
fp_tax_templ.tax_dest_id,
|
||||
context=None
|
||||
)
|
||||
for fp_tax in fp.tax_ids:
|
||||
if fp_tax.tax_src_id.id == tax_src_id:
|
||||
if not fp_tax.tax_dest_id:
|
||||
@@ -708,12 +738,18 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
notes += _("Tax mapping not found on the fiscal position instance: %s -> %s.\n") % (
|
||||
msg = (fp_tax_templ.tax_dest_id and
|
||||
fp_tax_templ.tax_dest_id.name or
|
||||
_('None'))
|
||||
notes += _("Tax mapping not found on the fiscal "
|
||||
"position instance: %s -> %s.\n") % (
|
||||
fp_tax_templ.tax_src_id.name,
|
||||
fp_tax_templ.tax_dest_id and fp_tax_templ.tax_dest_id.name or _('None'))
|
||||
msg
|
||||
)
|
||||
modified = True
|
||||
elif fp_templ.tax_ids and not fp.tax_ids:
|
||||
notes += _("The template has taxes the fiscal position instance does not.\n")
|
||||
notes += _("The template has taxes the fiscal "
|
||||
"position instance does not.\n")
|
||||
modified = True
|
||||
# Check fiscal position accounts for changes
|
||||
if fp_templ.account_ids and fp.account_ids:
|
||||
@@ -751,7 +787,9 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
'update_fiscal_position_id': fp_id,
|
||||
'notes': notes,
|
||||
}, context=context)
|
||||
return {'new': new_fps, 'updated': updated_fps, 'mapping': fp_templ_mapping}
|
||||
return {'new': new_fps,
|
||||
'updated': updated_fps,
|
||||
'mapping': fp_templ_mapping}
|
||||
|
||||
def action_find_records(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
@@ -822,12 +860,13 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
tax_code_template.parent_id,
|
||||
context=context)
|
||||
# Values
|
||||
p_id = tax_code_template.parent_id.id
|
||||
vals = {
|
||||
'name': tax_code_name,
|
||||
'code': tax_code_template.code,
|
||||
'info': tax_code_template.info,
|
||||
'parent_id': (tax_code_template.parent_id and
|
||||
tax_code_template_mapping.get(tax_code_template.parent_id.id)),
|
||||
tax_code_template_mapping.get(p_id)),
|
||||
'company_id': wizard.company_id.id,
|
||||
'sign': tax_code_template.sign,
|
||||
}
|
||||
@@ -847,16 +886,19 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
updated_tax_codes += 1
|
||||
modified = True
|
||||
else:
|
||||
tax_code_id = wiz_tax_code.update_tax_code_id and wiz_tax_code.update_tax_code_id.id
|
||||
tax_code_id = (wiz_tax_code.update_tax_code_id and
|
||||
wiz_tax_code.update_tax_code_id.id)
|
||||
modified = False
|
||||
# Store the tax codes on the map
|
||||
tax_code_template_mapping[tax_code_template.id] = tax_code_id
|
||||
if modified:
|
||||
# Detect errors
|
||||
p_id = tax_code_template.parent_id.id
|
||||
if (tax_code_template.parent_id and
|
||||
not tax_code_template_mapping.get(tax_code_template.parent_id.id)):
|
||||
not tax_code_template_mapping.get(p_id)):
|
||||
log.add(
|
||||
_("Tax code %s: The parent tax code %s can not be set.\n") % (
|
||||
_("Tax code %s: The parent tax code %s "
|
||||
"can not be set.\n") % (
|
||||
tax_code_name, tax_code_template.parent_id.name),
|
||||
True
|
||||
)
|
||||
@@ -866,7 +908,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
'mapping': tax_code_template_mapping
|
||||
}
|
||||
|
||||
def _update_taxes(self, cr, uid, wizard, log, tax_code_template_mapping, context=None):
|
||||
def _update_taxes(self, cr, uid, wizard, log, tax_code_template_mapping,
|
||||
context=None):
|
||||
"""
|
||||
Search for, and load, tax templates to create/update.
|
||||
"""
|
||||
@@ -887,7 +930,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
tax_template.ref_base_code_id,
|
||||
tax_template.ref_tax_code_id
|
||||
]
|
||||
for tax_code_template in [tmpl for tmpl in tax_code_templates_to_find if tmpl]:
|
||||
for tax_code_template in \
|
||||
[tmpl for tmpl in tax_code_templates_to_find if tmpl]:
|
||||
self._map_tax_code_template(cr, uid, wizard,
|
||||
tax_code_template_mapping,
|
||||
tax_code_template, context=context)
|
||||
@@ -899,21 +943,35 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
'type': tax_template.type,
|
||||
'applicable_type': tax_template.applicable_type,
|
||||
'domain': tax_template.domain,
|
||||
'parent_id': tax_template.parent_id and tax_template_mapping.get(tax_template.parent_id.id),
|
||||
'parent_id': (
|
||||
tax_template.parent_id and
|
||||
tax_template_mapping.get(tax_template.parent_id.id)
|
||||
),
|
||||
'child_depend': tax_template.child_depend,
|
||||
'python_compute': tax_template.python_compute,
|
||||
'python_compute_inv': tax_template.python_compute_inv,
|
||||
'python_applicable': tax_template.python_applicable,
|
||||
'base_code_id': (tax_template.base_code_id and
|
||||
tax_code_template_mapping.get(tax_template.base_code_id.id)),
|
||||
'tax_code_id': (tax_template.tax_code_id and
|
||||
tax_code_template_mapping.get(tax_template.tax_code_id.id)),
|
||||
'base_code_id': (
|
||||
tax_template.base_code_id and
|
||||
tax_code_template_mapping.get(tax_template.base_code_id.id)
|
||||
),
|
||||
'tax_code_id': (
|
||||
tax_template.tax_code_id and
|
||||
tax_code_template_mapping.get(tax_template.tax_code_id.id)
|
||||
),
|
||||
'base_sign': tax_template.base_sign,
|
||||
'tax_sign': tax_template.tax_sign,
|
||||
'ref_base_code_id': (tax_template.ref_base_code_id and
|
||||
tax_code_template_mapping.get(tax_template.ref_base_code_id.id)),
|
||||
'ref_tax_code_id': (tax_template.ref_tax_code_id and
|
||||
tax_code_template_mapping.get(tax_template.ref_tax_code_id.id)),
|
||||
'ref_base_code_id': (
|
||||
tax_template.ref_base_code_id and
|
||||
tax_code_template_mapping.get(
|
||||
tax_template.ref_base_code_id.id
|
||||
)
|
||||
),
|
||||
'ref_tax_code_id': (
|
||||
tax_template.ref_tax_code_id and
|
||||
tax_code_template_mapping.get(
|
||||
tax_template.ref_tax_code_id.id)
|
||||
),
|
||||
'ref_base_sign': tax_template.ref_base_sign,
|
||||
'ref_tax_sign': tax_template.ref_tax_sign,
|
||||
'include_base_amount': tax_template.include_base_amount,
|
||||
@@ -943,41 +1001,63 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
if modified:
|
||||
# Add to the dict of taxes waiting for accounts.
|
||||
taxes_pending_for_accounts[tax_id] = {
|
||||
'account_collected_id': (tax_template.account_collected_id and
|
||||
tax_template.account_collected_id.id or False),
|
||||
'account_paid_id': (tax_template.account_paid_id and
|
||||
tax_template.account_paid_id.id or False),
|
||||
'account_collected_id': (
|
||||
tax_template.account_collected_id and
|
||||
tax_template.account_collected_id.id or False
|
||||
),
|
||||
'account_paid_id': (
|
||||
tax_template.account_paid_id and
|
||||
tax_template.account_paid_id.id or False
|
||||
),
|
||||
}
|
||||
# Detect errors
|
||||
if tax_template.parent_id and not tax_template_mapping.get(tax_template.parent_id.id):
|
||||
if (tax_template.parent_id and not
|
||||
tax_template_mapping.get(tax_template.parent_id.id)):
|
||||
log.add(
|
||||
_("Tax %s: The parent tax %s can not be set.\n") % (
|
||||
tax_template.name, tax_template.parent_id.name),
|
||||
True
|
||||
)
|
||||
if tax_template.base_code_id and not tax_code_template_mapping.get(tax_template.base_code_id.id):
|
||||
if (tax_template.base_code_id and not
|
||||
tax_code_template_mapping.get(
|
||||
tax_template.base_code_id.id
|
||||
)):
|
||||
log.add(
|
||||
_("Tax %s: The tax code for the base %s can not be set.\n") % (
|
||||
_("Tax %s: The tax code for the base %s "
|
||||
"can not be set.\n") % (
|
||||
tax_template.name, tax_template.base_code_id.name),
|
||||
True
|
||||
)
|
||||
if tax_template.tax_code_id and not tax_code_template_mapping.get(tax_template.tax_code_id.id):
|
||||
if (tax_template.tax_code_id and not
|
||||
tax_code_template_mapping.get(
|
||||
tax_template.tax_code_id.id)):
|
||||
log.add(
|
||||
_("Tax %s: The tax code for the tax %s can not be set.\n") % (
|
||||
tax_template.name, tax_template.tax_code_id.name),
|
||||
_("Tax %s: The tax code for the tax %s "
|
||||
"can not be set.\n") % (
|
||||
tax_template.name, tax_template.tax_code_id.name
|
||||
),
|
||||
True
|
||||
)
|
||||
if (tax_template.ref_base_code_id and
|
||||
not tax_code_template_mapping.get(tax_template.ref_base_code_id.id)):
|
||||
not tax_code_template_mapping.get(
|
||||
tax_template.ref_base_code_id.id)):
|
||||
log.add(
|
||||
_("Tax %s: The tax code for the base refund %s can not be set.\n") % (
|
||||
tax_template.name, tax_template.ref_base_code_id.name),
|
||||
_("Tax %s: The tax code for the base refund %s "
|
||||
"can not be set.\n") % (
|
||||
tax_template.name,
|
||||
tax_template.ref_base_code_id.name
|
||||
),
|
||||
True
|
||||
)
|
||||
if tax_template.ref_tax_code_id and not tax_code_template_mapping.get(tax_template.ref_tax_code_id.id):
|
||||
if (tax_template.ref_tax_code_id and not
|
||||
tax_code_template_mapping.get(
|
||||
tax_template.ref_tax_code_id.id)):
|
||||
log.add(
|
||||
_("Tax %s: The tax code for the tax refund %s can not be set.\n") % (
|
||||
tax_template.name, tax_template.ref_tax_code_id.name),
|
||||
_("Tax %s: The tax code for the tax refund %s"
|
||||
" can not be set.\n") % (
|
||||
tax_template.name,
|
||||
tax_template.ref_tax_code_id.name
|
||||
),
|
||||
True
|
||||
)
|
||||
return {
|
||||
@@ -987,7 +1067,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
'pending': taxes_pending_for_accounts
|
||||
}
|
||||
|
||||
def _update_children_accounts_parent(self, cr, uid, wizard, log, parent_account_id, context=None):
|
||||
def _update_children_accounts_parent(self, cr, uid, wizard,
|
||||
log, parent_account_id, context=None):
|
||||
"""
|
||||
Updates the parent_id of accounts that seem to be children of the
|
||||
given account (accounts that start with the same code and are brothers
|
||||
@@ -1010,24 +1091,30 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
|
||||
if children_ids:
|
||||
try:
|
||||
account_account.write(cr, uid, children_ids, {'parent_id': parent_account.id}, context=context)
|
||||
account_account.write(cr, uid, children_ids,
|
||||
{'parent_id': parent_account.id},
|
||||
context=context)
|
||||
except orm.except_orm, ex:
|
||||
log.add(
|
||||
_("Exception setting the parent of account %s children: %s - %s.\n") % (
|
||||
parent_account.code, ex.name, ex.value),
|
||||
_("Exception setting the parent of "
|
||||
"account %s children: %s - %s.\n") % (
|
||||
parent_account.code, ex.name, ex.value
|
||||
),
|
||||
True
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def _update_accounts(self, cr, uid, wizard, log, tax_template_mapping, context=None):
|
||||
def _update_accounts(self, cr, uid, wizard, log, tax_template_mapping,
|
||||
context=None):
|
||||
"""
|
||||
Search for, and load, account templates to create/update.
|
||||
"""
|
||||
accounts = self.pool.get('account.account')
|
||||
root_account_id = wizard.chart_template_id.account_root_id.id
|
||||
# Disable the parent_store computing on account_account during the batch
|
||||
# processing, we will force _parent_store_compute afterwards.
|
||||
# Disable the parent_store computing on account_account
|
||||
# during the batch processing,
|
||||
# we will force _parent_store_compute afterwards.
|
||||
self.pool._init = True
|
||||
new_accounts = 0
|
||||
updated_accounts = 0
|
||||
@@ -1035,7 +1122,10 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
for wiz_account in wizard.account_ids:
|
||||
account_template = wiz_account.account_id
|
||||
# Ensure the parent account template is on the map.
|
||||
self._map_account_template(cr, uid, wizard, account_template_mapping, account_template.parent_id, context)
|
||||
self._map_account_template(cr, uid, wizard,
|
||||
account_template_mapping,
|
||||
account_template.parent_id,
|
||||
context)
|
||||
# Ensure the related tax templates are on the map.
|
||||
for tax_template in account_template.tax_ids:
|
||||
self._map_tax_template(cr, uid, wizard, tax_template_mapping,
|
||||
@@ -1052,17 +1142,27 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
code = '%s%s' % (
|
||||
code, '0' * (wizard.code_digits - len(code)))
|
||||
# Values
|
||||
p_id = account_template.parent_id.id
|
||||
vals = {
|
||||
'name': (root_account_id == account_template.id) and wizard.company_id.name or account_template.name,
|
||||
'currency_id': account_template.currency_id and account_template.currency_id.id or False,
|
||||
'name': ((root_account_id == account_template.id) and
|
||||
wizard.company_id.name or
|
||||
account_template.name),
|
||||
'currency_id': (account_template.currency_id and
|
||||
account_template.currency_id.id or
|
||||
False),
|
||||
'code': code,
|
||||
'type': account_template.type,
|
||||
'user_type': account_template.user_type and account_template.user_type.id or False,
|
||||
'user_type': (account_template.user_type and
|
||||
account_template.user_type.id or
|
||||
False),
|
||||
'reconcile': account_template.reconcile,
|
||||
'shortcut': account_template.shortcut,
|
||||
'note': account_template.note,
|
||||
'parent_id': (account_template.parent_id
|
||||
and account_template_mapping.get(account_template.parent_id.id) or False),
|
||||
'parent_id': (
|
||||
account_template.parent_id
|
||||
and account_template_mapping.get(p_id) or
|
||||
False
|
||||
),
|
||||
'tax_ids': [(6, 0, tax_ids)],
|
||||
'company_id': wizard.company_id.id,
|
||||
}
|
||||
@@ -1090,15 +1190,20 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
log.add(_("Exception writing account %s: %s - %s.\n")
|
||||
% (code, ex.name, ex.value), True)
|
||||
else:
|
||||
account_id = wiz_account.update_account_id and wiz_account.update_account_id.id
|
||||
account_id = (wiz_account.update_account_id and
|
||||
wiz_account.update_account_id.id)
|
||||
# Store the account on the map
|
||||
account_template_mapping[account_template.id] = account_id
|
||||
if modified:
|
||||
# Detect errors
|
||||
if account_template.parent_id and not account_template_mapping.get(account_template.parent_id.id):
|
||||
if (account_template.parent_id and not
|
||||
account_template_mapping.get(
|
||||
account_template.parent_id.id)):
|
||||
log.add(
|
||||
_("Account %s: The parent account %s can not be set.\n") % (
|
||||
code, account_template.parent_id.code),
|
||||
_("Account %s: The parent account %s "
|
||||
"can not be set.\n") % (
|
||||
code, account_template.parent_id.code
|
||||
),
|
||||
True
|
||||
)
|
||||
# Set this account as the parent of the accounts that seem to
|
||||
@@ -1142,25 +1247,40 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
if (acc_templ_mapping.get(value['account_collected_id']) and
|
||||
acc_templ_mapping.get(value['account_paid_id'])):
|
||||
vals = {
|
||||
'account_collected_id': acc_templ_mapping[value['account_collected_id']],
|
||||
'account_paid_id': acc_templ_mapping[value['account_paid_id']],
|
||||
'account_collected_id': acc_templ_mapping[
|
||||
value['account_collected_id']
|
||||
],
|
||||
'account_paid_id': acc_templ_mapping[
|
||||
value['account_paid_id']
|
||||
],
|
||||
}
|
||||
taxes.write(cr, uid, [key], vals)
|
||||
else:
|
||||
tax = taxes.browse(cr, uid, key)
|
||||
if not acc_templ_mapping.get(value['account_collected_id']):
|
||||
log.add(_("Tax %s: The collected account can not be set.\n") % (tax.name), True)
|
||||
val = value['account_collected_id']
|
||||
if not acc_templ_mapping.get(val):
|
||||
log.add(
|
||||
_("Tax %s: The collected account "
|
||||
"can not be set.\n") % (
|
||||
tax.name
|
||||
),
|
||||
True
|
||||
)
|
||||
if not acc_templ_mapping.get(value['account_paid_id']):
|
||||
log.add(_("Tax %s: The paid account can not be set.\n")
|
||||
% (tax.name), True)
|
||||
|
||||
def _update_fiscal_positions(self, cr, uid, wizard, log, tax_template_mapping, acc_templ_mapping, context=None):
|
||||
def _update_fiscal_positions(self, cr, uid, wizard, log,
|
||||
tax_template_mapping,
|
||||
acc_templ_mapping, context=None):
|
||||
"""
|
||||
Search for, and load, fiscal position templates to create/update.
|
||||
"""
|
||||
fiscalpositions = self.pool.get('account.fiscal.position')
|
||||
fiscalpositions_taxes = self.pool.get('account.fiscal.position.tax')
|
||||
fiscalpositions_account = self.pool.get('account.fiscal.position.account')
|
||||
fiscalpositions_account = self.pool.get(
|
||||
'account.fiscal.position.account'
|
||||
)
|
||||
|
||||
new_fps = 0
|
||||
updated_fps = 0
|
||||
@@ -1178,7 +1298,8 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
fp_id = fiscalpositions.create(cr, uid, vals_fp)
|
||||
new_fps += 1
|
||||
modified = True
|
||||
elif wizard.update_fiscal_position and wiz_fp.update_fiscal_position_id:
|
||||
elif (wizard.update_fiscal_position and
|
||||
wiz_fp.update_fiscal_position_id):
|
||||
# Update the given fiscal position (remove the tax and account
|
||||
# mappings, that will be regenerated later)
|
||||
fp_id = wiz_fp.update_fiscal_position_id.id
|
||||
@@ -1193,33 +1314,47 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
cr, uid, [('position_id', '=', fp_id)])
|
||||
fiscalpositions_account.unlink(cr, uid, fp_account_ids)
|
||||
else:
|
||||
fp_id = wiz_fp.update_fiscal_position_id and wiz_fp.update_fiscal_position_id.id
|
||||
fp_id = (wiz_fp.update_fiscal_position_id and
|
||||
wiz_fp.update_fiscal_position_id.id)
|
||||
|
||||
if modified:
|
||||
# (Re)create the tax mappings
|
||||
for fp_tax in fp_template.tax_ids:
|
||||
# Ensure the related tax templates are on the map.
|
||||
self._map_tax_template(cr, uid, wizard, tax_template_mapping, fp_tax.tax_src_id, context)
|
||||
self._map_tax_template(cr, uid, wizard,
|
||||
tax_template_mapping,
|
||||
fp_tax.tax_src_id, context)
|
||||
if fp_tax.tax_dest_id:
|
||||
self._map_tax_template(cr, uid, wizard, tax_template_mapping, fp_tax.tax_dest_id, context)
|
||||
self._map_tax_template(cr, uid, wizard,
|
||||
tax_template_mapping,
|
||||
fp_tax.tax_dest_id, context)
|
||||
# Create the fp tax mapping
|
||||
vals_tax = {
|
||||
'tax_src_id': tax_template_mapping.get(fp_tax.tax_src_id.id),
|
||||
'tax_dest_id': fp_tax.tax_dest_id and tax_template_mapping.get(fp_tax.tax_dest_id.id),
|
||||
'tax_src_id': tax_template_mapping.get(
|
||||
fp_tax.tax_src_id.id
|
||||
),
|
||||
'tax_dest_id': (fp_tax.tax_dest_id and
|
||||
tax_template_mapping.get(
|
||||
fp_tax.tax_dest_id.id)),
|
||||
'position_id': fp_id,
|
||||
}
|
||||
fiscalpositions_taxes.create(cr, uid, vals_tax)
|
||||
# Check for errors
|
||||
if not tax_template_mapping.get(fp_tax.tax_src_id.id):
|
||||
log.add(
|
||||
_("Fiscal position %s: The source tax %s can not be set.\n") % (
|
||||
fp_template.name, fp_tax.tax_src_id.code),
|
||||
_("Fiscal position %s: The source tax %s "
|
||||
"can not be set.\n") % (
|
||||
fp_template.name, fp_tax.tax_src_id.code
|
||||
),
|
||||
True
|
||||
)
|
||||
if fp_tax.tax_dest_id and not tax_template_mapping.get(fp_tax.tax_dest_id.id):
|
||||
if fp_tax.tax_dest_id and not tax_template_mapping.get(
|
||||
fp_tax.tax_dest_id.id):
|
||||
log.add(
|
||||
_("Fiscal position %s: The destination tax %s can not be set.\n") % (
|
||||
fp_template.name, fp_tax.tax_dest_id.name),
|
||||
_("Fiscal position %s: The destination"
|
||||
"tax %s can not be set.\n") % (
|
||||
fp_template.name, fp_tax.tax_dest_id.name
|
||||
),
|
||||
True
|
||||
)
|
||||
# (Re)create the account mappings
|
||||
@@ -1236,23 +1371,37 @@ class wizard_update_charts_accounts(orm.TransientModel):
|
||||
context=context)
|
||||
# Create the fp account mapping
|
||||
vals_account = {
|
||||
'account_src_id': acc_templ_mapping.get(fp_account.account_src_id.id),
|
||||
'account_dest_id': (fp_account.account_dest_id and
|
||||
acc_templ_mapping.get(fp_account.account_dest_id.id)),
|
||||
'account_src_id': acc_templ_mapping.get(
|
||||
fp_account.account_src_id.id
|
||||
),
|
||||
'account_dest_id': (
|
||||
fp_account.account_dest_id and
|
||||
acc_templ_mapping.get(
|
||||
fp_account.account_dest_id.id
|
||||
)
|
||||
),
|
||||
'position_id': fp_id,
|
||||
}
|
||||
fiscalpositions_account.create(cr, uid, vals_account)
|
||||
# Check for errors
|
||||
if not acc_templ_mapping.get(fp_account.account_src_id.id):
|
||||
log.add(
|
||||
_("Fiscal position %s: The source account %s can not be set.\n") % (
|
||||
fp_template.name, fp_account.account_src_id.code),
|
||||
_("Fiscal position %s: The source account %s "
|
||||
"can not be set.\n") % (
|
||||
fp_template.name,
|
||||
fp_account.account_src_id.code
|
||||
),
|
||||
True
|
||||
)
|
||||
if fp_account.account_dest_id and not acc_templ_mapping.get(fp_account.account_dest_id.id):
|
||||
if (fp_account.account_dest_id and not
|
||||
acc_templ_mapping.get(
|
||||
fp_account.account_dest_id.id)):
|
||||
log.add(
|
||||
_("Fiscal position %s: The destination account %s can not be set.\n") % (
|
||||
fp_template.name, fp_account.account_dest_id.code),
|
||||
_("Fiscal position %s: The destination account %s "
|
||||
"can not be set.\n") % (
|
||||
fp_template.name,
|
||||
fp_account.account_dest_id.code
|
||||
),
|
||||
True
|
||||
)
|
||||
|
||||
@@ -1363,15 +1512,28 @@ class wizard_update_charts_accounts_tax(orm.TransientModel):
|
||||
"""
|
||||
_name = 'wizard.update.charts.accounts.tax'
|
||||
_columns = {
|
||||
'tax_id': fields.many2one('account.tax.template', 'Tax template', required=True, ondelete='set null'),
|
||||
'tax_id': fields.many2one(
|
||||
'account.tax.template',
|
||||
'Tax template',
|
||||
required=True,
|
||||
ondelete='set null'
|
||||
),
|
||||
'update_chart_wizard_id': fields.many2one(
|
||||
'wizard.update.charts.accounts', 'Update chart wizard', required=True, ondelete='cascade'),
|
||||
'wizard.update.charts.accounts',
|
||||
'Update chart wizard',
|
||||
required=True,
|
||||
ondelete='cascade'
|
||||
),
|
||||
'type': fields.selection([
|
||||
('new', 'New template'),
|
||||
('updated', 'Updated template'),
|
||||
], 'Type'),
|
||||
'update_tax_id': fields.many2one(
|
||||
'account.tax', 'Tax to update', required=False, ondelete='set null'),
|
||||
'account.tax',
|
||||
'Tax to update',
|
||||
required=False,
|
||||
ondelete='set null'
|
||||
),
|
||||
'notes': fields.text('Notes'),
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"author": "Camptocamp",
|
||||
"description": """Recompute tax_amount to avoid sign problem""",
|
||||
'website': 'http://www.camptocamp.com',
|
||||
'data' : [],
|
||||
'data': [],
|
||||
'installable': False,
|
||||
'active': False,
|
||||
}
|
||||
|
||||
@@ -30,14 +30,16 @@ class account_move_line(orm.Model):
|
||||
|
||||
# We set the tax_amount invisible, because we recompute it in every case.
|
||||
_columns = {
|
||||
'tax_amount': fields.float('Tax/Base Amount',
|
||||
digits_compute=dp.get_precision('Account'),
|
||||
invisible=True,
|
||||
select=True,
|
||||
help="If the Tax account is a tax code account, "
|
||||
"this field will contain the taxed amount. "
|
||||
"If the tax account is base tax code, "
|
||||
"this field will contain the basic amount (without tax)."),
|
||||
'tax_amount': fields.float(
|
||||
'Tax/Base Amount',
|
||||
digits_compute=dp.get_precision('Account'),
|
||||
invisible=True,
|
||||
select=True,
|
||||
help="If the Tax account is a tax code account, "
|
||||
"this field will contain the taxed amount. "
|
||||
"If the tax account is base tax code, "
|
||||
"this field will contain the basic amount (without tax)."
|
||||
),
|
||||
}
|
||||
|
||||
def create(self, cr, uid, vals, context=None, check=True):
|
||||
@@ -55,11 +57,14 @@ class account_move_line(orm.Model):
|
||||
context=context)
|
||||
return result
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
|
||||
result = super(account_move_line, self).write(cr, uid, ids, vals,
|
||||
context=context,
|
||||
check=check,
|
||||
update_check=update_check)
|
||||
def write(self, cr, uid, ids, vals, context=None, check=True,
|
||||
update_check=True):
|
||||
result = super(account_move_line, self).write(
|
||||
cr, uid, ids, vals,
|
||||
context=context,
|
||||
check=check,
|
||||
update_check=update_check
|
||||
)
|
||||
if result:
|
||||
if ('debit' in vals) or ('credit' in vals):
|
||||
move_lines = self.read(cr, uid, ids,
|
||||
|
||||
@@ -51,8 +51,8 @@ Summary of constraints are:
|
||||
invoice or a bank statement, no matter what the status of the move
|
||||
(draft, validated or posted). This is useful in a standard context but
|
||||
even more if you're using `account_default_draft_move`. This way you ensure
|
||||
that the user cannot make mistakes even in draft state, he must pass through the
|
||||
parent object to make his modification.
|
||||
that the user cannot make mistakes even in draft state, he must pass
|
||||
through the parent object to make his modification.
|
||||
|
||||
Contributors
|
||||
* Stéphane Bidoul <stephane.bidoul@acsone.eu>
|
||||
|
||||
@@ -62,9 +62,12 @@ class AccountMoveLine(orm.Model):
|
||||
_inherit = 'account.move.line'
|
||||
|
||||
def _authorized_reconcile(self, vals):
|
||||
""" Check if only reconcile_id and/or reconcile_partial_id are altered. We
|
||||
cannot change other vals, but we should be able to write or unlink those field
|
||||
(e.g. when you want to manually unreconcile an entry generated by an invoice).
|
||||
""" Check if only reconcile_id and/or reconcile_partial_id are altered.
|
||||
We cannot change other vals, but we should be able to write or unlink
|
||||
those field.
|
||||
e.g. when you want to manually unreconcile an entry
|
||||
generated by an invoice
|
||||
|
||||
"""
|
||||
if not vals:
|
||||
return False
|
||||
@@ -72,7 +75,8 @@ class AccountMoveLine(orm.Model):
|
||||
write_keys = set(vals)
|
||||
return rec_keys.issuperset(write_keys)
|
||||
|
||||
def _check_invoice_related_move(self, cr, uid, ids, vals=None, context=None):
|
||||
def _check_invoice_related_move(self, cr, uid, ids, vals=None,
|
||||
context=None):
|
||||
for line in self.browse(cr, uid, ids, context=context):
|
||||
if line.invoice:
|
||||
if self._authorized_reconcile(vals):
|
||||
@@ -81,11 +85,14 @@ class AccountMoveLine(orm.Model):
|
||||
(line.invoice.name, line.invoice.id))
|
||||
raise osv.except_osv(
|
||||
_('Error'),
|
||||
_('You cannot do this on an entry generated by an invoice. You must '
|
||||
'change the related invoice directly.\n%s.') % err_msg)
|
||||
_('You cannot do this on an entry generated '
|
||||
'by an invoice. You must change the related '
|
||||
'invoice directly.\n%s.') % err_msg
|
||||
)
|
||||
return True
|
||||
|
||||
def _check_statement_related_move(self, cr, uid, ids, vals=None, context=None):
|
||||
def _check_statement_related_move(self, cr, uid, ids, vals=None,
|
||||
context=None):
|
||||
for line in self.browse(cr, uid, ids, context=context):
|
||||
if line.statement_id:
|
||||
if self._authorized_reconcile(vals):
|
||||
@@ -94,8 +101,10 @@ class AccountMoveLine(orm.Model):
|
||||
(line.statement_id.name, line.statement_id.id))
|
||||
raise osv.except_osv(
|
||||
_('Error'),
|
||||
_('You cannot do this on an entry generated by a bank statement. '
|
||||
'You must change the related bank statement directly.\n%s.') % err_msg)
|
||||
_('You cannot do this on an entry generated '
|
||||
'by a bank statement. You must change the related'
|
||||
'bank statement directly.\n%s.') % err_msg
|
||||
)
|
||||
return True
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None, check=True):
|
||||
@@ -103,7 +112,8 @@ class AccountMoveLine(orm.Model):
|
||||
|
||||
- Is the move related to an invoice
|
||||
- Is the move related to a bank statement
|
||||
- Is other values than reconcile_partial_id and/or reconcile_id modified
|
||||
- Is other values than reconcile_partial_id and/or
|
||||
reconcile_id modified
|
||||
|
||||
In that case, we forbid the move to be deleted even if draft. We
|
||||
should never delete directly a move line related or generated by
|
||||
@@ -115,14 +125,18 @@ class AccountMoveLine(orm.Model):
|
||||
if not context.get('from_parent_object', False):
|
||||
self._check_invoice_related_move(cr, uid, ids, context=context)
|
||||
self._check_statement_related_move(cr, uid, ids, context=context)
|
||||
return super(AccountMoveLine, self).unlink(cr, uid, ids, context=context, check=check)
|
||||
return super(AccountMoveLine, self).unlink(cr, uid, ids,
|
||||
context=context,
|
||||
check=check)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
|
||||
def write(self, cr, uid, ids, vals, context=None, check=True,
|
||||
update_check=True):
|
||||
""" Add the following checks:
|
||||
|
||||
- Is the move related to an invoice
|
||||
- Is the move related to a bank statement
|
||||
- Is other values than reconcile_partial_id and/or reconcile_id modified
|
||||
- Is other values than reconcile_partial_id and/or
|
||||
reconcile_id modified
|
||||
|
||||
In that case, we forbid the move to be modified even if draft.
|
||||
We should never update directly a move line related or generated
|
||||
@@ -132,8 +146,10 @@ class AccountMoveLine(orm.Model):
|
||||
if context is None:
|
||||
context = {}
|
||||
if not context.get('from_parent_object', False):
|
||||
self._check_invoice_related_move(cr, uid, ids, vals, context=context)
|
||||
self._check_statement_related_move(cr, uid, ids, vals, context=context)
|
||||
self._check_invoice_related_move(cr, uid, ids, vals,
|
||||
context=context)
|
||||
self._check_statement_related_move(cr, uid, ids, vals,
|
||||
context=context)
|
||||
return super(AccountMoveLine, self).write(
|
||||
cr, uid, ids, vals,
|
||||
context=context, check=check, update_check=update_check
|
||||
@@ -194,7 +210,8 @@ class AccountInvoice(orm.Model):
|
||||
else:
|
||||
context = context.copy()
|
||||
context['from_parent_object'] = True
|
||||
return super(AccountInvoice, self).action_cancel(cr, uid, ids, context=context)
|
||||
return super(AccountInvoice, self).action_cancel(cr, uid, ids,
|
||||
context=context)
|
||||
|
||||
def action_move_create(self, cr, uid, ids, context=None):
|
||||
"""Override the method to add the key 'from_parent_object' in
|
||||
@@ -204,7 +221,8 @@ class AccountInvoice(orm.Model):
|
||||
else:
|
||||
context = context.copy()
|
||||
context['from_parent_object'] = True
|
||||
return super(AccountInvoice, self).action_move_create(cr, uid, ids, context=context)
|
||||
return super(AccountInvoice, self).action_move_create(cr, uid, ids,
|
||||
context=context)
|
||||
|
||||
|
||||
class AccountBankStatement(orm.Model):
|
||||
@@ -220,9 +238,11 @@ class AccountBankStatement(orm.Model):
|
||||
else:
|
||||
context = context.copy()
|
||||
context['from_parent_object'] = True
|
||||
return super(AccountBankStatement, self).button_cancel(cr, uid, ids, context=context)
|
||||
return super(AccountBankStatement, self).button_cancel(cr, uid, ids,
|
||||
context=context)
|
||||
|
||||
def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id,
|
||||
def create_move_from_st_line(self, cr, uid, st_line_id,
|
||||
company_currency_id,
|
||||
st_line_number, context=None):
|
||||
"""Add the from_parent_object key in context in order to be able
|
||||
to post the move.
|
||||
|
||||
@@ -44,7 +44,8 @@ class CreditControlLine(orm.Model):
|
||||
required=True,
|
||||
select=True
|
||||
),
|
||||
# maturity date of related move line we do not use a related field in order to
|
||||
# maturity date of related move line we do not use
|
||||
# a related field in order to
|
||||
# allow manual changes
|
||||
'date_due': fields.date(
|
||||
'Due date',
|
||||
@@ -196,7 +197,8 @@ class CreditControlLine(orm.Model):
|
||||
data['date_due'] = move_line.date_maturity
|
||||
data['state'] = 'draft'
|
||||
data['channel'] = level.channel
|
||||
data['invoice_id'] = move_line.invoice.id if move_line.invoice else False
|
||||
data['invoice_id'] = (move_line.invoice.id if
|
||||
move_line.invoice else False)
|
||||
data['partner_id'] = move_line.partner_id.id
|
||||
data['amount_due'] = (move_line.amount_currency or move_line.debit or
|
||||
move_line.credit)
|
||||
@@ -282,7 +284,9 @@ class CreditControlLine(orm.Model):
|
||||
if line.state != 'draft':
|
||||
raise orm.except_orm(
|
||||
_('Error !'),
|
||||
_('You are not allowed to delete a credit control line that '
|
||||
'is not in draft state.'))
|
||||
_('You are not allowed to delete a credit control '
|
||||
'line that is not in draft state.')
|
||||
)
|
||||
|
||||
return super(CreditControlLine, self).unlink(cr, uid, ids, context=context)
|
||||
return super(CreditControlLine, self).unlink(cr, uid, ids,
|
||||
context=context)
|
||||
|
||||
@@ -63,7 +63,8 @@ class CreditControlPolicy(orm.Model):
|
||||
'active': True,
|
||||
}
|
||||
|
||||
def _move_lines_domain(self, cr, uid, policy, controlling_date, context=None):
|
||||
def _move_lines_domain(self, cr, uid, policy, controlling_date,
|
||||
context=None):
|
||||
"""Build the default domain for searching move lines"""
|
||||
account_ids = [a.id for a in policy.account_ids]
|
||||
return [('account_id', 'in', account_ids),
|
||||
@@ -74,8 +75,8 @@ class CreditControlPolicy(orm.Model):
|
||||
def _due_move_lines(self, cr, uid, policy, controlling_date, context=None):
|
||||
""" Get the due move lines for the policy of the company.
|
||||
|
||||
The set of ids will be reduced and extended according to the specific policies
|
||||
defined on partners and invoices.
|
||||
The set of ids will be reduced and extended according
|
||||
to the specific policies defined on partners and invoices.
|
||||
|
||||
Do not use direct SQL in order to respect security rules.
|
||||
|
||||
@@ -88,7 +89,8 @@ class CreditControlPolicy(orm.Model):
|
||||
return set()
|
||||
|
||||
domain_line = self._move_lines_domain(cr, uid, policy,
|
||||
controlling_date, context=context)
|
||||
controlling_date,
|
||||
context=context)
|
||||
return set(move_l_obj.search(cr, uid, domain_line, context=context))
|
||||
|
||||
def _move_lines_subset(self, cr, uid, policy, controlling_date,
|
||||
@@ -131,7 +133,8 @@ class CreditControlPolicy(orm.Model):
|
||||
if add_obj_ids:
|
||||
domain = list(default_domain)
|
||||
domain.append((move_relation_field, 'in', add_obj_ids))
|
||||
to_add_ids = set(move_l_obj.search(cr, uid, domain, context=context))
|
||||
to_add_ids = set(move_l_obj.search(cr, uid, domain,
|
||||
context=context))
|
||||
|
||||
# The lines which are linked to another policy do not have to be
|
||||
# included in the run for this policy.
|
||||
@@ -143,10 +146,12 @@ class CreditControlPolicy(orm.Model):
|
||||
if neg_obj_ids:
|
||||
domain = list(default_domain)
|
||||
domain.append((move_relation_field, 'in', neg_obj_ids))
|
||||
to_remove_ids = set(move_l_obj.search(cr, uid, domain, context=context))
|
||||
to_remove_ids = set(move_l_obj.search(cr, uid, domain,
|
||||
context=context))
|
||||
return to_add_ids, to_remove_ids
|
||||
|
||||
def _get_partner_related_lines(self, cr, uid, policy, controlling_date, context=None):
|
||||
def _get_partner_related_lines(self, cr, uid, policy, controlling_date,
|
||||
context=None):
|
||||
""" Get the move lines for a policy related to a partner.
|
||||
|
||||
:param browse_record policy: policy
|
||||
@@ -158,9 +163,11 @@ class CreditControlPolicy(orm.Model):
|
||||
the process
|
||||
"""
|
||||
return self._move_lines_subset(cr, uid, policy, controlling_date,
|
||||
'res.partner', 'partner_id', context=context)
|
||||
'res.partner', 'partner_id',
|
||||
context=context)
|
||||
|
||||
def _get_invoice_related_lines(self, cr, uid, policy, controlling_date, context=None):
|
||||
def _get_invoice_related_lines(self, cr, uid, policy, controlling_date,
|
||||
context=None):
|
||||
""" Get the move lines for a policy related to an invoice.
|
||||
|
||||
:param browse_record policy: policy
|
||||
@@ -172,10 +179,13 @@ class CreditControlPolicy(orm.Model):
|
||||
the process
|
||||
"""
|
||||
return self._move_lines_subset(cr, uid, policy, controlling_date,
|
||||
'account.invoice', 'invoice', context=context)
|
||||
'account.invoice', 'invoice',
|
||||
context=context)
|
||||
|
||||
def _get_move_lines_to_process(self, cr, uid, policy_id, controlling_date, context=None):
|
||||
"""Build a list of move lines ids to include in a run for a policy at a given date.
|
||||
def _get_move_lines_to_process(self, cr, uid, policy_id, controlling_date,
|
||||
context=None):
|
||||
"""Build a list of move lines ids to include in a run
|
||||
for a policy at a given date.
|
||||
|
||||
:param int/long policy: id of the policy
|
||||
:param str controlling_date: date of credit control
|
||||
@@ -189,7 +199,8 @@ class CreditControlPolicy(orm.Model):
|
||||
policy = self.browse(cr, uid, policy_id, context=context)
|
||||
# there is a priority between the lines, depicted by the calls below
|
||||
# warning, side effect method called on lines
|
||||
lines = self._due_move_lines(cr, uid, policy, controlling_date, context=context)
|
||||
lines = self._due_move_lines(cr, uid, policy, controlling_date,
|
||||
context=context)
|
||||
add_ids, remove_ids = self._get_partner_related_lines(cr, uid, policy,
|
||||
controlling_date,
|
||||
context=context)
|
||||
@@ -234,8 +245,11 @@ class CreditControlPolicy(orm.Model):
|
||||
if policy not in allowed:
|
||||
allowed_names = u"\n".join(x.name for x in allowed)
|
||||
raise orm.except_orm(
|
||||
_('You can only use a policy set on account %s') % account.name,
|
||||
_("Please choose one of the following policies:\n %s") % allowed_names)
|
||||
_('You can only use a policy set on '
|
||||
'account %s') % account.name,
|
||||
_("Please choose one of the following "
|
||||
"policies:\n %s") % allowed_names
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
@@ -253,19 +267,24 @@ class CreditControlPolicyLevel(orm.Model):
|
||||
translate=True),
|
||||
'level': fields.integer('Level', required=True),
|
||||
|
||||
'computation_mode': fields.selection([('net_days', 'Due Date'),
|
||||
('end_of_month', 'Due Date, End Of Month'),
|
||||
('previous_date', 'Previous Reminder')],
|
||||
'Compute Mode',
|
||||
required=True),
|
||||
'computation_mode': fields.selection(
|
||||
[('net_days', 'Due Date'),
|
||||
('end_of_month', 'Due Date, End Of Month'),
|
||||
('previous_date', 'Previous Reminder')],
|
||||
'Compute Mode',
|
||||
required=True
|
||||
),
|
||||
|
||||
'delay_days': fields.integer('Delay (in days)', required='True'),
|
||||
'email_template_id': fields.many2one('email.template', 'Email Template',
|
||||
'email_template_id': fields.many2one('email.template',
|
||||
'Email Template',
|
||||
required=True),
|
||||
'channel': fields.selection([('letter', 'Letter'),
|
||||
('email', 'Email')],
|
||||
'Channel', required=True),
|
||||
'custom_text': fields.text('Custom Message', required=True, translate=True),
|
||||
'custom_text': fields.text('Custom Message',
|
||||
required=True,
|
||||
translate=True),
|
||||
'custom_mail_text': fields.text('Custom Mail Message',
|
||||
required=True, translate=True),
|
||||
|
||||
@@ -281,7 +300,8 @@ class CreditControlPolicyLevel(orm.Model):
|
||||
cr, uid,
|
||||
[('policy_id', '=', level.policy_id.id)],
|
||||
order='level asc', limit=1, context=context)
|
||||
smallest_level = self.browse(cr, uid, smallest_level_id[0], context)
|
||||
smallest_level = self.browse(cr, uid, smallest_level_id[0],
|
||||
context)
|
||||
if smallest_level.computation_mode == 'previous_date':
|
||||
return False
|
||||
return True
|
||||
@@ -316,16 +336,20 @@ class CreditControlPolicyLevel(orm.Model):
|
||||
# ----- sql time related methods ---------
|
||||
|
||||
def _net_days_get_boundary(self):
|
||||
return " (mv_line.date_maturity + %(delay)s)::date <= date(%(controlling_date)s)"
|
||||
return " (mv_line.date_maturity + %(delay)s)::date <= "
|
||||
"date(%(controlling_date)s)"
|
||||
|
||||
def _end_of_month_get_boundary(self):
|
||||
return ("(date_trunc('MONTH', (mv_line.date_maturity + %(delay)s))+INTERVAL '1 MONTH - 1 day')::date"
|
||||
return ("(date_trunc('MONTH', (mv_line.date_maturity + %(delay)s))+"
|
||||
"INTERVAL '1 MONTH - 1 day')::date"
|
||||
"<= date(%(controlling_date)s)")
|
||||
|
||||
def _previous_date_get_boundary(self):
|
||||
return "(cr_line.date + %(delay)s)::date <= date(%(controlling_date)s)"
|
||||
|
||||
def _get_sql_date_boundary_for_computation_mode(self, cr, uid, level, controlling_date, context=None):
|
||||
def _get_sql_date_boundary_for_computation_mode(self, cr, uid, level,
|
||||
controlling_date,
|
||||
context=None):
|
||||
"""Return a where clauses statement for the given
|
||||
controlling date and computation mode of the level"""
|
||||
fname = "_%s_get_boundary" % (level.computation_mode,)
|
||||
@@ -333,12 +357,15 @@ class CreditControlPolicyLevel(orm.Model):
|
||||
fnc = getattr(self, fname)
|
||||
return fnc()
|
||||
else:
|
||||
raise NotImplementedError(_('Can not get function for computation mode: '
|
||||
'%s is not implemented') % (fname,))
|
||||
raise NotImplementedError(
|
||||
_('Can not get function for computation mode: '
|
||||
'%s is not implemented') % (fname,)
|
||||
)
|
||||
|
||||
# -----------------------------------------
|
||||
|
||||
def _get_first_level_move_line_ids(self, cr, uid, level, controlling_date, lines, context=None):
|
||||
def _get_first_level_move_line_ids(self, cr, uid, level, controlling_date,
|
||||
lines, context=None):
|
||||
"""Retrieve all the move lines that are linked to a first level.
|
||||
We use Raw SQL for performance. Security rule where applied in
|
||||
policy object when the first set of lines were retrieved"""
|
||||
@@ -358,8 +385,10 @@ class CreditControlPolicyLevel(orm.Model):
|
||||
" AND state NOT IN ('draft', 'ignored'))"
|
||||
" AND (mv_line.debit IS NOT NULL AND mv_line.debit != 0.0)\n")
|
||||
sql += " AND"
|
||||
sql += self._get_sql_date_boundary_for_computation_mode(cr, uid, level,
|
||||
controlling_date, context)
|
||||
sql += self._get_sql_date_boundary_for_computation_mode(
|
||||
cr, uid, level,
|
||||
controlling_date, context
|
||||
)
|
||||
data_dict = {'controlling_date': controlling_date,
|
||||
'line_ids': tuple(lines),
|
||||
'delay': level.delay_days}
|
||||
@@ -369,7 +398,8 @@ class CreditControlPolicyLevel(orm.Model):
|
||||
level_lines.update([x[0] for x in res])
|
||||
return level_lines
|
||||
|
||||
def _get_other_level_move_line_ids(self, cr, uid, level, controlling_date, lines, context=None):
|
||||
def _get_other_level_move_line_ids(self, cr, uid, level, controlling_date,
|
||||
lines, context=None):
|
||||
""" Retrieve the move lines for other levels than first level.
|
||||
"""
|
||||
level_lines = set()
|
||||
@@ -379,11 +409,12 @@ class CreditControlPolicyLevel(orm.Model):
|
||||
" FROM account_move_line mv_line\n"
|
||||
" JOIN credit_control_line cr_line\n"
|
||||
" ON (mv_line.id = cr_line.move_line_id)\n"
|
||||
" WHERE cr_line.id = (SELECT credit_control_line.id FROM credit_control_line\n"
|
||||
" WHERE credit_control_line.move_line_id = mv_line.id\n"
|
||||
" AND state != 'ignored'"
|
||||
" AND NOT manually_overridden"
|
||||
" ORDER BY credit_control_line.level desc limit 1)\n"
|
||||
" WHERE cr_line.id = (SELECT credit_control_line.id "
|
||||
" FROM credit_control_line\n"
|
||||
" WHERE credit_control_line.move_line_id = mv_line.id\n"
|
||||
" AND state != 'ignored'"
|
||||
" AND NOT manually_overridden"
|
||||
" ORDER BY credit_control_line.level desc limit 1)\n"
|
||||
" AND cr_line.level = %(previous_level)s\n"
|
||||
" AND (mv_line.debit IS NOT NULL AND mv_line.debit != 0.0)\n"
|
||||
# lines from a previous level with a draft or ignored state
|
||||
@@ -393,10 +424,15 @@ class CreditControlPolicyLevel(orm.Model):
|
||||
" AND cr_line.state NOT IN ('draft', 'ignored')\n"
|
||||
" AND mv_line.id in %(line_ids)s\n")
|
||||
sql += " AND "
|
||||
sql += self._get_sql_date_boundary_for_computation_mode(cr, uid, level,
|
||||
controlling_date, context)
|
||||
previous_level_id = self._previous_level(cr, uid, level, context=context)
|
||||
previous_level = self.browse(cr, uid, previous_level_id, context=context)
|
||||
sql += self._get_sql_date_boundary_for_computation_mode(
|
||||
cr, uid, level,
|
||||
controlling_date,
|
||||
context
|
||||
)
|
||||
previous_level_id = self._previous_level(cr, uid, level,
|
||||
context=context)
|
||||
previous_level = self.browse(cr, uid, previous_level_id,
|
||||
context=context)
|
||||
data_dict = {'controlling_date': controlling_date,
|
||||
'line_ids': tuple(lines),
|
||||
'delay': level.delay_days,
|
||||
@@ -409,7 +445,8 @@ class CreditControlPolicyLevel(orm.Model):
|
||||
level_lines.update([x[0] for x in res])
|
||||
return level_lines
|
||||
|
||||
def get_level_lines(self, cr, uid, level_id, controlling_date, lines, context=None):
|
||||
def get_level_lines(self, cr, uid, level_id, controlling_date, lines,
|
||||
context=None):
|
||||
"""get all move lines in entry lines that match the current level"""
|
||||
assert not (isinstance(level_id, list) and len(level_id) > 1), \
|
||||
"level_id: only one id expected"
|
||||
|
||||
@@ -25,7 +25,8 @@ from openerp.report import report_sxw
|
||||
|
||||
class CreditSummaryReport(report_sxw.rml_parse):
|
||||
def __init__(self, cr, uid, name, context):
|
||||
super(CreditSummaryReport, self).__init__(cr, uid, name, context=context)
|
||||
super(CreditSummaryReport, self).__init__(cr, uid, name,
|
||||
context=context)
|
||||
self.localcontext.update({
|
||||
'time': time,
|
||||
'cr': cr,
|
||||
|
||||
@@ -76,13 +76,17 @@ class CreditControlRun(orm.Model):
|
||||
cr, uid, id, default=default, context=context)
|
||||
|
||||
def _get_policies(self, cr, uid, context=None):
|
||||
return self.pool['credit.control.policy'].search(cr, uid, [], context=context)
|
||||
return self.pool['credit.control.policy'].search(cr, uid, [],
|
||||
context=context)
|
||||
|
||||
_defaults = {'state': 'draft',
|
||||
'policy_ids': _get_policies}
|
||||
|
||||
def _check_run_date(self, cr, uid, ids, controlling_date, context=None):
|
||||
"""Ensure that there is no credit line in the future using controlling_date"""
|
||||
"""Ensure that there is no credit line in the future
|
||||
using controlling_date
|
||||
|
||||
"""
|
||||
run_obj = self.pool['credit.control.run']
|
||||
runs = run_obj.search(cr, uid, [('date', '>', controlling_date)],
|
||||
order='date DESC', limit=1, context=context)
|
||||
@@ -126,25 +130,38 @@ class CreditControlRun(orm.Model):
|
||||
for policy in policies:
|
||||
if policy.do_nothing:
|
||||
continue
|
||||
lines = policy._get_move_lines_to_process(run.date, context=context)
|
||||
manual_lines = policy._lines_different_policy(lines, context=context)
|
||||
lines = policy._get_move_lines_to_process(run.date,
|
||||
context=context)
|
||||
manual_lines = policy._lines_different_policy(lines,
|
||||
context=context)
|
||||
lines.difference_update(manual_lines)
|
||||
manually_managed_lines.update(manual_lines)
|
||||
policy_generated_ids = []
|
||||
if lines:
|
||||
# policy levels are sorted by level so iteration is in the correct order
|
||||
# policy levels are sorted by level
|
||||
# so iteration is in the correct order
|
||||
for level in reversed(policy.level_ids):
|
||||
level_lines = level.get_level_lines(run.date, lines, context=context)
|
||||
policy_generated_ids += cr_line_obj.create_or_update_from_mv_lines(
|
||||
cr, uid, [], list(level_lines), level.id, run.date, context=context)
|
||||
level_lines = level.get_level_lines(run.date, lines,
|
||||
context=context)
|
||||
policy_generated_ids += \
|
||||
cr_line_obj.create_or_update_from_mv_lines(
|
||||
cr, uid,
|
||||
[],
|
||||
list(level_lines),
|
||||
level.id,
|
||||
run.date,
|
||||
context=context
|
||||
)
|
||||
generated_ids.extend(policy_generated_ids)
|
||||
if policy_generated_ids:
|
||||
report += _("Policy \"%s\" has generated %d Credit Control Lines.\n") % \
|
||||
(policy.name, len(policy_generated_ids))
|
||||
credit_line_ids += policy_generated_ids
|
||||
else:
|
||||
report += _("Policy \"%s\" has not generated any Credit Control Lines.\n" %
|
||||
policy.name)
|
||||
report += _(
|
||||
"Policy \"%s\" has not generated any "
|
||||
"Credit Control Lines.\n" % policy.name
|
||||
)
|
||||
|
||||
vals = {'state': 'done',
|
||||
'report': report,
|
||||
@@ -162,7 +179,8 @@ class CreditControlRun(orm.Model):
|
||||
cr.execute('SELECT id FROM credit_control_run'
|
||||
' LIMIT 1 FOR UPDATE NOWAIT')
|
||||
except Exception:
|
||||
# In case of exception openerp will do a rollback for us and free the lock
|
||||
# In case of exception openerp will do a rollback
|
||||
# for us and free the lock
|
||||
raise orm.except_orm(_('Error'),
|
||||
_('A credit control run is already running'
|
||||
' in background, please try later.'))
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# flake8: noqa
|
||||
import time
|
||||
from behave import given, when
|
||||
from support import model
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# flake8: noqa
|
||||
@given(u'I change level for invoice "{invoice_name}" to "{level_name}" of policy "{policy_name}"')
|
||||
def impl(ctx, invoice_name, level_name, policy_name):
|
||||
invoice = model('account.invoice').get([('number', '=', invoice_name)])
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# flake8: noqa
|
||||
from support import *
|
||||
import datetime
|
||||
|
||||
|
||||
@@ -27,34 +27,57 @@ logger = logging.getLogger('credit.control.line.mailing')
|
||||
|
||||
class CreditCommunication(TransientModel):
|
||||
"""Shell class used to provide a base model to email template and reporting.
|
||||
Il use this approche in version 7 a browse record will exist even if not saved"""
|
||||
Il use this approche in version 7 a browse record
|
||||
will exist even if not saved
|
||||
|
||||
"""
|
||||
_name = "credit.control.communication"
|
||||
_description = "credit control communication"
|
||||
_rec_name = 'partner_id'
|
||||
_columns = {'partner_id': fields.many2one('res.partner', 'Partner', required=True),
|
||||
_columns = {
|
||||
'partner_id': fields.many2one(
|
||||
'res.partner',
|
||||
'Partner',
|
||||
required=True
|
||||
),
|
||||
|
||||
'current_policy_level': fields.many2one('credit.control.policy.level',
|
||||
'Level', required=True),
|
||||
'current_policy_level': fields.many2one(
|
||||
'credit.control.policy.level',
|
||||
'Level',
|
||||
required=True
|
||||
),
|
||||
|
||||
'credit_control_line_ids': fields.many2many('credit.control.line',
|
||||
rel='comm_credit_rel',
|
||||
string='Credit Lines'),
|
||||
'credit_control_line_ids': fields.many2many(
|
||||
'credit.control.line',
|
||||
rel='comm_credit_rel',
|
||||
string='Credit Lines'
|
||||
),
|
||||
|
||||
'company_id': fields.many2one('res.company', 'Company',
|
||||
required=True),
|
||||
'company_id': fields.many2one(
|
||||
'res.company',
|
||||
'Company',
|
||||
required=True
|
||||
),
|
||||
|
||||
'user_id': fields.many2one('res.users', 'User')}
|
||||
'user_id': fields.many2one('res.users', 'User')
|
||||
}
|
||||
|
||||
def _get_comp(self, cr, uid, context=None):
|
||||
self.pool.get('res.company')._company_default_get(
|
||||
cr, uid,
|
||||
'credit.control.policy',
|
||||
context=context
|
||||
)
|
||||
|
||||
_defaults = {
|
||||
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(
|
||||
cr, uid, 'credit.control.policy', context=c),
|
||||
'company_id': _get_comp,
|
||||
'user_id': lambda s, cr, uid, c: uid
|
||||
}
|
||||
|
||||
def get_email(self, cr, uid, com_id, context=None):
|
||||
"""Return a valid email for customer"""
|
||||
if isinstance(com_id, list):
|
||||
assert len(com_id) == 1, "get_email only support one id as parameter"
|
||||
assert len(com_id) == 1, "get_email only support one id as param."
|
||||
com_id = com_id[0]
|
||||
form = self.browse(cr, uid, com_id, context=context)
|
||||
contact = form.get_contact_address()
|
||||
@@ -70,7 +93,8 @@ class CreditCommunication(TransientModel):
|
||||
add_id = add_ids.get('invoice', add_ids.get('default', False))
|
||||
return pmod.browse(cr, uid, add_id, context)
|
||||
|
||||
def _get_credit_lines(self, cr, uid, line_ids, partner_id, level_id, context=None):
|
||||
def _get_credit_lines(self, cr, uid, line_ids, partner_id, level_id,
|
||||
context=None):
|
||||
"""Return credit lines related to a partner and a policy level"""
|
||||
cr_line_obj = self.pool.get('credit.control.line')
|
||||
cr_l_ids = cr_line_obj.search(cr,
|
||||
@@ -81,19 +105,25 @@ class CreditCommunication(TransientModel):
|
||||
context=context)
|
||||
return cr_l_ids
|
||||
|
||||
def _generate_comm_from_credit_line_ids(self, cr, uid, line_ids, context=None):
|
||||
def _generate_comm_from_credit_line_ids(self, cr, uid, line_ids,
|
||||
context=None):
|
||||
"""Aggregate credit control line by partner, level, and currency
|
||||
It also generate a communication object per aggregation.
|
||||
"""
|
||||
if not line_ids:
|
||||
return []
|
||||
comms = []
|
||||
sql = ("SELECT distinct partner_id, policy_level_id, "
|
||||
" credit_control_line.currency_id, credit_control_policy_level.level"
|
||||
" FROM credit_control_line JOIN credit_control_policy_level "
|
||||
" ON (credit_control_line.policy_level_id = credit_control_policy_level.id)"
|
||||
" WHERE credit_control_line.id in %s"
|
||||
" ORDER by credit_control_policy_level.level, credit_control_line.currency_id")
|
||||
sql = (
|
||||
"SELECT distinct partner_id, policy_level_id, "
|
||||
" credit_control_line.currency_id, "
|
||||
" credit_control_policy_level.level"
|
||||
" FROM credit_control_line JOIN credit_control_policy_level "
|
||||
" ON (credit_control_line.policy_level_id = "
|
||||
" credit_control_policy_level.id)"
|
||||
" WHERE credit_control_line.id in %s"
|
||||
" ORDER by credit_control_policy_level.level, "
|
||||
" credit_control_line.currency_id"
|
||||
)
|
||||
|
||||
cr.execute(sql, (tuple(line_ids),))
|
||||
res = cr.dictfetchall()
|
||||
@@ -140,13 +170,15 @@ class CreditCommunication(TransientModel):
|
||||
email_values['body_html'] = email_values['body']
|
||||
email_values['type'] = 'email'
|
||||
|
||||
email_id = email_message_obj.create(cr, uid, email_values, context=context)
|
||||
email_id = email_message_obj.create(cr, uid, email_values,
|
||||
context=context)
|
||||
|
||||
state = 'sent'
|
||||
# The mail will not be send, however it will be in the pool, in an
|
||||
# error state. So we create it, link it with the credit control line
|
||||
# and put this latter in a `email_error` state we not that we have a
|
||||
# problem with the email
|
||||
# error state. So we create it, link it with
|
||||
# the credit control line
|
||||
# and put this latter in a `email_error` state we not that we have
|
||||
# a problem with the email
|
||||
if any(not email_values.get(field) for field in essential_fields):
|
||||
state = 'email_error'
|
||||
|
||||
@@ -168,7 +200,10 @@ class CreditCommunication(TransientModel):
|
||||
'res_id': email_id,
|
||||
'type': 'binary',
|
||||
}
|
||||
att_ids.append(att_obj.create(cr, uid, data_attach, context=context))
|
||||
att_ids.append(
|
||||
att_obj.create(cr, uid, data_attach,
|
||||
context=context)
|
||||
)
|
||||
email_message_obj.write(cr, uid, [email_id],
|
||||
{'attachment_ids': [(6, 0, att_ids)]},
|
||||
context=context)
|
||||
@@ -176,7 +211,10 @@ class CreditCommunication(TransientModel):
|
||||
return email_ids
|
||||
|
||||
def _generate_report(self, cr, uid, comms, context=None):
|
||||
"""Will generate a report by inserting mako template of related policy template"""
|
||||
"""Will generate a report by inserting mako template
|
||||
of related policy template
|
||||
|
||||
"""
|
||||
service = netsvc.LocalService('report.credit_control_summary')
|
||||
ids = [x.id for x in comms]
|
||||
result, format = service.create(cr, uid, ids, {}, {})
|
||||
|
||||
@@ -47,7 +47,9 @@ class CreditControlEmailer(orm.TransientModel):
|
||||
'line_ids': fields.many2many(
|
||||
'credit.control.line',
|
||||
string='Credit Control Lines',
|
||||
domain="[('state', '=', 'to_be_sent'), ('channel', '=', 'email')]"),
|
||||
domain=[('state', '=', 'to_be_sent'),
|
||||
('channel', '=', 'email')]
|
||||
),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
@@ -71,7 +73,10 @@ class CreditControlEmailer(orm.TransientModel):
|
||||
form = self.browse(cr, uid, wiz_id, context)
|
||||
|
||||
if not form.line_ids:
|
||||
raise orm.except_orm(_('Error'), _('No credit control lines selected.'))
|
||||
raise orm.except_orm(
|
||||
_('Error'),
|
||||
_('No credit control lines selected.')
|
||||
)
|
||||
|
||||
line_ids = [l.id for l in form.line_ids]
|
||||
filtered_ids = self._filter_line_ids(
|
||||
|
||||
@@ -69,7 +69,9 @@ class CreditControlMarker(orm.TransientModel):
|
||||
line_obj = self.pool.get('credit.control.line')
|
||||
if not state:
|
||||
raise ValueError(_('state can not be empty'))
|
||||
line_obj.write(cr, uid, filtered_ids, {'state': state}, context=context)
|
||||
line_obj.write(cr, uid, filtered_ids,
|
||||
{'state': state},
|
||||
context=context)
|
||||
return filtered_ids
|
||||
|
||||
def mark_lines(self, cr, uid, wiz_id, context=None):
|
||||
@@ -82,7 +84,10 @@ class CreditControlMarker(orm.TransientModel):
|
||||
form = self.browse(cr, uid, wiz_id, context)
|
||||
|
||||
if not form.line_ids:
|
||||
raise orm.except_orm(_('Error'), _('No credit control lines selected.'))
|
||||
raise orm.except_orm(
|
||||
_('Error'),
|
||||
_('No credit control lines selected.')
|
||||
)
|
||||
|
||||
line_ids = [l.id for l in form.line_ids]
|
||||
|
||||
@@ -90,7 +95,8 @@ class CreditControlMarker(orm.TransientModel):
|
||||
if not filtered_ids:
|
||||
raise orm.except_orm(
|
||||
_('Information'),
|
||||
_('No lines will be changed. All the selected lines are already done.')
|
||||
_('No lines will be changed. '
|
||||
'All the selected lines are already done.')
|
||||
)
|
||||
|
||||
self._mark_lines(cr, uid, filtered_ids, form.name, context)
|
||||
|
||||
@@ -41,8 +41,10 @@ class CreditControlPrinter(orm.TransientModel):
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'mark_as_sent': fields.boolean('Mark letter lines as sent',
|
||||
help="Only letter lines will be marked."),
|
||||
'mark_as_sent': fields.boolean(
|
||||
'Mark letter lines as sent',
|
||||
help="Only letter lines will be marked."
|
||||
),
|
||||
'report_file': fields.binary('Generated Report', readonly=True),
|
||||
'report_name': fields.char('Report name'),
|
||||
'state': fields.char('state', size=32),
|
||||
@@ -90,10 +92,12 @@ class CreditControlPrinter(orm.TransientModel):
|
||||
|
||||
comms = comm_obj._generate_comm_from_credit_line_ids(cr, uid, line_ids,
|
||||
context=context)
|
||||
report_file = comm_obj._generate_report(cr, uid, comms, context=context)
|
||||
report_file = comm_obj._generate_report(cr, uid, comms,
|
||||
context=context)
|
||||
|
||||
form.write({'report_file': base64.b64encode(report_file),
|
||||
'report_name': 'credit_control_esr_bvr_%s.pdf' % fields.datetime.now(),
|
||||
'report_name': ('credit_control_esr_bvr_%s.pdf' %
|
||||
fields.datetime.now()),
|
||||
'state': 'done'})
|
||||
|
||||
if form.mark_as_sent:
|
||||
|
||||
@@ -41,17 +41,20 @@ class FeesComputer(orm.BaseModel):
|
||||
def _get_compute_fun(self, level_fees_type):
|
||||
"""Retrieve function of class that should compute the fees based on type
|
||||
|
||||
:param level_fee_type: type exisiting in model `credit.control.policy.level`
|
||||
:param level_fee_type: type exisiting in model
|
||||
`credit.control.policy.level`
|
||||
for field dunning_fees_type
|
||||
|
||||
:returns: a function of class :class:`FeesComputer` with following signature
|
||||
:returns: a function of class :class:`FeesComputer`
|
||||
with following signature
|
||||
self, cr, uid, credit_line (record), context
|
||||
|
||||
"""
|
||||
if level_fees_type == 'fixed':
|
||||
return self.compute_fixed_fees
|
||||
else:
|
||||
raise NotImplementedError('fees type %s is not supported' % level_fees_type)
|
||||
raise NotImplementedError('fees type %s is not supported' %
|
||||
level_fees_type)
|
||||
|
||||
def _compute_fees(self, cr, uid, credit_line_ids, context=None):
|
||||
"""Compute fees for `credit_line_ids` parameter
|
||||
@@ -68,7 +71,8 @@ class FeesComputer(orm.BaseModel):
|
||||
if not credit_line_ids:
|
||||
return credit_line_ids
|
||||
c_model = self.pool['credit.control.line']
|
||||
credit_lines = c_model.browse(cr, uid, credit_line_ids, context=context)
|
||||
credit_lines = c_model.browse(cr, uid, credit_line_ids,
|
||||
context=context)
|
||||
for credit in credit_lines:
|
||||
# if there is no dependence between generated credit lines
|
||||
# this could be threaded
|
||||
|
||||
@@ -28,12 +28,12 @@ class credit_control_run(orm.Model):
|
||||
|
||||
def _generate_credit_lines(self, cr, uid, run_id, context=None):
|
||||
"""Override method to add fees computation"""
|
||||
credit_line_ids = super(credit_control_run, self)._generate_credit_lines(
|
||||
cr,
|
||||
uid,
|
||||
run_id,
|
||||
context=context
|
||||
)
|
||||
credit_line_ids = super(credit_control_run,
|
||||
self)._generate_credit_lines(
|
||||
cr,
|
||||
uid,
|
||||
run_id,
|
||||
context=context)
|
||||
fees_model = self.pool['credit.control.dunning.fees.computer']
|
||||
fees_model._compute_fees(cr, uid, credit_line_ids, context=context)
|
||||
return credit_line_ids
|
||||
|
||||
@@ -51,7 +51,9 @@ class FixedFeesTester(test_common.TransactionCase):
|
||||
self.usd_level.dunning_fixed_amount = 5.0
|
||||
self.usd_level.dunning_currency_id = self.usd
|
||||
self.usd_level.dunning_type = 'fixed'
|
||||
self.dunning_model = self.registry('credit.control.dunning.fees.computer')
|
||||
self.dunning_model = self.registry(
|
||||
'credit.control.dunning.fees.computer'
|
||||
)
|
||||
|
||||
def test_type_getter(self):
|
||||
"""Test that correct compute function is returned for "fixed" type"""
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Author Vincent Renaville/Joel Grand-Guillaume. Copyright 2012 Camptocamp SA
|
||||
# Author Vincent Renaville/Joel Grand-Guillaume.
|
||||
# Copyright 2012 Camptocamp SA
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
@@ -26,11 +27,13 @@ class AccountInvoice(orm.Model):
|
||||
|
||||
def action_move_create(self, cr, uid, ids, context=None):
|
||||
"""Set move line in draft state after creating them."""
|
||||
res = super(AccountInvoice, self).action_move_create(cr, uid, ids, context=context)
|
||||
res = super(AccountInvoice, self).action_move_create(cr, uid, ids,
|
||||
context=context)
|
||||
move_obj = self.pool.get('account.move')
|
||||
for inv in self.browse(cr, uid, ids, context=context):
|
||||
if inv.move_id:
|
||||
move_obj.write(cr, uid, [inv.move_id.id], {'state': 'draft'}, context=context)
|
||||
move_obj.write(cr, uid, [inv.move_id.id], {'state': 'draft'},
|
||||
context=context)
|
||||
return res
|
||||
|
||||
|
||||
@@ -38,8 +41,11 @@ class AccountMove(orm.Model):
|
||||
_inherit = 'account.move'
|
||||
|
||||
def button_cancel(self, cr, uid, ids, context=None):
|
||||
""" We rewrite function button_cancel, to allow invoice or bank statement with linked draft moved
|
||||
to be canceled """
|
||||
""" We rewrite function button_cancel, to allow invoice or bank
|
||||
statement with linked draft moved
|
||||
to be canceled
|
||||
|
||||
"""
|
||||
for line in self.browse(cr, uid, ids, context=context):
|
||||
if line.state == 'draft':
|
||||
continue
|
||||
@@ -48,12 +54,11 @@ class AccountMove(orm.Model):
|
||||
raise osv.except_osv(
|
||||
_('Error!'),
|
||||
_('You cannot modify a posted entry of this journal.'
|
||||
'First you should set the journal to allow cancelling entries.')
|
||||
'First you should set the journal '
|
||||
'to allow cancelling entries.')
|
||||
)
|
||||
if ids:
|
||||
cr.execute('UPDATE account_move '
|
||||
'SET state=%s '
|
||||
'WHERE id IN %s', ('draft', tuple(ids),))
|
||||
return True
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -23,7 +23,8 @@ from openerp.osv import orm
|
||||
class AccountBankStatement(orm.Model):
|
||||
_inherit = "account.bank.statement"
|
||||
|
||||
def create_move_from_st_line(self, cr, uid, st_line_id, company_currency_id,
|
||||
def create_move_from_st_line(self, cr, uid,
|
||||
st_line_id, company_currency_id,
|
||||
st_line_number, context=None):
|
||||
move_ids = super(AccountBankStatement, self).create_move_from_st_line(
|
||||
cr, uid, st_line_id, company_currency_id,
|
||||
@@ -35,7 +36,8 @@ class AccountBankStatement(orm.Model):
|
||||
voucher_obj = self.pool.get('account.voucher')
|
||||
st_line = bank_st_line_obj.browse(cr, uid, st_line_id, context=context)
|
||||
if st_line.voucher_id:
|
||||
v = voucher_obj.browse(cr, uid, st_line.voucher_id.id, context=context)
|
||||
v = voucher_obj.browse(cr, uid, st_line.voucher_id.id,
|
||||
context=context)
|
||||
move_ids = v.move_id.id
|
||||
|
||||
if not isinstance(move_ids, (tuple, list)):
|
||||
|
||||
@@ -32,17 +32,25 @@ Check that the Customer has a VAT number on invoice validation
|
||||
==============================================================
|
||||
|
||||
This module adds an option **Customer must have VAT** on fiscal positions.
|
||||
When a user tries to validate a customer invoice or refund with a fiscal position
|
||||
When a user tries to validate a customer invoice or refund
|
||||
with a fiscal position
|
||||
that have this option, OpenERP will check that the customer has a VAT number.
|
||||
|
||||
If it doesn't, OpenERP will block the validation of the invoice and display an error message.
|
||||
If it doesn't, OpenERP will block the validation of the invoice
|
||||
and display an error message.
|
||||
|
||||
In the European Union (EU), when an EU company sends an invoice to another EU company in another country,
|
||||
In the European Union (EU), when an EU company sends an invoice to
|
||||
another EU company in another country,
|
||||
|
||||
it can invoice without VAT (most of the time) but the VAT number of the customer must be displayed on the invoice.
|
||||
it can invoice without VAT (most of the time) but the VAT number of the
|
||||
customer must be displayed on the invoice.
|
||||
|
||||
This module also displays a warning when a user sets a fiscal position with the option
|
||||
**Customer must have VAT** on a customer and this customer doesn't have a VAT number in OpenERP yet.
|
||||
This module also displays a warning when a user sets
|
||||
a fiscal position with the option
|
||||
|
||||
**Customer must have VAT**
|
||||
|
||||
on a customer and this customer doesn't have a VAT number in OpenERP yet.
|
||||
|
||||
Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com>
|
||||
for any help or question about this module.
|
||||
@@ -57,7 +65,7 @@ Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com>
|
||||
'images': [
|
||||
'images/fiscal_position_form.jpg',
|
||||
'images/vat_check_invoice_validation.jpg',
|
||||
],
|
||||
],
|
||||
'installable': False,
|
||||
'active': False,
|
||||
'application': True,
|
||||
|
||||
@@ -30,7 +30,8 @@ class account_fiscal_position(orm.Model):
|
||||
_columns = {
|
||||
'customer_must_have_vat': fields.boolean(
|
||||
'Customer Must Have VAT number',
|
||||
help="If enabled, OpenERP will check that the customer has a VAT number "
|
||||
help="If enabled, OpenERP will check "
|
||||
"that the customer has a VAT number "
|
||||
"when the user validates a customer invoice/refund."
|
||||
),
|
||||
}
|
||||
@@ -53,9 +54,11 @@ class account_invoice(orm.Model):
|
||||
type_label = _('a Customer Refund')
|
||||
raise orm.except_orm(
|
||||
_('Missing VAT number:'),
|
||||
_("You are trying to validate %s with the fiscal position '%s' "
|
||||
_("You are trying to validate %s "
|
||||
"with the fiscal position '%s' "
|
||||
"that require the customer to have a VAT number. "
|
||||
"But the Customer '%s' doesn't have a VAT number in OpenERP."
|
||||
"But the Customer '%s' doesn't "
|
||||
"have a VAT number in OpenERP."
|
||||
"Please add the VAT number of this Customer in OpenERP "
|
||||
" and try to validate again.")
|
||||
% (type_label, invoice.fiscal_position.name,
|
||||
|
||||
@@ -27,7 +27,8 @@ from openerp.tools.translate import _
|
||||
class res_partner(orm.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
def fiscal_position_change(self, cr, uid, ids, account_position, vat, customer):
|
||||
def fiscal_position_change(self, cr, uid, ids, account_position,
|
||||
vat, customer):
|
||||
'''Warning is the fiscal position requires a vat number and the partner
|
||||
doesn't have one yet'''
|
||||
if account_position and customer and not vat:
|
||||
@@ -40,7 +41,8 @@ class res_partner(orm.Model):
|
||||
'message': _(
|
||||
"You have set the fiscal position '%s' "
|
||||
"that require the customer to have a VAT number. "
|
||||
"You should add the VAT number of this customer in OpenERP."
|
||||
"You should add the VAT number of this customer"
|
||||
" in OpenERP."
|
||||
) % fp['name']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2004-2011 Zikzakmedia S.L. (http://zikzakmedia.com) All Rights Reserved.
|
||||
# Copyright (C) 2004-2011 Zikzakmedia S.L. (http://zikzakmedia.com)
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
|
||||
import invoice
|
||||
from . import invoice
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP - Account invoice currency
|
||||
# Copyright (C) 2004-2011 Zikzakmedia S.L. (http://zikzakmedia.com) All Rights Reserved.
|
||||
# Jordi Esteve <jesteve@zikzakmedia.com>
|
||||
# Copyright (c) 2013 Joaquin Gutierrez (http://www.gutierrezweb.es)
|
||||
# OpenERP - Account invoice currency
|
||||
# Copyright (C) 2004-2011 Zikzakmedia S.L. (http://zikzakmedia.com)
|
||||
# Jordi Esteve <jesteve@zikzakmedia.com>
|
||||
# Copyright (c) 2013 Joaquin Gutierrez (http://www.gutierrezweb.es)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
This Module adds functional fields to show invoice in the company currency
|
||||
==========================================================================
|
||||
|
||||
Amount Untaxed, Amount Tax and Amount Total invoice fields in the company currency.
|
||||
Amount Untaxed, Amount Tax and Amount Total invoice
|
||||
fields in the company currency.
|
||||
These fields are shown in "Other information" tab in invoice form.
|
||||
""",
|
||||
'license': "AGPL-3",
|
||||
@@ -43,5 +44,3 @@ These fields are shown in "Other information" tab in invoice form.
|
||||
'installable': False,
|
||||
'active': False,
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2004-2011
|
||||
# Pexego Sistemas Informáticos. (http://pexego.es) All Rights Reserved
|
||||
# Zikzakmedia S.L. (http://zikzakmedia.com) All Rights Reserved.
|
||||
# Copyright (C) 2004-2011
|
||||
# Pexego Sistemas Informáticos. (http://pexego.es) All Rights Reserved
|
||||
# Zikzakmedia S.L. (http://zikzakmedia.com) All Rights Reserved.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
@@ -63,7 +63,8 @@ class account_invoice(orm.Model):
|
||||
'cc_amount_total': 0.0,
|
||||
}
|
||||
|
||||
# It could be computed only in open or paid invoices with a generated account move
|
||||
# It could be computed only in open or paid invoices with a
|
||||
# generated account move
|
||||
if invoice.move_id:
|
||||
# Accounts to compute amount_untaxed
|
||||
line_account = []
|
||||
@@ -74,20 +75,28 @@ class account_invoice(orm.Model):
|
||||
# Accounts to compute amount_tax
|
||||
tax_account = []
|
||||
for line in invoice.tax_line:
|
||||
if line.account_id.id not in tax_account and line.amount != 0:
|
||||
if (line.account_id.id not in tax_account and
|
||||
line.amount != 0):
|
||||
tax_account.append(line.account_id.id)
|
||||
|
||||
# The company currency amounts are the debit-credit amounts in the account moves
|
||||
# The company currency amounts are the debit-credit
|
||||
# amounts in the account moves
|
||||
for line in invoice.move_id.line_id:
|
||||
if line.account_id.id in line_account:
|
||||
res[invoice.id]['cc_amount_untaxed'] += line.debit - line.credit
|
||||
amt = line.debit - line.credit
|
||||
res[invoice.id]['cc_amount_untaxed'] += amt
|
||||
if line.account_id.id in tax_account:
|
||||
res[invoice.id]['cc_amount_tax'] += line.debit - line.credit
|
||||
amt = line.debit - line.credit
|
||||
res[invoice.id]['cc_amount_tax'] += amt
|
||||
if invoice.type in ('out_invoice', 'in_refund'):
|
||||
res[invoice.id]['cc_amount_untaxed'] = -res[invoice.id]['cc_amount_untaxed']
|
||||
res[invoice.id]['cc_amount_tax'] = -res[invoice.id]['cc_amount_tax']
|
||||
res[invoice.id]['cc_amount_total'] = (res[invoice.id]['cc_amount_tax'] +
|
||||
res[invoice.id]['cc_amount_untaxed'])
|
||||
res[invoice.id]['cc_amount_untaxed'] = \
|
||||
-res[invoice.id]['cc_amount_untaxed']
|
||||
res[invoice.id]['cc_amount_tax'] = \
|
||||
-res[invoice.id]['cc_amount_tax']
|
||||
res[invoice.id]['cc_amount_total'] = (
|
||||
res[invoice.id]['cc_amount_tax'] +
|
||||
res[invoice.id]['cc_amount_untaxed']
|
||||
)
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
@@ -97,15 +106,20 @@ class account_invoice(orm.Model):
|
||||
digits_compute=dp.get_precision('Account'),
|
||||
string='Company Cur. Untaxed',
|
||||
help="Invoice untaxed amount in the company currency"
|
||||
"(useful when invoice currency is different from company currency).",
|
||||
"(useful when invoice currency is different "
|
||||
"from company currency).",
|
||||
store={
|
||||
'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line',
|
||||
'currency_id',
|
||||
'move_id'], 20),
|
||||
'account.invoice': (lambda self, cr, uid, ids, c={}: ids,
|
||||
['invoice_line',
|
||||
'currency_id',
|
||||
'move_id'],
|
||||
20),
|
||||
'account.invoice.tax': (_get_invoice_tax2, None, 20),
|
||||
'account.invoice.line': (_get_invoice_line2, ['price_unit',
|
||||
'invoice_line_tax_id',
|
||||
'quantity', 'discount'], 20),
|
||||
'account.invoice.line': (_get_invoice_line2,
|
||||
['price_unit',
|
||||
'invoice_line_tax_id',
|
||||
'quantity', 'discount'],
|
||||
20),
|
||||
},
|
||||
multi='cc_all'
|
||||
),
|
||||
@@ -116,15 +130,20 @@ class account_invoice(orm.Model):
|
||||
digits_compute=dp.get_precision('Account'),
|
||||
string='Company Cur. Tax',
|
||||
help="Invoice tax amount in the company currency "
|
||||
"(useful when invoice currency is different from company currency).",
|
||||
"(useful when invoice currency is different "
|
||||
"from company currency).",
|
||||
store={
|
||||
'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line',
|
||||
'currency_id',
|
||||
'move_id'], 20),
|
||||
'account.invoice': (lambda self, cr, uid, ids, c={}: ids,
|
||||
['invoice_line',
|
||||
'currency_id',
|
||||
'move_id'],
|
||||
20),
|
||||
'account.invoice.tax': (_get_invoice_tax2, None, 20),
|
||||
'account.invoice.line': (_get_invoice_line2, ['price_unit',
|
||||
'invoice_line_tax_id',
|
||||
'quantity', 'discount'], 20),
|
||||
'account.invoice.line': (_get_invoice_line2,
|
||||
['price_unit',
|
||||
'invoice_line_tax_id',
|
||||
'quantity', 'discount'],
|
||||
20),
|
||||
},
|
||||
multi='cc_all'
|
||||
),
|
||||
@@ -135,15 +154,20 @@ class account_invoice(orm.Model):
|
||||
digits_compute=dp.get_precision('Account'),
|
||||
string='Company Cur. Total',
|
||||
help="Invoice total amount in the company currency "
|
||||
"(useful when invoice currency is different from company currency).",
|
||||
"(useful when invoice currency is different from "
|
||||
"company currency).",
|
||||
store={
|
||||
'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line',
|
||||
'currency_id',
|
||||
'move_id'], 20),
|
||||
'account.invoice': (lambda self, cr, uid, ids, c={}: ids,
|
||||
['invoice_line',
|
||||
'currency_id',
|
||||
'move_id'],
|
||||
20),
|
||||
'account.invoice.tax': (_get_invoice_tax2, None, 20),
|
||||
'account.invoice.line': (_get_invoice_line2, ['price_unit',
|
||||
'invoice_line_tax_id',
|
||||
'quantity', 'discount'], 20),
|
||||
'account.invoice.line': (_get_invoice_line2,
|
||||
['price_unit',
|
||||
'invoice_line_tax_id',
|
||||
'quantity', 'discount'],
|
||||
20),
|
||||
},
|
||||
multi='cc_all'),
|
||||
}
|
||||
|
||||
@@ -39,9 +39,11 @@ This module:
|
||||
|
||||
* prevent users from deactivating the 'Check Date in Period' option.
|
||||
|
||||
So this module is an additionnal security for countries where, on an account move, the date must be inside the period.
|
||||
So this module is an additionnal security for countries where, on an account
|
||||
move, the date must be inside the period.
|
||||
|
||||
Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com> for any help or question about this module.
|
||||
Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com>
|
||||
for any help or question about this module.
|
||||
""",
|
||||
'author': 'Akretion',
|
||||
'website': 'http://www.akretion.com',
|
||||
|
||||
@@ -32,8 +32,7 @@ This module leaves the search view extension for move lines intact, but
|
||||
disables the default search values for the dropdowns so that you do not
|
||||
have to disable these before entering your own search queries.
|
||||
|
||||
.. image:: /account_move_line_no_default_search/static/src/img/move_line_search_view.png
|
||||
|
||||
..image:: /account_move_line_no_default_search/static/src/img/sample.png
|
||||
""",
|
||||
'website': 'http://therp.nl',
|
||||
'depends': ['account'],
|
||||
|
||||
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
@@ -12,11 +12,11 @@
|
||||
<menuitem
|
||||
action="action_account_move_line_search_extension"
|
||||
icon="STOCK_JUSTIFY_FILL"
|
||||
id="menu_account_move_line_search_extension"
|
||||
id="account.menu_action_account_moves_all"
|
||||
parent="account.menu_finance_entries"
|
||||
sequence="1"
|
||||
groups="account.group_account_user"
|
||||
/>
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -27,9 +27,15 @@
|
||||
Templates for Journal Entries
|
||||
|
||||
User can configure journal entries templates, useful for recurring entries.
|
||||
The amount of each template line can be computed (through python code) or kept as user input.
|
||||
If user input, when using the template, user has to fill the amount of every input lines.
|
||||
The journal entry form allows lo load, through a wizard, the template to use and the amounts to fill.
|
||||
|
||||
The amount of each template line can be computed (through python code)
|
||||
or kept as user input.
|
||||
|
||||
If user input, when using the template, user has to fill
|
||||
the amount of every input lines.
|
||||
|
||||
The journal entry form allows lo load, through a wizard,
|
||||
the template to use and the amounts to fill.
|
||||
|
||||
""",
|
||||
'author': 'Agile Business Group',
|
||||
|
||||
@@ -51,21 +51,27 @@ class account_document_template(orm.Model):
|
||||
|
||||
def _generate_empty_lines(self, cr, uid, template_id):
|
||||
lines = {}
|
||||
for template_line in self.browse(cr, uid, template_id).template_line_ids:
|
||||
t_lines = self.browse(cr, uid, template_id).template_line_ids
|
||||
for template_line in t_lines:
|
||||
lines[template_line.sequence] = None
|
||||
return lines
|
||||
|
||||
def lines(self, line_number):
|
||||
if self._computed_lines[line_number] is not None:
|
||||
return self._computed_lines[line_number]
|
||||
line = self._get_template_line(self._cr, self._uid, self._current_template_id, line_number)
|
||||
line = self._get_template_line(self._cr,
|
||||
self._uid,
|
||||
self._current_template_id,
|
||||
line_number)
|
||||
if re.match('L\( *' + str(line_number) + ' *\)', line.python_code):
|
||||
raise orm.except_orm(
|
||||
_('Error'),
|
||||
_('Line %s can\'t refer to itself') % str(line_number)
|
||||
)
|
||||
try:
|
||||
self._computed_lines[line_number] = eval(line.python_code.replace('L', 'self.lines'))
|
||||
self._computed_lines[line_number] = eval(
|
||||
line.python_code.replace('L', 'self.lines')
|
||||
)
|
||||
except KeyError:
|
||||
raise orm.except_orm(
|
||||
_('Error'),
|
||||
@@ -74,12 +80,14 @@ class account_document_template(orm.Model):
|
||||
|
||||
def compute_lines(self, cr, uid, template_id, input_lines):
|
||||
# input_lines: dictionary in the form {line_number: line_amount}
|
||||
# returns all the lines (included input lines) in the form {line_number: line_amount}
|
||||
# returns all the lines (included input lines)
|
||||
# in the form {line_number: line_amount}
|
||||
template = self.browse(cr, uid, template_id)
|
||||
if len(input_lines) != self._input_lines(cr, uid, template):
|
||||
raise orm.except_orm(
|
||||
_('Error'),
|
||||
_('Inconsistency between input lines and filled lines for template %s') % template.name
|
||||
_('Inconsistency between input lines and '
|
||||
'filled lines for template %s') % template.name
|
||||
)
|
||||
self._current_template_id = template.id
|
||||
self._cr = cr
|
||||
@@ -106,6 +114,10 @@ class account_document_template_line(orm.Model):
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=64, required=True),
|
||||
'sequence': fields.integer('Sequence', required=True),
|
||||
'type': fields.selection([('computed', 'Computed'), ('input', 'User input')], 'Type', required=True),
|
||||
'type': fields.selection(
|
||||
[('computed', 'Computed'), ('input', 'User input')],
|
||||
'Type',
|
||||
required=True
|
||||
),
|
||||
'python_code': fields.text('Python Code'),
|
||||
}
|
||||
|
||||
@@ -28,23 +28,40 @@ class account_move_template(orm.Model):
|
||||
_name = 'account.move.template'
|
||||
|
||||
_columns = {
|
||||
'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True),
|
||||
'template_line_ids': fields.one2many('account.move.template.line', 'template_id', 'Template Lines'),
|
||||
'company_id': fields.many2one(
|
||||
'res.company',
|
||||
'Company',
|
||||
required=True,
|
||||
change_default=True
|
||||
),
|
||||
'template_line_ids': fields.one2many(
|
||||
'account.move.template.line',
|
||||
'template_id',
|
||||
'Template Lines'
|
||||
),
|
||||
'cross_journals': fields.boolean('Cross-Journals'),
|
||||
'transitory_acc_id': fields.many2one('account.account', 'Transitory account', required=False),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(
|
||||
cr, uid, 'account.move.template', context=c
|
||||
'transitory_acc_id': fields.many2one(
|
||||
'account.account',
|
||||
'Transitory account',
|
||||
required=False
|
||||
),
|
||||
}
|
||||
|
||||
def _get_default(self, cr, uid, context=None):
|
||||
self.pool.get('res.company')._company_default_get(
|
||||
cr, uid, 'account.move.template', context=context
|
||||
)
|
||||
_defaults = {
|
||||
'company_id': _get_default
|
||||
}
|
||||
|
||||
def _check_different_journal(self, cr, uid, ids, context=None):
|
||||
# Check that the journal on these lines are different/same in the case of cross journals/single journal
|
||||
# Check that the journal on these lines are different/same in the case
|
||||
# of cross journals/single journal
|
||||
journal_ids = []
|
||||
all_journal_ids = []
|
||||
move_template = self.pool.get('account.move.template').browse(cr, uid, ids)[0]
|
||||
move_template = self.pool.get('account.move.template').browse(
|
||||
cr, uid, ids)[0]
|
||||
if not move_template.template_line_ids:
|
||||
return True
|
||||
for template_line in move_template.template_line_ids:
|
||||
@@ -59,7 +76,8 @@ class account_move_template(orm.Model):
|
||||
_constraints = [
|
||||
(_check_different_journal,
|
||||
'If the template is "cross-journals", the Journals must be different,'
|
||||
'if the template does not "cross-journals" the Journals must be the same!',
|
||||
'if the template does not "cross-journals" '
|
||||
'the Journals must be the same!',
|
||||
['journal_id'])
|
||||
]
|
||||
|
||||
@@ -69,16 +87,28 @@ class account_move_template_line(orm.Model):
|
||||
_inherit = 'account.document.template.line'
|
||||
|
||||
_columns = {
|
||||
'journal_id': fields.many2one('account.journal', 'Journal', required=True),
|
||||
'account_id': fields.many2one('account.account', 'Account',
|
||||
required=True, ondelete="cascade"),
|
||||
'journal_id': fields.many2one(
|
||||
'account.journal',
|
||||
'Journal',
|
||||
required=True
|
||||
),
|
||||
'account_id': fields.many2one(
|
||||
'account.account',
|
||||
'Account',
|
||||
required=True,
|
||||
ondelete="cascade"
|
||||
),
|
||||
'move_line_type': fields.selection(
|
||||
[('cr', 'Credit'),
|
||||
('dr', 'Debit')],
|
||||
'Move Line Type',
|
||||
required=True
|
||||
),
|
||||
'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete="cascade"),
|
||||
'analytic_account_id': fields.many2one(
|
||||
'account.analytic.account',
|
||||
'Analytic Account',
|
||||
ondelete="cascade"
|
||||
),
|
||||
'template_id': fields.many2one('account.move.template', 'Template'),
|
||||
'account_tax_id': fields.many2one('account.tax', 'Tax'),
|
||||
}
|
||||
|
||||
@@ -86,7 +86,8 @@ class wizard_select_template(orm.TransientModel):
|
||||
return self.load_template(cr, uid, ids)
|
||||
wizard.write({'state': 'template_selected'})
|
||||
|
||||
view_rec = model_data_obj.get_object_reference(cr, uid, 'account_move_template', 'wizard_select_template')
|
||||
view_rec = model_data_obj.get_object_reference(
|
||||
cr, uid, 'account_move_template', 'wizard_select_template')
|
||||
view_id = view_rec and view_rec[1] or False
|
||||
|
||||
return {
|
||||
@@ -106,7 +107,10 @@ class wizard_select_template(orm.TransientModel):
|
||||
|
||||
wizard = self.browse(cr, uid, ids, context=context)[0]
|
||||
if not template_obj.check_zero_lines(cr, uid, wizard):
|
||||
raise orm.except_orm(_('Error !'), _('At least one amount has to be non-zero!'))
|
||||
raise orm.except_orm(
|
||||
_('Error !'),
|
||||
_('At least one amount has to be non-zero!')
|
||||
)
|
||||
input_lines = {}
|
||||
|
||||
for template_line in wizard.line_ids:
|
||||
@@ -114,10 +118,14 @@ class wizard_select_template(orm.TransientModel):
|
||||
|
||||
period_id = account_period_obj.find(cr, uid, context=context)
|
||||
if not period_id:
|
||||
raise orm.except_orm(_('No period found !'), _('Unable to find a valid period !'))
|
||||
raise orm.except_orm(
|
||||
_('No period found !'),
|
||||
_('Unable to find a valid period !')
|
||||
)
|
||||
period_id = period_id[0]
|
||||
|
||||
computed_lines = template_obj.compute_lines(cr, uid, wizard.template_id.id, input_lines)
|
||||
computed_lines = template_obj.compute_lines(
|
||||
cr, uid, wizard.template_id.id, input_lines)
|
||||
|
||||
moves = {}
|
||||
for line in wizard.template_id.template_line_ids:
|
||||
@@ -171,14 +179,16 @@ class wizard_select_template(orm.TransientModel):
|
||||
})
|
||||
return move_id
|
||||
|
||||
def _make_move_line(self, cr, uid, line, computed_lines, move_id, period_id, partner_id):
|
||||
def _make_move_line(self, cr, uid, line, computed_lines,
|
||||
move_id, period_id, partner_id):
|
||||
account_move_line_obj = self.pool.get('account.move.line')
|
||||
analytic_account_id = False
|
||||
if line.analytic_account_id:
|
||||
if not line.journal_id.analytic_journal_id:
|
||||
raise orm.except_orm(
|
||||
_('No Analytic Journal !'),
|
||||
_("You have to dfine an analytic journal on the '%s' journal!")
|
||||
_("You have to define an analytic "
|
||||
"journal on the '%s' journal!")
|
||||
% (line.journal_id.name,)
|
||||
)
|
||||
|
||||
@@ -212,7 +222,8 @@ class wizard_select_template(orm.TransientModel):
|
||||
if not line.journal_id.analytic_journal_id:
|
||||
raise orm.except_orm(
|
||||
_('No Analytic Journal !'),
|
||||
_("You have to define an analytic journal on the '%s' journal!")
|
||||
_("You have to define an analytic journal "
|
||||
"on the '%s' journal!")
|
||||
% (line.template_id.journal_id.name,)
|
||||
)
|
||||
analytic_account_id = line.analytic_account_id.id
|
||||
@@ -238,10 +249,16 @@ class wizard_select_template_line(orm.TransientModel):
|
||||
_description = 'Template Lines'
|
||||
_name = "wizard.select.move.template.line"
|
||||
_columns = {
|
||||
'template_id': fields.many2one('wizard.select.move.template', 'Template'),
|
||||
'template_id': fields.many2one('wizard.select.move.template',
|
||||
'Template'),
|
||||
'sequence': fields.integer('Number', required=True),
|
||||
'name': fields.char('Name', size=64, required=True, readonly=True),
|
||||
'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True),
|
||||
'account_id': fields.many2one(
|
||||
'account.account',
|
||||
'Account',
|
||||
required=True,
|
||||
readonly=True
|
||||
),
|
||||
'move_line_type': fields.selection(
|
||||
[('cr', 'Credit'),
|
||||
('dr', 'Debit')],
|
||||
|
||||
@@ -28,7 +28,7 @@ but extending it to multiple periods and multiple journals. It replaces the
|
||||
base one defined in addons/account/wizard.
|
||||
""",
|
||||
'website': 'http://www.camptocamp.com',
|
||||
'data' : ['wizard/account_validate_move_view.xml'],
|
||||
'data': ['wizard/account_validate_move_view.xml'],
|
||||
'installable': False,
|
||||
'active': False,
|
||||
}
|
||||
|
||||
@@ -54,12 +54,16 @@ class test_account_partner_required(common.TransactionCase):
|
||||
'account_id': self.ref('account.a_sale')})
|
||||
move_line_id = self.move_line_obj.create(
|
||||
self.cr, self.uid,
|
||||
{'move_id': move_id,
|
||||
'name': '/',
|
||||
'debit': amount,
|
||||
'credit': 0,
|
||||
'account_id': self.ref('account.a_recv'),
|
||||
'partner_id': self.ref('base.res_partner_1') if with_partner else False})
|
||||
{
|
||||
'move_id': move_id,
|
||||
'name': '/',
|
||||
'debit': amount,
|
||||
'credit': 0,
|
||||
'account_id': self.ref('account.a_recv'),
|
||||
'partner_id': self.ref('base.res_partner_1') if
|
||||
with_partner else False
|
||||
}
|
||||
)
|
||||
return move_line_id
|
||||
|
||||
def _set_partner_policy(self, policy, aref='account.a_recv'):
|
||||
@@ -116,6 +120,12 @@ class test_account_partner_required(common.TransactionCase):
|
||||
self.move_line_obj.write(self.cr, self.uid, line_id,
|
||||
{'account_id': self.ref('account.a_pay')})
|
||||
# change account to a_pay with policy always with partner -> ok
|
||||
self.move_line_obj.write(self.cr, self.uid, line_id,
|
||||
{'account_id': self.ref('account.a_pay'),
|
||||
'partner_id': self.ref('base.res_partner_1')})
|
||||
self.move_line_obj.write(
|
||||
self.cr,
|
||||
self.uid,
|
||||
line_id,
|
||||
{
|
||||
'account_id': self.ref('account.a_pay'),
|
||||
'partner_id': self.ref('base.res_partner_1')
|
||||
}
|
||||
)
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
#
|
||||
# OpenERP - Account renumber wizard
|
||||
# Copyright (C) 2009 Pexego Sistemas Informáticos. All Rights Reserved
|
||||
# Copyright (c) 2013 Servicios Tecnológicos Avanzados (http://www.serviciosbaeza.com)
|
||||
# Copyright (c) 2013 Servicios Tecnológicos Avanzados
|
||||
# (http://www.serviciosbaeza.com)
|
||||
# Pedro Manuel Baeza <pedro.baeza@serviciosbaeza.com>
|
||||
# Copyright (c) 2013 Joaquin Gutierrez (http://www.gutierrezweb.es)
|
||||
# $Id$
|
||||
@@ -31,17 +32,19 @@
|
||||
'category': "Enterprise Specific Modules",
|
||||
'contributors': ['Pedro M. Baeza', 'Joaquín Gutierrez'],
|
||||
'description': """
|
||||
This module adds a wizard to renumber account moves by date only for admin users.
|
||||
=================================================================================
|
||||
This module adds a wizard to renumber account moves by date only for admin.
|
||||
===========================================================================
|
||||
|
||||
The wizard, that will be added to the "End of Year Treatments",
|
||||
let's you select one or more journals and fiscal periods,
|
||||
set a starting number; and then renumber all the posted moves
|
||||
from those journals and periods sorted by date.
|
||||
|
||||
It will recreate the sequence number of each account move using their journal sequence so:
|
||||
It will recreate the sequence number of each account move
|
||||
using their journal sequence so:
|
||||
- Sequences per journal are supported.
|
||||
- Sequences with prefixes and sufixes based on the move date are also supported.
|
||||
- Sequences with prefixes and sufixes based on the move
|
||||
date are also supported.
|
||||
""",
|
||||
"license": "AGPL-3",
|
||||
"depends": [
|
||||
|
||||
@@ -89,7 +89,9 @@ def create_lots_of_account_moves(dbname, user, passwd, howmany):
|
||||
'name': 'Test_l2'})
|
||||
],
|
||||
'period_id': 1,
|
||||
'date': '2009-01-%s' % ((i % 31) or 1),
|
||||
'date': '2009-01-%s' % (
|
||||
(i % 31) or 1
|
||||
),
|
||||
'partner_id': False,
|
||||
'to_check': 0
|
||||
},
|
||||
@@ -97,7 +99,8 @@ def create_lots_of_account_moves(dbname, user, passwd, howmany):
|
||||
|
||||
# Validate the move
|
||||
object_facade.execute(dbname, user_id, passwd,
|
||||
u'account.move', 'button_validate', [move_id], {})
|
||||
u'account.move', 'button_validate',
|
||||
[move_id], {})
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# ------------------------------------------------------------------------
|
||||
@@ -105,7 +108,8 @@ def create_lots_of_account_moves(dbname, user, passwd, howmany):
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 5:
|
||||
logger.info(u"Usage: %s <dbname> <user> <password> <howmany>" % sys.argv[0])
|
||||
logger.info(u"Usage: %s <dbname> <user> <password> <howmany>" %
|
||||
sys.argv[0])
|
||||
else:
|
||||
create_lots_of_account_moves(
|
||||
sys.argv[1], sys.argv[2], sys.argv[3], int(sys.argv[4]))
|
||||
|
||||
@@ -42,7 +42,8 @@ class wizard_renumber(orm.TransientModel):
|
||||
help='Fiscal periods to renumber',
|
||||
string="Periods", ondelete='null'),
|
||||
'number_next': fields.integer('First Number', required=True,
|
||||
help="Journal sequences will start counting on this number"),
|
||||
help="Journal sequences will start "
|
||||
"counting on this number"),
|
||||
'state': fields.selection([('init', 'Initial'),
|
||||
('renumber', 'Renumbering')], readonly=True)
|
||||
}
|
||||
@@ -112,9 +113,11 @@ class wizard_renumber(orm.TransientModel):
|
||||
sequences_seen.append(sequence_id)
|
||||
# Generate (using our own get_id) and write the new move number
|
||||
c = {'fiscalyear_id': move.period_id.fiscalyear_id.id}
|
||||
new_name = sequence_obj.next_by_id(cr, uid,
|
||||
move.journal_id.sequence_id.id,
|
||||
context=c)
|
||||
new_name = sequence_obj.next_by_id(
|
||||
cr, uid,
|
||||
move.journal_id.sequence_id.id,
|
||||
context=c
|
||||
)
|
||||
# Note: We can't just do a
|
||||
# "move_obj.write(cr, uid, [move.id], {'name': new_name})"
|
||||
# cause it might raise a
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -26,9 +26,11 @@ class account_tax_declaration_analysis(orm.TransientModel):
|
||||
_description = 'Account Vat Declaration'
|
||||
_columns = {
|
||||
'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscalyear',
|
||||
help='Fiscalyear to look on', required=True),
|
||||
help='Fiscalyear to look on',
|
||||
required=True),
|
||||
|
||||
'period_list': fields.many2many('account.period', 'account_tax_period_rel',
|
||||
'period_list': fields.many2many('account.period',
|
||||
'account_tax_period_rel',
|
||||
'tax_analysis', 'period_id',
|
||||
'Period _list', required=True),
|
||||
}
|
||||
|
||||
@@ -171,51 +171,81 @@ class UpdateTaxConfig(orm.Model):
|
||||
cp_ref_tax_code_id = False
|
||||
if config.duplicate_tax_code:
|
||||
if line.source_tax_id.base_code_id:
|
||||
cp_base_code_id = tax_code_pool.copy(cr, uid,
|
||||
line.source_tax_id.base_code_id.id)
|
||||
rename_old = '[%s] %s' % (config.name,
|
||||
line.source_tax_id.base_code_id.name)
|
||||
cp_base_code_id = tax_code_pool.copy(
|
||||
cr, uid,
|
||||
line.source_tax_id.base_code_id.id
|
||||
)
|
||||
rename_old = '[%s] %s' % (
|
||||
config.name,
|
||||
line.source_tax_id.base_code_id.name
|
||||
)
|
||||
tax_code_pool.write(cr, uid,
|
||||
line.source_tax_id.base_code_id.id,
|
||||
{'name': rename_old})
|
||||
if line.source_tax_id.tax_code_id:
|
||||
cp_tax_code_id = tax_code_pool.copy(cr, uid,
|
||||
line.source_tax_id.tax_code_id.id)
|
||||
rename_old = '[%s] %s' % (config.name,
|
||||
line.source_tax_id.tax_code_id.name)
|
||||
cp_tax_code_id = tax_code_pool.copy(
|
||||
cr, uid,
|
||||
line.source_tax_id.tax_code_id.id
|
||||
)
|
||||
rename_old = '[%s] %s' % (
|
||||
config.name,
|
||||
line.source_tax_id.tax_code_id.name
|
||||
)
|
||||
tax_code_pool.write(cr, uid,
|
||||
line.source_tax_id.tax_code_id.id,
|
||||
{'name': rename_old})
|
||||
if line.source_tax_id.ref_base_code_id:
|
||||
# Check if with have the same tax code for base_code_id
|
||||
if line.source_tax_id.ref_base_code_id.id == line.source_tax_id.base_code_id.id:
|
||||
if (line.source_tax_id.ref_base_code_id.id ==
|
||||
line.source_tax_id.base_code_id.id):
|
||||
cp_ref_base_code_id = cp_base_code_id
|
||||
else:
|
||||
cp_ref_base_code_id = tax_code_pool.copy(cr, uid,
|
||||
line.source_tax_id.ref_base_code_id.id)
|
||||
rename_old = '[%s] %s' % (config.name,
|
||||
line.source_tax_id.ref_base_code_id.name)
|
||||
tax_code_pool.write(cr, uid,
|
||||
line.source_tax_id.ref_base_code_id.id,
|
||||
{'name': rename_old})
|
||||
cp_ref_base_code_id = tax_code_pool.copy(
|
||||
cr, uid,
|
||||
line.source_tax_id.ref_base_code_id.id
|
||||
)
|
||||
rename_old = '[%s] %s' % (
|
||||
config.name,
|
||||
line.source_tax_id.ref_base_code_id.name
|
||||
)
|
||||
tax_code_pool.write(
|
||||
cr, uid,
|
||||
line.source_tax_id.ref_base_code_id.id,
|
||||
{'name': rename_old}
|
||||
)
|
||||
if line.source_tax_id.ref_tax_code_id:
|
||||
if line.source_tax_id.ref_tax_code_id.id == line.source_tax_id.tax_code_id.id:
|
||||
if (line.source_tax_id.ref_tax_code_id.id ==
|
||||
line.source_tax_id.tax_code_id.id):
|
||||
cp_ref_tax_code_id = cp_tax_code_id
|
||||
else:
|
||||
cp_ref_tax_code_id = tax_code_pool.copy(cr, uid,
|
||||
line.source_tax_id.ref_tax_code_id.id)
|
||||
rename_old = '[%s] %s' % (config.name,
|
||||
line.source_tax_id.ref_tax_code_id.name)
|
||||
tax_code_pool.write(cr, uid,
|
||||
line.source_tax_id.ref_tax_code_id.id,
|
||||
{'name': rename_old})
|
||||
cp_ref_tax_code_id = tax_code_pool.copy(
|
||||
cr, uid,
|
||||
line.source_tax_id.ref_tax_code_id.id
|
||||
)
|
||||
rename_old = '[%s] %s' % (
|
||||
config.name,
|
||||
line.source_tax_id.ref_tax_code_id.name
|
||||
)
|
||||
tax_code_pool.write(
|
||||
cr, uid,
|
||||
line.source_tax_id.ref_tax_code_id.id,
|
||||
{'name': rename_old}
|
||||
)
|
||||
else:
|
||||
cp_base_code_id = line.source_tax_id.base_code_id and line.source_tax_id.base_code_id.id or False
|
||||
cp_ref_base_code_id = (line.source_tax_id.ref_base_code_id and
|
||||
line.source_tax_id.ref_base_code_id.id or False)
|
||||
cp_tax_code_id = line.source_tax_id.tax_code_id and line.source_tax_id.tax_code_id.id or False
|
||||
cp_base_code_id = (line.source_tax_id.base_code_id and
|
||||
line.source_tax_id.base_code_id.id or
|
||||
False)
|
||||
cp_ref_base_code_id = (
|
||||
line.source_tax_id.ref_base_code_id and
|
||||
line.source_tax_id.ref_base_code_id.id or
|
||||
False
|
||||
)
|
||||
cp_tax_code_id = (line.source_tax_id.tax_code_id and
|
||||
line.source_tax_id.tax_code_id.id or
|
||||
False)
|
||||
cp_ref_tax_code_id = (line.source_tax_id.ref_tax_code_id and
|
||||
line.source_tax_id.ref_tax_code_id.id or False)
|
||||
line.source_tax_id.ref_tax_code_id.id or
|
||||
False)
|
||||
|
||||
target_tax_id = tax_pool.copy(
|
||||
cr, uid, line.source_tax_id.id,
|
||||
@@ -352,7 +382,8 @@ class UpdateTaxConfig(orm.Model):
|
||||
if model:
|
||||
for field_name, column in model._columns.items():
|
||||
log += update_defaults(model_name, field_name, column)
|
||||
for field_name, field_tuple in model._inherit_fields.iteritems():
|
||||
for field_name, field_tuple in \
|
||||
model._inherit_fields.iteritems():
|
||||
if len(field_tuple) >= 3:
|
||||
column = field_tuple[2]
|
||||
log += update_defaults(model_name, field_name, column)
|
||||
|
||||
@@ -42,7 +42,8 @@ not support all CSV canvas.
|
||||
- AA lines will only be created when moves are posted.
|
||||
- Tax lines computation will be skipped until the move are posted.
|
||||
|
||||
This option should be used with caution and preferably in conjunction with provided canvas in tests/data
|
||||
This option should be used with caution and preferably in conjunction with
|
||||
provided canvas in tests/data
|
||||
|
||||
Then simply press import file button. The process will be run in background
|
||||
and you will be able to continue your work.
|
||||
|
||||
@@ -126,7 +126,8 @@ class account_move_line(orm.Model):
|
||||
context = {}
|
||||
if context.get('async_bypass_create'):
|
||||
return self._bypass_create(cr, uid, vals, context=context)
|
||||
return super(account_move_line, self).create(cr, uid, vals, context=context)
|
||||
return super(account_move_line, self).create(cr, uid, vals,
|
||||
context=context)
|
||||
|
||||
def _bypass_create(self, cr, uid, vals, context=None):
|
||||
"""Create entries using cursor directly
|
||||
|
||||
@@ -53,7 +53,8 @@ class move_line_importer(orm.Model):
|
||||
if default is None:
|
||||
default = {}
|
||||
default.update(state='draft', report=False)
|
||||
return super(move_line_importer, self).copy(cr, uid, id, default=default,
|
||||
return super(move_line_importer, self).copy(cr, uid, id,
|
||||
default=default,
|
||||
context=context)
|
||||
|
||||
def track_success(sef, cr, uid, obj, context=None):
|
||||
@@ -71,39 +72,58 @@ class move_line_importer(orm.Model):
|
||||
},
|
||||
}
|
||||
|
||||
_columns = {'name': fields.datetime('Name',
|
||||
required=True,
|
||||
readonly=True),
|
||||
'state': fields.selection([('draft', 'New'),
|
||||
('running', 'Running'),
|
||||
('done', 'Success'),
|
||||
('error', 'Error')],
|
||||
readonly=True,
|
||||
string='Status'),
|
||||
'report': fields.text('Report',
|
||||
readonly=True),
|
||||
'file': fields.binary('File',
|
||||
required=True),
|
||||
'delimiter': fields.selection([(',', ','), (';', ';'), ('|', '|')],
|
||||
string="CSV delimiter",
|
||||
required=True),
|
||||
'company_id': fields.many2one('res.company',
|
||||
'Company'),
|
||||
'bypass_orm': fields.boolean('Fast import (use with caution)',
|
||||
help="When enabled import will be faster but"
|
||||
" it will not use orm and may"
|
||||
" not support all CSV canvas. \n"
|
||||
"Entry posted option will be skipped. \n"
|
||||
"AA lines will only be created when"
|
||||
" moves are posted. \n"
|
||||
"Tax lines computation will be skipped. \n"
|
||||
"This option should be used with caution"
|
||||
" and in conjonction with provided canvas."),
|
||||
}
|
||||
_columns = {
|
||||
'name': fields.datetime(
|
||||
'Name',
|
||||
required=True,
|
||||
readonly=True
|
||||
),
|
||||
'state': fields.selection(
|
||||
[('draft', 'New'),
|
||||
('running', 'Running'),
|
||||
('done', 'Success'),
|
||||
('error', 'Error')],
|
||||
readonly=True,
|
||||
string='Status'
|
||||
),
|
||||
'report': fields.text(
|
||||
'Report',
|
||||
readonly=True
|
||||
),
|
||||
'file': fields.binary(
|
||||
'File',
|
||||
required=True
|
||||
),
|
||||
'delimiter': fields.selection(
|
||||
[(',', ','), (';', ';'), ('|', '|')],
|
||||
string="CSV delimiter",
|
||||
required=True
|
||||
),
|
||||
'company_id': fields.many2one(
|
||||
'res.company',
|
||||
'Company'
|
||||
),
|
||||
'bypass_orm': fields.boolean(
|
||||
'Fast import (use with caution)',
|
||||
help="When enabled import will be faster but"
|
||||
" it will not use orm and may"
|
||||
" not support all CSV canvas. \n"
|
||||
"Entry posted option will be skipped. \n"
|
||||
"AA lines will only be created when"
|
||||
" moves are posted. \n"
|
||||
"Tax lines computation will be skipped. \n"
|
||||
"This option should be used with caution"
|
||||
" and in conjonction with provided canvas."
|
||||
),
|
||||
}
|
||||
|
||||
def _get_current_company(self, cr, uid, context=None, model="move.line.importer"):
|
||||
return self.pool.get('res.company')._company_default_get(cr, uid, model,
|
||||
context=context)
|
||||
def _get_current_company(self, cr, uid, context=None,
|
||||
model="move.line.importer"):
|
||||
return self.pool.get('res.company')._company_default_get(
|
||||
cr, uid,
|
||||
model,
|
||||
context=context
|
||||
)
|
||||
|
||||
_defaults = {'state': 'draft',
|
||||
'name': fields.datetime.now(),
|
||||
@@ -143,9 +163,11 @@ class move_line_importer(orm.Model):
|
||||
try:
|
||||
data = csv.reader(csv_file, delimiter=str(delimiter))
|
||||
except csv.Error as error:
|
||||
raise orm.except_orm(_('CSV file is malformed'),
|
||||
_("Maybe you have not choose correct separator \n"
|
||||
"the error detail is : \n %s") % repr(error))
|
||||
raise orm.except_orm(
|
||||
_('CSV file is malformed'),
|
||||
_("Maybe you have not choose correct separator \n"
|
||||
"the error detail is : \n %s") % repr(error)
|
||||
)
|
||||
head = data.next()
|
||||
head = [x.replace(' ', '') for x in head]
|
||||
# Generator does not work with orm.BaseModel.load
|
||||
@@ -162,13 +184,16 @@ class move_line_importer(orm.Model):
|
||||
res = []
|
||||
for msg in messages:
|
||||
rows = msg.get('rows', {})
|
||||
res.append(_("%s. -- Field: %s -- rows %s to %s") % (msg.get('message', 'N/A'),
|
||||
msg.get('field', 'N/A'),
|
||||
rows.get('from', 'N/A'),
|
||||
rows.get('to', 'N/A')))
|
||||
res.append(_("%s. -- Field: %s -- rows %s to %s") % (
|
||||
msg.get('message', 'N/A'),
|
||||
msg.get('field', 'N/A'),
|
||||
rows.get('from', 'N/A'),
|
||||
rows.get('to', 'N/A'))
|
||||
)
|
||||
return "\n \n".join(res)
|
||||
|
||||
def _manage_load_results(self, cr, uid, imp_id, result, _do_commit=True, context=None):
|
||||
def _manage_load_results(self, cr, uid, imp_id, result, _do_commit=True,
|
||||
context=None):
|
||||
"""Manage the BaseModel.load function output and store exception.
|
||||
|
||||
Will generate success/failure report and store it into report field.
|
||||
@@ -176,8 +201,10 @@ class move_line_importer(orm.Model):
|
||||
Savepoints.
|
||||
|
||||
:param imp_id: current importer id
|
||||
:param result: BaseModel.laod return {ids: list(int)|False, messages: [Message]}
|
||||
:param _do_commit: toggle commit management only used for testing purpose only
|
||||
:param result: BaseModel.load returns
|
||||
{ids: list(int)|False, messages: [Message]}
|
||||
:param _do_commit: toggle commit management only used
|
||||
for testing purpose only
|
||||
:returns: current importer id
|
||||
|
||||
"""
|
||||
@@ -216,15 +243,23 @@ class move_line_importer(orm.Model):
|
||||
local_cr.commit()
|
||||
# We handle concurrent error troubles
|
||||
except psycopg2.OperationalError as pg_exc:
|
||||
_logger.error('Can not write report. System will retry %s time(s)' % max_tries)
|
||||
if pg_exc.pg_code in orm.PG_CONCURRENCY_ERRORS_TO_RETRY and max_tries >= 0:
|
||||
_logger.error(
|
||||
"Can not write report. "
|
||||
"System will retry %s time(s)" % max_tries
|
||||
)
|
||||
if (pg_exc.pg_code in orm.PG_CONCURRENCY_ERRORS_TO_RETRY and
|
||||
max_tries >= 0):
|
||||
local_cr.rollback()
|
||||
local_cr.close()
|
||||
remaining_try = max_tries - 1
|
||||
self._write_report(cr, uid, imp_id, cr, _do_commit=_do_commit,
|
||||
max_tries=remaining_try, context=context)
|
||||
self._write_report(cr, uid, imp_id, cr,
|
||||
_do_commit=_do_commit,
|
||||
max_tries=remaining_try,
|
||||
context=context)
|
||||
else:
|
||||
_logger.exception('Can not log report - Operational update error')
|
||||
_logger.exception(
|
||||
'Can not log report - Operational update error'
|
||||
)
|
||||
raise
|
||||
except Exception:
|
||||
_logger.exception('Can not log report')
|
||||
@@ -234,10 +269,13 @@ class move_line_importer(orm.Model):
|
||||
if not local_cr.closed:
|
||||
local_cr.close()
|
||||
else:
|
||||
self.write(cr, uid, [imp_id], {'state': state, 'report': msg}, context=context)
|
||||
self.write(cr, uid, [imp_id],
|
||||
{'state': state, 'report': msg},
|
||||
context=context)
|
||||
return imp_id
|
||||
|
||||
def _load_data(self, cr, uid, imp_id, head, data, _do_commit=True, context=None):
|
||||
def _load_data(self, cr, uid, imp_id, head, data, _do_commit=True,
|
||||
context=None):
|
||||
"""Function that does the load of parsed CSV file.
|
||||
|
||||
If will log exception and susccess into the report fields.
|
||||
@@ -245,13 +283,15 @@ class move_line_importer(orm.Model):
|
||||
:param imp_id: current importer id
|
||||
:param head: CSV file head (list of header)
|
||||
:param data: CSV file content (list of data list)
|
||||
:param _do_commit: toggle commit management only used for testing purpose only
|
||||
:param _do_commit: toggle commit management
|
||||
only used for testing purpose only
|
||||
:returns: current importer id
|
||||
|
||||
"""
|
||||
state = msg = None
|
||||
try:
|
||||
res = self.pool['account.move'].load(cr, uid, head, data, context=context)
|
||||
res = self.pool['account.move'].load(cr, uid, head, data,
|
||||
context=context)
|
||||
r_id, state, msg = self._manage_load_results(cr, uid, imp_id, res,
|
||||
_do_commit=_do_commit,
|
||||
context=context)
|
||||
@@ -285,8 +325,10 @@ class move_line_importer(orm.Model):
|
||||
"""
|
||||
for th in threading.enumerate():
|
||||
if th.getName() == 'async_move_line_import_%s' % imp_id:
|
||||
raise orm.except_orm(_('An import of this file is already running'),
|
||||
_('Please try latter'))
|
||||
raise orm.except_orm(
|
||||
_('An import of this file is already running'),
|
||||
_('Please try latter')
|
||||
)
|
||||
|
||||
def _check_permissions(self, cr, uid, context=None):
|
||||
"""Ensure that user is allowed to create move / move line"""
|
||||
@@ -295,7 +337,8 @@ class move_line_importer(orm.Model):
|
||||
move_obj.check_access_rule(cr, uid, [], 'create')
|
||||
move_obj.check_access_rights(cr, uid, 'create', raise_exception=True)
|
||||
move_line_obj.check_access_rule(cr, uid, [], 'create')
|
||||
move_line_obj.check_access_rights(cr, uid, 'create', raise_exception=True)
|
||||
move_line_obj.check_access_rights(cr, uid, 'create',
|
||||
raise_exception=True)
|
||||
|
||||
def import_file(self, cr, uid, imp_id, context=None):
|
||||
""" Will do an asynchronous load of a CSV file.
|
||||
|
||||
@@ -48,22 +48,29 @@ class TestMoveLineImporter(test_common.SingleTransactionCase):
|
||||
def test_01_one_line_without_orm_bypass(self):
|
||||
"""Test one line import without bypassing orm"""
|
||||
cr, uid = self.cr, self.uid
|
||||
importer_id = self.importer_model.create(cr, uid,
|
||||
{'file': self.get_file('one_move.csv'),
|
||||
'delimiter': ';'})
|
||||
importer_id = self.importer_model.create(
|
||||
cr, uid,
|
||||
{'file': self.get_file('one_move.csv'),
|
||||
'delimiter': ';'}
|
||||
)
|
||||
importer = self.importer_model.browse(cr, uid, importer_id)
|
||||
self.assertTrue(importer.company_id, 'Not default company set')
|
||||
self.assertFalse(importer.bypass_orm, 'Bypass orm must not be active')
|
||||
self.assertEqual(importer.state, 'draft')
|
||||
head, data = self.importer_model._parse_csv(cr, uid, importer.id)
|
||||
self.importer_model._load_data(cr, uid, importer.id, head, data, _do_commit=False, context={})
|
||||
self.importer_model._load_data(
|
||||
cr, uid, importer.id, head, data, _do_commit=False, context={})
|
||||
importer = self.importer_model.browse(cr, uid, importer_id)
|
||||
self.assertEquals(importer.state, 'done',
|
||||
'Exception %s during import' % importer.report)
|
||||
created_move_ids = self.move_model.search(cr, uid, [('ref', '=', 'éöüàè_test_1')])
|
||||
created_move_ids = self.move_model.search(
|
||||
cr, uid,
|
||||
[('ref', '=', 'éöüàè_test_1')]
|
||||
)
|
||||
self.assertTrue(created_move_ids, 'No move imported')
|
||||
created_move = self.move_model.browse(cr, uid, created_move_ids[0])
|
||||
self.assertTrue(len(created_move.line_id) == 3, 'Wrong number of move line imported')
|
||||
self.assertTrue(len(created_move.line_id) == 3,
|
||||
'Wrong number of move line imported')
|
||||
debit = credit = 0.0
|
||||
for line in created_move.line_id:
|
||||
debit += line.debit if line.debit else 0.0
|
||||
@@ -75,10 +82,12 @@ class TestMoveLineImporter(test_common.SingleTransactionCase):
|
||||
def test_02_one_line_using_orm_bypass(self):
|
||||
"""Test one line import using orm bypass"""
|
||||
cr, uid = self.cr, self.uid
|
||||
importer_id = self.importer_model.create(cr, uid,
|
||||
{'file': self.get_file('one_move2.csv'),
|
||||
'delimiter': ';',
|
||||
'bypass_orm': True})
|
||||
importer_id = self.importer_model.create(
|
||||
cr, uid,
|
||||
{'file': self.get_file('one_move2.csv'),
|
||||
'delimiter': ';',
|
||||
'bypass_orm': True}
|
||||
)
|
||||
importer = self.importer_model.browse(cr, uid, importer_id)
|
||||
self.assertTrue(importer.company_id, 'Not default company set')
|
||||
self.assertTrue(importer.bypass_orm, 'Bypass orm must be active')
|
||||
@@ -91,10 +100,12 @@ class TestMoveLineImporter(test_common.SingleTransactionCase):
|
||||
importer = self.importer_model.browse(cr, uid, importer_id)
|
||||
self.assertEquals(importer.state, 'done',
|
||||
'Exception %s during import' % importer.report)
|
||||
created_move_ids = self.move_model.search(cr, uid, [('ref', '=', 'test_2')])
|
||||
created_move_ids = self.move_model.search(cr, uid,
|
||||
[('ref', '=', 'test_2')])
|
||||
self.assertTrue(created_move_ids, 'No move imported')
|
||||
created_move = self.move_model.browse(cr, uid, created_move_ids[0])
|
||||
self.assertTrue(len(created_move.line_id) == 3, 'Wrong number of move line imported')
|
||||
self.assertTrue(len(created_move.line_id) == 3,
|
||||
'Wrong number of move line imported')
|
||||
debit = credit = 0.0
|
||||
for line in created_move.line_id:
|
||||
debit += line.debit if line.debit else 0.0
|
||||
@@ -106,17 +117,22 @@ class TestMoveLineImporter(test_common.SingleTransactionCase):
|
||||
def test_03_one_line_failing(self):
|
||||
"""Test one line import with faulty CSV file"""
|
||||
cr, uid = self.cr, self.uid
|
||||
importer_id = self.importer_model.create(cr, uid,
|
||||
{'file': self.get_file('faulty_moves.csv'),
|
||||
'delimiter': ';'})
|
||||
importer_id = self.importer_model.create(
|
||||
cr, uid,
|
||||
{'file': self.get_file('faulty_moves.csv'),
|
||||
'delimiter': ';'}
|
||||
)
|
||||
importer = self.importer_model.browse(cr, uid, importer_id)
|
||||
self.assertTrue(importer.company_id, 'Not default company set')
|
||||
self.assertFalse(importer.bypass_orm, 'Bypass orm must not be active')
|
||||
self.assertEqual(importer.state, 'draft')
|
||||
head, data = self.importer_model._parse_csv(cr, uid, importer.id)
|
||||
self.importer_model._load_data(cr, uid, importer.id, head, data, _do_commit=False, context={})
|
||||
self.importer_model._load_data(cr, uid, importer.id, head, data,
|
||||
_do_commit=False, context={})
|
||||
importer = self.importer_model.browse(cr, uid, importer_id)
|
||||
self.assertEquals(importer.state, 'error',
|
||||
'No exception %s during import' % importer.report)
|
||||
created_move_ids = self.move_model.search(cr, uid, [('ref', '=', 'test_3')])
|
||||
self.assertFalse(created_move_ids, 'Move was imported but it should not be the case')
|
||||
created_move_ids = self.move_model.search(cr, uid,
|
||||
[('ref', '=', 'test_3')])
|
||||
self.assertFalse(created_move_ids,
|
||||
'Move was imported but it should not be the case')
|
||||
|
||||
@@ -32,10 +32,12 @@ Currency Rate Date Check
|
||||
========================
|
||||
|
||||
This module adds a check on dates when doing currency conversion in OpenERP.
|
||||
It checks that the currency rate used to make the conversion is not more than N days away
|
||||
It checks that the currency rate used to make the conversion
|
||||
is not more than N days away
|
||||
from the date of the amount to convert.
|
||||
|
||||
The maximum number of days of the interval can be configured on the company form.
|
||||
The maximum number of days of the interval can be
|
||||
configured on the company form.
|
||||
|
||||
Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com>
|
||||
for any help or question about this module.
|
||||
@@ -47,7 +49,7 @@ for any help or question about this module.
|
||||
'images': [
|
||||
'images/date_check_error_popup.jpg',
|
||||
'images/date_check_company_config.jpg',
|
||||
],
|
||||
],
|
||||
'installable': False,
|
||||
'active': False,
|
||||
}
|
||||
|
||||
@@ -29,8 +29,10 @@ class res_company(orm.Model):
|
||||
_columns = {
|
||||
'currency_rate_max_delta': fields.integer(
|
||||
'Max Time Delta in Days for Currency Rates',
|
||||
help="This is the maximum interval in days between the date associated "
|
||||
"with the amount to convert and the date of the nearest currency "
|
||||
help="This is the maximum interval in days between "
|
||||
"the date associated "
|
||||
"with the amount to convert and the date "
|
||||
"of the nearest currency "
|
||||
"rate available in OpenERP."),
|
||||
}
|
||||
|
||||
@@ -41,5 +43,6 @@ class res_company(orm.Model):
|
||||
_sql_constraints = [
|
||||
('currency_rate_max_delta_positive',
|
||||
'CHECK (currency_rate_max_delta >= 0)',
|
||||
"The value of the field 'Max Time Delta in Days for Currency Rates' must be positive or 0."),
|
||||
"The value of the field 'Max Time Delta in Days for Currency Rates' "
|
||||
"must be positive or 0."),
|
||||
]
|
||||
|
||||
@@ -26,27 +26,36 @@ from openerp.tools.translate import _
|
||||
|
||||
# Here are some explainations about the design of this module.
|
||||
# In server/openerp/addons/base/res/res_currency.py :
|
||||
# compute() -> _get_conversion_rate() -> _current_rate() -> _current_rate_computation()
|
||||
# compute() -> _get_conversion_rate()
|
||||
# -> _current_rate() -> _current_rate_computation()
|
||||
# The date used for the rate is the one in the context
|
||||
# compute() adds currency_rate_type_from and currency_rate_type_to to the context
|
||||
# compute() adds currency_rate_type_from
|
||||
# and currency_rate_type_to to the context
|
||||
# _get_conversion_rate() adds currency_rate_type_id to context ;
|
||||
# its value is currency_rate_type_to ;
|
||||
# if it doesn't exist it's currency_rate_type_from ; if it doesn't exist either it's False
|
||||
# if it doesn't exist it's currency_rate_type_from ;
|
||||
# if it doesn't exist either it's False
|
||||
# It already contains raise "No rate found for currency ... at the date ..."
|
||||
# _current_rate() reads currency_rate_type_id from context and uses it in the SQL request
|
||||
# This is the function used for the definition of the field.function 'rate' on res_currency
|
||||
# _current_rate() reads currency_rate_type_id
|
||||
# from context and uses it in the SQL request
|
||||
# This is the function used for the definition of
|
||||
# the field.function 'rate' on res_currency
|
||||
|
||||
# Which one of the 3 functions should we inherit ? Good question...
|
||||
# It's probably better to inherit the lowest level function, i.e. _current_rate_computation()
|
||||
# Advantage : by inheriting the lowest level function, we can be sure that the check
|
||||
# always apply, even for scenarios where we read the field "rate" of the obj currency
|
||||
# It's probably better to inherit the lowest level function,
|
||||
# i.e. _current_rate_computation()
|
||||
# Advantage : by inheriting the lowest level function,
|
||||
# we can be sure that the check
|
||||
# always apply, even for scenarios where we
|
||||
# read the field "rate" of the obj currency
|
||||
# => that's the solution I implement in the code below
|
||||
|
||||
|
||||
class res_currency(orm.Model):
|
||||
_inherit = 'res.currency'
|
||||
|
||||
def _current_rate_computation(self, cr, uid, ids, name, arg, raise_on_no_rate, context=None):
|
||||
def _current_rate_computation(self, cr, uid, ids, name, arg,
|
||||
raise_on_no_rate, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
# We only do the check if there is an explicit date in the context and
|
||||
@@ -55,16 +64,18 @@ class res_currency(orm.Model):
|
||||
not context.get('disable_rate_date_check'):
|
||||
for currency_id in ids:
|
||||
# We could get the company from the currency, but it's not a
|
||||
# 'required' field, so we should probably continue to get it from
|
||||
# the user, shouldn't we ?
|
||||
user = self.pool['res.users'].browse(cr, uid, uid, context=context)
|
||||
# 'required' field, so we should probably continue to get it
|
||||
# from the user, shouldn't we ?
|
||||
user = self.pool['res.users'].browse(cr, uid, uid,
|
||||
context=context)
|
||||
# if it's the company currency, don't do anything
|
||||
# (there is just one old rate at 1.0)
|
||||
if user.company_id.currency_id.id == currency_id:
|
||||
continue
|
||||
else:
|
||||
# now we do the real work !
|
||||
date = context.get('date', datetime.today().strftime('%Y-%m-%d'))
|
||||
date = context.get('date',
|
||||
datetime.today().strftime('%Y-%m-%d'))
|
||||
date_datetime = datetime.strptime(date, '%Y-%m-%d')
|
||||
rate_obj = self.pool['res.currency.rate']
|
||||
selected_rate = rate_obj.search(cr, uid, [
|
||||
@@ -75,17 +86,26 @@ class res_currency(orm.Model):
|
||||
if not selected_rate:
|
||||
continue
|
||||
|
||||
rate_date = rate_obj.read(cr, uid, selected_rate[0], ['name'], context=context)['name']
|
||||
rate_date_datetime = datetime.strptime(rate_date, '%Y-%m-%d')
|
||||
rate_date = rate_obj.read(cr, uid, selected_rate[0],
|
||||
['name'],
|
||||
context=context)['name']
|
||||
rate_date_datetime = datetime.strptime(rate_date,
|
||||
'%Y-%m-%d')
|
||||
max_delta = user.company_id.currency_rate_max_delta
|
||||
if (date_datetime - rate_date_datetime).days > max_delta:
|
||||
currency_name = self.read(cr, uid, currency_id, ['name'], context=context)['name']
|
||||
currency_name = self.read(cr, uid,
|
||||
currency_id,
|
||||
['name'],
|
||||
context=context)['name']
|
||||
raise orm.except_orm(
|
||||
_('Error'),
|
||||
_('You are requesting a rate conversion on %s for '
|
||||
'currency %s but the nearest rate before that date is '
|
||||
'dated %s and the maximum currency rate time delta for '
|
||||
'your company is %s days') % (date, currency_name, rate_date, max_delta)
|
||||
'currency %s but the nearest '
|
||||
'rate before that date is '
|
||||
'dated %s and the maximum currency '
|
||||
'rate time delta for '
|
||||
'your company is %s days') % (
|
||||
date, currency_name, rate_date, max_delta)
|
||||
)
|
||||
# Now we call the regular function from the "base" module
|
||||
return super(res_currency, self)._current_rate_computation(
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
#
|
||||
# Copyright (c) 2008 Camtocamp SA
|
||||
# @author JB Aubort, Nicolas Bessi, Joel Grand-Guillaume
|
||||
# European Central Bank and Polish National Bank invented by Grzegorz Grzelak
|
||||
# Ported to OpenERP 7.0 by Lorenzo Battistini <lorenzo.battistini@agilebg.com>
|
||||
# European Central Bank and Polish National Bank by Grzegorz Grzelak
|
||||
# Ported to OpenERP 7.0 by Lorenzo Battistini
|
||||
# <lorenzo.battistini@agilebg.com>
|
||||
# Banxico implemented by Agustin Cruz openpyme.mx
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -35,7 +36,8 @@ The module is able to use 4 different sources:
|
||||
Updated daily, source in CHF.
|
||||
|
||||
2. European Central Bank (ported by Grzegorz Grzelak)
|
||||
The reference rates are based on the regular daily concertation procedure between
|
||||
The reference rates are based on the regular
|
||||
daily concertation procedure between
|
||||
central banks within and outside the European System of Central Banks,
|
||||
which normally takes place at 2.15 p.m. (14:15) ECB time. Source in EUR.
|
||||
http://www.ecb.europa.eu/stats/exchange/eurofxref/html/index.en.html
|
||||
@@ -43,10 +45,12 @@ The module is able to use 4 different sources:
|
||||
3. Yahoo Finance
|
||||
Updated daily
|
||||
|
||||
4. Polish National Bank (Narodowy Bank Polski) (contribution by Grzegorz Grzelak)
|
||||
4. Polish National Bank (Narodowy Bank Polski)
|
||||
(contribution by Grzegorz Grzelak)
|
||||
Takes official rates from www.nbp.pl. Adds rate table symbol in log.
|
||||
You should check when rates should apply to bookkeeping. If next day you should
|
||||
change the update hour in schedule settings because in OpenERP they apply from
|
||||
You should check when rates should apply to bookkeeping.
|
||||
If next day you should change the update hour in schedule settings
|
||||
because in OpenERP they apply from
|
||||
date of update (date - no hours).
|
||||
|
||||
5. Banxico for USD & MXN (created by Agustín Cruz)
|
||||
@@ -60,7 +64,8 @@ The update can be set under the company form.
|
||||
You can set for each services which currency you want to update.
|
||||
The logs of the update are visible under the service note.
|
||||
You can active or deactivate the update.
|
||||
The module uses internal ir_cron feature from OpenERP, so the job is launched once
|
||||
The module uses internal ir_cron feature from OpenERP,
|
||||
so the job is launched once
|
||||
the server starts if the 'first execute date' is before the current day.
|
||||
The module supports multi-company currency in two ways:
|
||||
|
||||
@@ -71,7 +76,8 @@ The module supports multi-company currency in two ways:
|
||||
|
||||
A function field lets you know your currency configuration.
|
||||
|
||||
If in multi-company mode, the base currency will be the first company's currency
|
||||
If in multi-company mode, the base currency will
|
||||
be the first company's currency
|
||||
found in database.
|
||||
|
||||
Thanks to main contributors: Grzegorz Grzelak, Alexis de Lattre
|
||||
|
||||
@@ -69,7 +69,8 @@ class res_company(orm.Model):
|
||||
compagnies = self.search(cr, uid, [])
|
||||
activate_cron = 'f'
|
||||
if not value:
|
||||
# this statement is here beacaus we do no want to save in case of error
|
||||
# this statement is here beacaus we do no want to save #
|
||||
# in case of error
|
||||
self.write(cr, uid, id, {'auto_currency_up': value})
|
||||
for comp in compagnies:
|
||||
if self.browse(cr, uid, comp).auto_currency_up:
|
||||
@@ -84,17 +85,19 @@ class res_company(orm.Model):
|
||||
else:
|
||||
for comp in compagnies:
|
||||
if comp != id and not enable:
|
||||
if self.browse(cr, uid, comp).multi_company_currency_enable:
|
||||
current = self.browse(cr, uid, comp)
|
||||
if current.multi_company_currency_enable:
|
||||
# We ensure taht we did not have write a true value
|
||||
self.write(cr, uid, id, {'auto_currency_up': False})
|
||||
msg = ('You can not activate auto currency'
|
||||
'update on more thant one company with this '
|
||||
'multi company configuration')
|
||||
return {
|
||||
'value': {'auto_currency_up': False},
|
||||
|
||||
'warning': {
|
||||
'title': "Warning",
|
||||
'message': 'You can not activate auto currency '
|
||||
'update on more thant one company with this '
|
||||
'multi company configuration'
|
||||
'message': msg,
|
||||
}
|
||||
}
|
||||
self.write(cr, uid, id, {'auto_currency_up': value})
|
||||
|
||||
@@ -3,17 +3,20 @@
|
||||
#
|
||||
# Copyright (c) 2009 Camptocamp SA
|
||||
# @source JBA and AWST inpiration
|
||||
# @contributor Grzegorz Grzelak (grzegorz.grzelak@birdglobe.com), Joel Grand-Guillaume
|
||||
# @contributor Grzegorz Grzelak (grzegorz.grzelak@birdglobe.com),
|
||||
# Joel Grand-Guillaume
|
||||
# Copyright (c) 2010 Alexis de Lattre (alexis@via.ecp.fr)
|
||||
# - ported XML-based webservices (Admin.ch, ECB, PL NBP) to new XML lib
|
||||
# - rates given by ECB webservice is now correct even when main_cur <> EUR
|
||||
# - rates given by PL_NBP webservice is now correct even when main_cur <> PLN
|
||||
# - if company_currency <> CHF, you can now update CHF via Admin.ch webservice
|
||||
# - rates given by PL_NBP webs. is now correct even when main_cur <> PLN
|
||||
# - if company_currency <> CHF, you can now update CHF via Admin.ch
|
||||
# (same for EUR with ECB webservice and PLN with NBP webservice)
|
||||
# For more details, see Launchpad bug #645263
|
||||
# - mecanism to check if rates given by the webservice are "fresh" enough to be
|
||||
# written in OpenERP ('max_delta_days' parameter for each currency update service)
|
||||
# Ported to OpenERP 7.0 by Lorenzo Battistini <lorenzo.battistini@agilebg.com>
|
||||
# - mecanism to check if rates given by the webservice are "fresh"
|
||||
# enough to be written in OpenERP
|
||||
# ('max_delta_days' parameter for each currency update service)
|
||||
# Ported to OpenERP 7.0 by Lorenzo Battistini
|
||||
# <lorenzo.battistini@agilebg.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
@@ -37,6 +40,8 @@
|
||||
import logging
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
from openerp.osv import fields, osv, orm
|
||||
from openerp.tools.translate import _
|
||||
|
||||
@@ -56,11 +61,15 @@ class Currency_rate_update_service(osv.Model):
|
||||
('Admin_ch_getter', 'Admin.ch'),
|
||||
('ECB_getter', 'European Central Bank'),
|
||||
('Yahoo_getter', 'Yahoo Finance '),
|
||||
('PL_NBP_getter', 'Narodowy Bank Polski'), # Added for polish rates
|
||||
('Banxico_getter', 'Banco de México'), # Added for mexican rates
|
||||
# Bank of Canada is using RSS-CB http://www.cbwiki.net/wiki/index.php/Specification_1.1 :
|
||||
# This RSS format is used by other national banks (Thailand, Malaysia, Mexico...)
|
||||
('CA_BOC_getter', 'Bank of Canada - noon rates'), # Added for canadian rates
|
||||
# Added for polish rates
|
||||
('PL_NBP_getter', 'Narodowy Bank Polski'),
|
||||
# Added for mexican rates
|
||||
('Banxico_getter', 'Banco de México'),
|
||||
# Bank of Canada is using RSS-CB
|
||||
# http://www.cbwiki.net/wiki/index.php/Specification_1.1
|
||||
# This RSS format is used by other national banks
|
||||
# (Thailand, Malaysia, Mexico...)
|
||||
('CA_BOC_getter', 'Bank of Canada - noon rates'),
|
||||
],
|
||||
"Webservice to use",
|
||||
required=True
|
||||
@@ -119,13 +128,14 @@ class Currency_rate_update(osv.Model):
|
||||
_name = "currency.rate.update"
|
||||
_description = "Currency Rate Update"
|
||||
# Dict that represent a cron object
|
||||
nextcall_time = datetime.today() + timedelta(days=1)
|
||||
nextcall = nextcall_time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
cron = {
|
||||
'active': False,
|
||||
'priority': 1,
|
||||
'interval_number': 1,
|
||||
'interval_type': 'weeks',
|
||||
'nextcall': time.strftime("%Y-%m-%d %H:%M:%S",
|
||||
datetime.today() + timedelta(days=1)).timetuple(),
|
||||
'nextcall': nextcall,
|
||||
'numbercall': -1,
|
||||
'doall': True,
|
||||
'model': 'currency.rate.update',
|
||||
@@ -159,7 +169,9 @@ class Currency_rate_update(osv.Model):
|
||||
cron_id = int(cron_id[0])
|
||||
except Exception:
|
||||
_logger.info('warning cron not found one will be created')
|
||||
pass # Ignore if the cron is missing cause we are going to create it in db
|
||||
# Ignore if the cron is missing cause we are
|
||||
# going to create it in db
|
||||
pass
|
||||
if not cron_id:
|
||||
self.cron['name'] = _('Currency Rate Update')
|
||||
cron_id = cron_obj.create(cr, uid, self.cron, context)
|
||||
@@ -213,13 +225,14 @@ class Currency_rate_update(osv.Model):
|
||||
# We initalize the class that will handle the request
|
||||
# and return a dict of rate
|
||||
getter = factory.register(service.service)
|
||||
curr_to_fetch = map(lambda x: x.name, service.currency_to_update)
|
||||
curr_to_fetch = map(lambda x: x.name,
|
||||
service.currency_to_update)
|
||||
res, log_info = getter.get_updated_currency(
|
||||
curr_to_fetch,
|
||||
main_curr,
|
||||
service.max_delta_days
|
||||
)
|
||||
rate_name = time.strftime('%Y-%m-%d')
|
||||
rate_name = time.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
for curr in service.currency_to_update:
|
||||
if curr.name == main_curr:
|
||||
continue
|
||||
@@ -242,16 +255,22 @@ class Currency_rate_update(osv.Model):
|
||||
)
|
||||
|
||||
# Show the most recent note at the top
|
||||
msg = "%s \n%s currency updated. %s" % \
|
||||
(log_info or '',
|
||||
datetime.strftime(datetime.today(), '%Y-%m-%d %H:%M:%S'),
|
||||
note)
|
||||
msg = "%s \n%s currency updated. %s" % (
|
||||
log_info or '',
|
||||
datetime.today().strftime(
|
||||
DEFAULT_SERVER_DATETIME_FORMAT
|
||||
),
|
||||
note
|
||||
)
|
||||
service.write({'note': msg})
|
||||
except Exception as exc:
|
||||
error_msg = "\n%s ERROR : %s %s" %\
|
||||
(datetime.strftime(datetime.today(), '%Y-%m-%d %H:%M:%S'),
|
||||
repr(exc),
|
||||
note)
|
||||
error_msg = "\n%s ERROR : %s %s" % (
|
||||
datetime.today().strftime(
|
||||
DEFAULT_SERVER_DATETIME_FORMAT
|
||||
),
|
||||
repr(exc),
|
||||
note
|
||||
)
|
||||
_logger.info(repr(exc))
|
||||
service.write({'note': error_msg})
|
||||
|
||||
@@ -321,40 +340,31 @@ class Curreny_getter_interface(object):
|
||||
log_info = " "
|
||||
|
||||
supported_currency_array = [
|
||||
'AFN', 'ALL', 'DZD', 'USD', 'USD', 'USD', 'EUR', 'AOA', 'XCD', 'XCD', 'ARS',
|
||||
'AMD', 'AWG', 'AUD', 'EUR', 'AZN', 'EUR', 'BSD', 'BHD', 'EUR', 'BDT', 'BBD',
|
||||
'XCD', 'BYR', 'EUR', 'BZD', 'XOF', 'BMD', 'BTN', 'INR', 'BOB', 'ANG', 'BAM',
|
||||
'BWP', 'NOK', 'BRL', 'GBP', 'USD', 'USD', 'BND', 'BGN', 'XOF', 'MMK', 'BIF',
|
||||
'XOF', 'USD', 'KHR', 'XAF', 'CAD', 'EUR', 'CVE', 'KYD', 'XAF', 'XAF', 'CLP',
|
||||
'CNY', 'AUD', 'AUD', 'COP', 'XAF', 'KMF', 'XPF', 'XAF', 'CDF', 'NZD', 'CRC',
|
||||
'HRK', 'CUP', 'ANG', 'EUR', 'CYP', 'CZK', 'DKK', 'DJF', 'XCD', 'DOP', 'EUR',
|
||||
'XCD', 'IDR', 'USD', 'EGP', 'EUR', 'SVC', 'USD', 'GBP', 'XAF', 'ETB', 'ERN',
|
||||
'EEK', 'ETB', 'EUR', 'FKP', 'DKK', 'FJD', 'EUR', 'EUR', 'EUR', 'XPF', 'XPF',
|
||||
'EUR', 'XPF', 'XAF', 'GMD', 'GEL', 'EUR', 'GHS', 'GIP', 'XAU', 'GBP', 'EUR',
|
||||
'DKK', 'XCD', 'XCD', 'EUR', 'USD', 'GTQ', 'GGP', 'GNF', 'XOF', 'GYD', 'HTG',
|
||||
'USD', 'AUD', 'BAM', 'EUR', 'EUR', 'HNL', 'HKD', 'HUF', 'ISK', 'INR', 'IDR',
|
||||
'XDR', 'IRR', 'IQD', 'EUR', 'IMP', 'ILS', 'EUR', 'JMD', 'NOK', 'JPY', 'JEP',
|
||||
'JOD', 'KZT', 'AUD', 'KES', 'AUD', 'KPW', 'KRW', 'KWD', 'KGS', 'LAK', 'LVL',
|
||||
'LBP', 'LSL', 'ZAR', 'LRD', 'LYD', 'CHF', 'LTL', 'EUR', 'MOP', 'MKD', 'MGA',
|
||||
'EUR', 'MWK', 'MYR', 'MVR', 'XOF', 'EUR', 'MTL', 'FKP', 'USD', 'USD', 'EUR',
|
||||
'MRO', 'MUR', 'EUR', 'AUD', 'MXN', 'USD', 'USD', 'EUR', 'MDL', 'EUR', 'MNT',
|
||||
'EUR', 'XCD', 'MAD', 'MZN', 'MMK', 'NAD', 'ZAR', 'AUD', 'NPR', 'ANG', 'EUR',
|
||||
'XCD', 'XPF', 'NZD', 'NIO', 'XOF', 'NGN', 'NZD', 'AUD', 'USD', 'NOK', 'OMR',
|
||||
'PKR', 'USD', 'XPD', 'PAB', 'USD', 'PGK', 'PYG', 'PEN', 'PHP', 'NZD', 'XPT',
|
||||
'PLN', 'EUR', 'STD', 'USD', 'QAR', 'EUR', 'RON', 'RUB', 'RWF', 'STD', 'ANG',
|
||||
'MAD', 'XCD', 'SHP', 'XCD', 'XCD', 'EUR', 'XCD', 'EUR', 'USD', 'WST', 'EUR',
|
||||
'SAR', 'SPL', 'XOF', 'RSD', 'SCR', 'SLL', 'XAG', 'SGD', 'ANG', 'ANG', 'EUR',
|
||||
'EUR', 'SBD', 'SOS', 'ZAR', 'GBP', 'GBP', 'EUR', 'XDR', 'LKR', 'SDG', 'SRD',
|
||||
'NOK', 'SZL', 'SEK', 'CHF', 'SYP', 'TWD', 'RUB', 'TJS', 'TZS', 'THB', 'IDR',
|
||||
'TTD', 'XOF', 'NZD', 'TOP', 'TTD', 'TND', 'TRY', 'TMM', 'USD', 'TVD', 'UGX',
|
||||
'UAH', 'AED', 'GBP', 'USD', 'USD', 'UYU', 'USD', 'UZS', 'VUV', 'EUR', 'VEB',
|
||||
'VEF', 'VND', 'USD', 'USD', 'USD', 'XPF', 'MAD', 'YER', 'ZMK', 'ZWD'
|
||||
'AED', 'AFN', 'ALL', 'AMD', 'ANG', 'AOA', 'ARS', 'AUD', 'AWG', 'AZN',
|
||||
'BAM', 'BBD', 'BDT', 'BGN', 'BHD', 'BIF', 'BMD', 'BND', 'BOB', 'BRL',
|
||||
'BSD', 'BTN', 'BWP', 'BYR', 'BZD', 'CAD', 'CDF', 'CHF', 'CLP', 'CNY',
|
||||
'COP', 'CRC', 'CUP', 'CVE', 'CYP', 'CZK', 'DJF', 'DKK', 'DOP', 'DZD',
|
||||
'EEK', 'EGP', 'ERN', 'ETB', 'EUR', 'FJD', 'FKP', 'GBP', 'GEL', 'GGP',
|
||||
'GHS', 'GIP', 'GMD', 'GNF', 'GTQ', 'GYD', 'HKD', 'HNL', 'HRK', 'HTG',
|
||||
'HUF', 'IDR', 'ILS', 'IMP', 'INR', 'IQD', 'IRR', 'ISK', 'JEP', 'JMD',
|
||||
'JOD', 'JPY', 'KES', 'KGS', 'KHR', 'KMF', 'KPW', 'KRW', 'KWD', 'KYD',
|
||||
'KZT', 'LAK', 'LBP', 'LKR', 'LRD', 'LSL', 'LTL', 'LVL', 'LYD', 'MAD',
|
||||
'MDL', 'MGA', 'MKD', 'MMK', 'MNT', 'MOP', 'MRO', 'MTL', 'MUR', 'MVR',
|
||||
'MWK', 'MXN', 'MYR', 'MZN', 'NAD', 'NGN', 'NIO', 'NOK', 'NPR', 'NZD',
|
||||
'OMR', 'PAB', 'PEN', 'PGK', 'PHP', 'PKR', 'PLN', 'PYG', 'QAR', 'RON',
|
||||
'RSD', 'RUB', 'RWF', 'SAR', 'SBD', 'SCR', 'SDG', 'SEK', 'SGD', 'SHP',
|
||||
'SLL', 'SOS', 'SPL', 'SRD', 'STD', 'SVC', 'SYP', 'SZL', 'THB', 'TJS',
|
||||
'TMM', 'TND', 'TOP', 'TRY', 'TTD', 'TVD', 'TWD', 'TZS', 'UAH', 'UGX',
|
||||
'USD', 'UYU', 'UZS', 'VEB', 'VEF', 'VND', 'VUV', 'WST', 'XAF', 'XAG',
|
||||
'XAU', 'XCD', 'XDR', 'XOF', 'XPD', 'XPF', 'XPT', 'YER', 'ZAR', 'ZMK',
|
||||
'ZWD'
|
||||
]
|
||||
|
||||
# Updated currency this arry will contain the final result
|
||||
updated_currency = {}
|
||||
|
||||
def get_updated_currency(self, currency_array, main_currency, max_delta_days):
|
||||
def get_updated_currency(self, currency_array, main_currency,
|
||||
max_delta_days):
|
||||
"""Interface method that will retrieve the currency
|
||||
This function has to be reinplemented in child
|
||||
"""
|
||||
@@ -397,23 +407,26 @@ class Curreny_getter_interface(object):
|
||||
)
|
||||
|
||||
# We always have a warning when rate_date != today
|
||||
rate_date_str = datetime.strftime(rate_date, '%Y-%m-%d')
|
||||
if rate_date_str != datetime.strftime(datetime.today(), '%Y-%m-%d'):
|
||||
rate_date_str = datetime.strftime(rate_date,
|
||||
DEFAULT_SERVER_DATE_FORMAT)
|
||||
if rate_date.date() != datetime.today().date():
|
||||
msg = "The rate timestamp (%s) is not today's date"
|
||||
self.log_info = ("WARNING : %s %s") % (msg, rate_date_str)
|
||||
_logger.warning(msg, rate_date_str)
|
||||
|
||||
|
||||
# Yahoo ########################################################################
|
||||
# Yahoo #######################################################################
|
||||
class Yahoo_getter(Curreny_getter_interface):
|
||||
"""Implementation of Currency_getter_factory interface
|
||||
for Yahoo finance service
|
||||
"""
|
||||
|
||||
def get_updated_currency(self, currency_array, main_currency, max_delta_days):
|
||||
def get_updated_currency(self, currency_array, main_currency,
|
||||
max_delta_days):
|
||||
"""implementation of abstract method of curreny_getter_interface"""
|
||||
self.validate_cur(main_currency)
|
||||
url = 'http://download.finance.yahoo.com/d/quotes.txt?s="%s"=X&f=sl1c1abg'
|
||||
url = ('http://download.finance.yahoo.com/d/'
|
||||
'quotes.txt?s="%s"=X&f=sl1c1abg')
|
||||
if main_currency in currency_array:
|
||||
currency_array.remove(main_currency)
|
||||
for curr in currency_array:
|
||||
@@ -438,15 +451,23 @@ class Admin_ch_getter(Curreny_getter_interface):
|
||||
def rate_retrieve(self, dom, ns, curr):
|
||||
"""Parse a dom node to retrieve currencies data"""
|
||||
res = {}
|
||||
xpath_rate_currency = "/def:wechselkurse/def:devise[@code='%s']/def:kurs/text()" % (curr.lower())
|
||||
xpath_rate_ref = "/def:wechselkurse/def:devise[@code='%s']/def:waehrung/text()" % (curr.lower())
|
||||
res['rate_currency'] = float(dom.xpath(xpath_rate_currency, namespaces=ns)[0])
|
||||
res['rate_ref'] = float((dom.xpath(xpath_rate_ref, namespaces=ns)[0]).split(' ')[0])
|
||||
xpath_rate_currency = ("/def:wechselkurse/def:devise[@code='%s']/"
|
||||
"def:kurs/text()") % (curr.lower())
|
||||
xpath_rate_ref = ("/def:wechselkurse/def:devise[@code='%s']/"
|
||||
"def:waehrung/text()") % (curr.lower())
|
||||
res['rate_currency'] = float(
|
||||
dom.xpath(xpath_rate_currency, namespaces=ns)[0]
|
||||
)
|
||||
res['rate_ref'] = float(
|
||||
(dom.xpath(xpath_rate_ref, namespaces=ns)[0]).split(' ')[0]
|
||||
)
|
||||
return res
|
||||
|
||||
def get_updated_currency(self, currency_array, main_currency, max_delta_days):
|
||||
def get_updated_currency(self, currency_array, main_currency,
|
||||
max_delta_days):
|
||||
"""Implementation of abstract method of Curreny_getter_interface"""
|
||||
url = 'http://www.afd.admin.ch/publicdb/newdb/mwst_kurse/wechselkurse.php'
|
||||
url = ('http://www.afd.admin.ch/publicdb/newdb/'
|
||||
'mwst_kurse/wechselkurse.php')
|
||||
# We do not want to update the main currency
|
||||
if main_currency in currency_array:
|
||||
currency_array.remove(main_currency)
|
||||
@@ -456,21 +477,36 @@ class Admin_ch_getter(Curreny_getter_interface):
|
||||
rawfile = self.get_url(url)
|
||||
dom = etree.fromstring(rawfile)
|
||||
_logger.debug("Admin.ch sent a valid XML file")
|
||||
adminch_ns = {'def': 'http://www.afd.admin.ch/publicdb/newdb/mwst_kurse'}
|
||||
rate_date = dom.xpath('/def:wechselkurse/def:datum/text()', namespaces=adminch_ns)[0]
|
||||
rate_date_datetime = datetime.strptime(rate_date, '%Y-%m-%d')
|
||||
adminch_ns = {
|
||||
'def': 'http://www.afd.admin.ch/publicdb/newdb/mwst_kurse'
|
||||
}
|
||||
rate_date = dom.xpath(
|
||||
'/def:wechselkurse/def:datum/text()',
|
||||
namespaces=adminch_ns
|
||||
)
|
||||
rate_date = rate_date[0]
|
||||
rate_date_datetime = datetime.strptime(rate_date,
|
||||
DEFAULT_SERVER_DATE_FORMAT)
|
||||
self.check_rate_date(rate_date_datetime, max_delta_days)
|
||||
# we dynamically update supported currencies
|
||||
self.supported_currency_array = dom.xpath("/def:wechselkurse/def:devise/@code", namespaces=adminch_ns)
|
||||
self.supported_currency_array = [x.upper() for x in self.supported_currency_array]
|
||||
self.supported_currency_array = dom.xpath(
|
||||
"/def:wechselkurse/def:devise/@code",
|
||||
namespaces=adminch_ns
|
||||
)
|
||||
self.supported_currency_array = [x.upper() for x
|
||||
in self.supported_currency_array]
|
||||
self.supported_currency_array.append('CHF')
|
||||
|
||||
_logger.debug("Supported currencies = " + str(self.supported_currency_array))
|
||||
_logger.debug(
|
||||
"Supported currencies = " + str(self.supported_currency_array)
|
||||
)
|
||||
self.validate_cur(main_currency)
|
||||
if main_currency != 'CHF':
|
||||
main_curr_data = self.rate_retrieve(dom, adminch_ns, main_currency)
|
||||
# 1 MAIN_CURRENCY = main_rate CHF
|
||||
main_rate = main_curr_data['rate_currency'] / main_curr_data['rate_ref']
|
||||
rate_curr = main_curr_data['rate_currency']
|
||||
rate_ref = main_curr_data['rate_ref']
|
||||
main_rate = rate_curr / rate_ref
|
||||
for curr in currency_array:
|
||||
self.validate_cur(curr)
|
||||
if curr == 'CHF':
|
||||
@@ -481,7 +517,8 @@ class Admin_ch_getter(Curreny_getter_interface):
|
||||
if main_currency == 'CHF':
|
||||
rate = curr_data['rate_ref'] / curr_data['rate_currency']
|
||||
else:
|
||||
rate = main_rate * curr_data['rate_ref'] / curr_data['rate_currency']
|
||||
rate = (main_rate * curr_data['rate_ref'] /
|
||||
curr_data['rate_currency'])
|
||||
self.updated_currency[curr] = rate
|
||||
_logger.debug(
|
||||
"Rate retrieved : 1 %s = %s %s" % (main_currency, rate, curr)
|
||||
@@ -501,17 +538,21 @@ class ECB_getter(Curreny_getter_interface):
|
||||
|
||||
"""
|
||||
res = {}
|
||||
xpath_curr_rate = "/gesmes:Envelope/def:Cube/def:Cube/def:Cube[@currency='%s']/@rate" % (curr.upper())
|
||||
res['rate_currency'] = float(dom.xpath(xpath_curr_rate, namespaces=ns)[0])
|
||||
xpath_curr_rate = ("/gesmes:Envelope/def:Cube/def:Cube/"
|
||||
"def:Cube[@currency='%s']/@rate") % (curr.upper())
|
||||
res['rate_currency'] = float(
|
||||
dom.xpath(xpath_curr_rate, namespaces=ns)[0]
|
||||
)
|
||||
return res
|
||||
|
||||
def get_updated_currency(self, currency_array, main_currency, max_delta_days):
|
||||
def get_updated_currency(self, currency_array, main_currency,
|
||||
max_delta_days):
|
||||
"""implementation of abstract method of Curreny_getter_interface"""
|
||||
url = 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'
|
||||
# Important : as explained on the ECB web site, the currencies are
|
||||
# at the beginning of the afternoon ; so, until 3 p.m. Paris time
|
||||
# the currency rates are the ones of trading day N-1
|
||||
# see http://www.ecb.europa.eu/stats/exchange/eurofxref/html/index.en.html
|
||||
# http://www.ecb.europa.eu/stats/exchange/eurofxref/html/index.en.html
|
||||
|
||||
# We do not want to update the main currency
|
||||
if main_currency in currency_array:
|
||||
@@ -528,13 +569,17 @@ class ECB_getter(Curreny_getter_interface):
|
||||
}
|
||||
rate_date = dom.xpath('/gesmes:Envelope/def:Cube/def:Cube/@time',
|
||||
namespaces=ecb_ns)[0]
|
||||
rate_date_datetime = datetime.strptime(rate_date, '%Y-%m-%d')
|
||||
rate_date_datetime = datetime.strptime(rate_date,
|
||||
DEFAULT_SERVER_DATE_FORMAT)
|
||||
self.check_rate_date(rate_date_datetime, max_delta_days)
|
||||
# We dynamically update supported currencies
|
||||
self.supported_currency_array = dom.xpath("/gesmes:Envelope/def:Cube/def:Cube/def:Cube/@currency",
|
||||
namespaces=ecb_ns)
|
||||
self.supported_currency_array = dom.xpath(
|
||||
"/gesmes:Envelope/def:Cube/def:Cube/def:Cube/@currency",
|
||||
namespaces=ecb_ns
|
||||
)
|
||||
self.supported_currency_array.append('EUR')
|
||||
_logger.debug("Supported currencies = %s " % self.supported_currency_array)
|
||||
_logger.debug("Supported currencies = %s " %
|
||||
self.supported_currency_array)
|
||||
self.validate_cur(main_currency)
|
||||
if main_currency != 'EUR':
|
||||
main_curr_data = self.rate_retrieve(dom, ecb_ns, main_currency)
|
||||
@@ -547,9 +592,12 @@ class ECB_getter(Curreny_getter_interface):
|
||||
if main_currency == 'EUR':
|
||||
rate = curr_data['rate_currency']
|
||||
else:
|
||||
rate = curr_data['rate_currency'] / main_curr_data['rate_currency']
|
||||
rate = (curr_data['rate_currency'] /
|
||||
main_curr_data['rate_currency'])
|
||||
self.updated_currency[curr] = rate
|
||||
_logger.debug("Rate retrieved : 1 %s = %s %s" % (main_currency, rate, curr))
|
||||
_logger.debug(
|
||||
"Rate retrieved : 1 %s = %s %s" % (main_currency, rate, curr)
|
||||
)
|
||||
return self.updated_currency, self.log_info
|
||||
|
||||
|
||||
@@ -564,13 +612,18 @@ class PL_NBP_getter(Curreny_getter_interface):
|
||||
""" Parse a dom node to retrieve
|
||||
currencies data"""
|
||||
res = {}
|
||||
xpath_rate_currency = "/tabela_kursow/pozycja[kod_waluty='%s']/kurs_sredni/text()" % (curr.upper())
|
||||
xpath_rate_ref = "/tabela_kursow/pozycja[kod_waluty='%s']/przelicznik/text()" % (curr.upper())
|
||||
res['rate_currency'] = float(dom.xpath(xpath_rate_currency, namespaces=ns)[0].replace(',', '.'))
|
||||
xpath_rate_currency = ("/tabela_kursow/pozycja[kod_waluty='%s']/"
|
||||
"kurs_sredni/text()") % (curr.upper())
|
||||
xpath_rate_ref = ("/tabela_kursow/pozycja[kod_waluty='%s']/"
|
||||
"przelicznik/text()") % (curr.upper())
|
||||
res['rate_currency'] = float(
|
||||
dom.xpath(xpath_rate_currency, namespaces=ns)[0].replace(',', '.')
|
||||
)
|
||||
res['rate_ref'] = float(dom.xpath(xpath_rate_ref, namespaces=ns)[0])
|
||||
return res
|
||||
|
||||
def get_updated_currency(self, currency_array, main_currency, max_delta_days):
|
||||
def get_updated_currency(self, currency_array, main_currency,
|
||||
max_delta_days):
|
||||
"""implementation of abstract method of Curreny_getter_interface"""
|
||||
# LastA.xml is always the most recent one
|
||||
url = 'http://www.nbp.pl/kursy/xml/LastA.xml'
|
||||
@@ -584,18 +637,25 @@ class PL_NBP_getter(Curreny_getter_interface):
|
||||
dom = etree.fromstring(rawfile)
|
||||
ns = {} # Cool, there are no namespaces !
|
||||
_logger.debug("NBP.pl sent a valid XML file")
|
||||
rate_date = dom.xpath('/tabela_kursow/data_publikacji/text()', namespaces=ns)[0]
|
||||
rate_date_datetime = datetime.strptime(rate_date, '%Y-%m-%d')
|
||||
rate_date = dom.xpath('/tabela_kursow/data_publikacji/text()',
|
||||
namespaces=ns)[0]
|
||||
rate_date_datetime = datetime.strptime(rate_date,
|
||||
DEFAULT_SERVER_DATE_FORMAT)
|
||||
self.check_rate_date(rate_date_datetime, max_delta_days)
|
||||
# We dynamically update supported currencies
|
||||
self.supported_currency_array = dom.xpath('/tabela_kursow/pozycja/kod_waluty/text()', namespaces=ns)
|
||||
self.supported_currency_array = dom.xpath(
|
||||
'/tabela_kursow/pozycja/kod_waluty/text()',
|
||||
namespaces=ns
|
||||
)
|
||||
self.supported_currency_array.append('PLN')
|
||||
_logger.debug("Supported currencies = %s" % self.supported_currency_array)
|
||||
_logger.debug("Supported currencies = %s" %
|
||||
self.supported_currency_array)
|
||||
self.validate_cur(main_currency)
|
||||
if main_currency != 'PLN':
|
||||
main_curr_data = self.rate_retrieve(dom, ns, main_currency)
|
||||
# 1 MAIN_CURRENCY = main_rate PLN
|
||||
main_rate = main_curr_data['rate_currency'] / main_curr_data['rate_ref']
|
||||
main_rate = (main_curr_data['rate_currency'] /
|
||||
main_curr_data['rate_ref'])
|
||||
for curr in currency_array:
|
||||
self.validate_cur(curr)
|
||||
if curr == 'PLN':
|
||||
@@ -606,9 +666,11 @@ class PL_NBP_getter(Curreny_getter_interface):
|
||||
if main_currency == 'PLN':
|
||||
rate = curr_data['rate_ref'] / curr_data['rate_currency']
|
||||
else:
|
||||
rate = main_rate * curr_data['rate_ref'] / curr_data['rate_currency']
|
||||
rate = (main_rate * curr_data['rate_ref'] /
|
||||
curr_data['rate_currency'])
|
||||
self.updated_currency[curr] = rate
|
||||
_logger.debug("Rate retrieved : %s = %s %s" % (main_currency, rate, curr))
|
||||
_logger.debug("Rate retrieved : %s = %s %s" %
|
||||
(main_currency, rate, curr))
|
||||
return self.updated_currency, self.log_info
|
||||
|
||||
|
||||
@@ -623,7 +685,8 @@ class Banxico_getter(Curreny_getter_interface):
|
||||
""" Get currency exchange from Banxico.xml and proccess it
|
||||
TODO: Get correct data from xml instead of process string
|
||||
"""
|
||||
url = 'http://www.banxico.org.mx/rsscb/rss?BMXC_canal=pagos&BMXC_idioma=es'
|
||||
url = ('http://www.banxico.org.mx/rsscb/rss?'
|
||||
'BMXC_canal=pagos&BMXC_idioma=es')
|
||||
|
||||
from xml.dom.minidom import parse
|
||||
from StringIO import StringIO
|
||||
@@ -640,7 +703,8 @@ class Banxico_getter(Curreny_getter_interface):
|
||||
|
||||
return float(rate)
|
||||
|
||||
def get_updated_currency(self, currency_array, main_currency, max_delta_days=1):
|
||||
def get_updated_currency(self, currency_array, main_currency,
|
||||
max_delta_days=1):
|
||||
"""implementation of abstract method of Curreny_getter_interface"""
|
||||
logger = logging.getLogger(__name__)
|
||||
# we do not want to update the main currency
|
||||
@@ -662,7 +726,8 @@ class Banxico_getter(Curreny_getter_interface):
|
||||
continue
|
||||
|
||||
self.updated_currency[curr] = rate
|
||||
logger.debug("Rate retrieved : %s = %s %s" % (main_currency, rate, curr))
|
||||
logger.debug("Rate retrieved : %s = %s %s" %
|
||||
(main_currency, rate, curr))
|
||||
|
||||
|
||||
# CA BOC ##### Bank of Canada #############################################
|
||||
@@ -672,11 +737,13 @@ class CA_BOC_getter(Curreny_getter_interface):
|
||||
|
||||
"""
|
||||
|
||||
def get_updated_currency(self, currency_array, main_currency, max_delta_days):
|
||||
def get_updated_currency(self, currency_array, main_currency,
|
||||
max_delta_days):
|
||||
"""implementation of abstract method of Curreny_getter_interface"""
|
||||
|
||||
# as of Jan 2014 BOC is publishing noon rates for about 60 currencies
|
||||
url = 'http://www.bankofcanada.ca/stats/assets/rates_rss/noon/en_%s.xml'
|
||||
url = ('http://www.bankofcanada.ca/stats/assets/'
|
||||
'rates_rss/noon/en_%s.xml')
|
||||
# closing rates are available as well (please note there are only 12
|
||||
# currencies reported):
|
||||
# http://www.bankofcanada.ca/stats/assets/rates_rss/closing/en_%s.xml
|
||||
@@ -718,11 +785,15 @@ class CA_BOC_getter(Curreny_getter_interface):
|
||||
.astimezone(pytz.utc).replace(tzinfo=None)
|
||||
self.check_rate_date(rate_date_datetime, max_delta_days)
|
||||
self.updated_currency[curr] = rate
|
||||
_logger.debug("BOC Rate retrieved : %s = %s %s" % (main_currency, rate, curr))
|
||||
_logger.debug("BOC Rate retrieved : %s = %s %s" %
|
||||
(main_currency, rate, curr))
|
||||
else:
|
||||
_logger.error("Exchange data format error for Bank of Canada -\
|
||||
%s. Please check provider data format and/or source code." % curr)
|
||||
raise osv.except_osv('Error !', 'Exchange data format error for\
|
||||
Bank of Canada - %s !' % str(curr))
|
||||
_logger.error(
|
||||
"Exchange data format error for Bank of Canada -"
|
||||
"%s. Please check provider data format "
|
||||
"and/or source code." % curr)
|
||||
raise osv.except_osv('Error !',
|
||||
'Exchange data format error for\
|
||||
Bank of Canada - %s !' % str(curr))
|
||||
|
||||
return self.updated_currency, self.log_info
|
||||
|
||||
21
account_balance_line/__init__.py
Normal file
21
account_balance_line/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Author: Vincent Renaville (Camptocamp)
|
||||
# Copyright 2010-2014 Camptocamp SA
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from . import account_move_line
|
||||
50
account_balance_line/__openerp__.py
Normal file
50
account_balance_line/__openerp__.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Author: Vincent Renaville (Camptocamp)
|
||||
# Copyright 2010-2014 Camptocamp SA
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
{'name': 'Balance on lines',
|
||||
'summary': 'Display balance totals in move line view',
|
||||
'version': '1.1',
|
||||
'author': 'Camptocamp',
|
||||
'maintainter': 'Camptocamp',
|
||||
'category': 'Accounting',
|
||||
'depends': ['account'],
|
||||
'description': """
|
||||
Balance for a line
|
||||
==================
|
||||
|
||||
Add a balance total for grouped lines in move line view.
|
||||
|
||||
Balance field will only be shown when move lines are grouped by account
|
||||
or filtered by account.
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
* Vincent revaville <vincent.renaville@camptocamp.com>
|
||||
* Yannick Vaucher <yannick.vaucher@camptocamp.com>
|
||||
""",
|
||||
'website': 'http://www.camptocamp.com',
|
||||
'data': ['account_move_line_view.xml'],
|
||||
'tests': [],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'license': 'AGPL-3',
|
||||
'application': False,
|
||||
}
|
||||
42
account_balance_line/account_move_line.py
Normal file
42
account_balance_line/account_move_line.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Author: Vincent Renaville (Camptocamp)
|
||||
# Copyright 2010-2014 Camptocamp SA
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from openerp.osv import orm, fields
|
||||
|
||||
|
||||
class account_move_line(orm.Model):
|
||||
_inherit = "account.move.line"
|
||||
|
||||
def _line_balance(self, cr, uid, ids, field, arg, context=None):
|
||||
res = {}
|
||||
move_lines = self.read(cr, uid, ids,
|
||||
['debit', 'credit'],
|
||||
context=context)
|
||||
|
||||
for line in move_lines:
|
||||
res[line['id']] = line['debit'] - line['credit']
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'line_balance': fields.function(
|
||||
_line_balance, method=True,
|
||||
string='Balance',
|
||||
store=True),
|
||||
}
|
||||
30
account_balance_line/account_move_line_view.xml
Normal file
30
account_balance_line/account_move_line_view.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="view_account_move_line_filter_balance" model="ir.ui.view">
|
||||
<field name="name">Journal Items add visibilty for balance</field>
|
||||
<field name="model">account.move.line</field>
|
||||
<field name="inherit_id" ref="account.view_account_move_line_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="account_id" position="attributes">
|
||||
<attribute name="context">{'invisible_balance': False}</attribute>
|
||||
</field>
|
||||
<filter string="Account" position="attributes">
|
||||
<attribute name="context">{'group_by': 'account_id', 'invisible_balance': False}</attribute>
|
||||
</filter>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="account_move_line_balance_custom">
|
||||
<field name="name">account_move_line_balance_custom</field>
|
||||
<field name="model">account.move.line</field>
|
||||
<field name="inherit_id" ref="account.view_move_line_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="credit" position="after" >
|
||||
<field name="line_balance" sum="Total Balance" invisible="context.get('invisible_balance', True)"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
53
account_balance_line/i18n/account_balance_line.pot
Normal file
53
account_balance_line/i18n/account_balance_line.pot
Normal file
@@ -0,0 +1,53 @@
|
||||
# Translation of OpenERP Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * account_balance_line
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: OpenERP Server 7.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-06-20 14:11+0000\n"
|
||||
"PO-Revision-Date: 2014-06-20 14:11+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: account_balance_line
|
||||
#: view:account.move.line:0
|
||||
msgid "Account"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_balance_line
|
||||
#: model:ir.model,name:account_balance_line.model_account_move_line
|
||||
msgid "Entry lines"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_balance_line
|
||||
#: code:_description:0
|
||||
#, python-format
|
||||
msgid "Journal Items"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_balance_line
|
||||
#: field:account.move.line,line_balance:0
|
||||
msgid "Balance"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_balance_line
|
||||
#: view:account.move.line:0
|
||||
msgid "Total Balance"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_balance_line
|
||||
#: view:account.move.line:0
|
||||
msgid "{'group_by': 'account_id', 'invisible_balance': False}"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_balance_line
|
||||
#: view:account.move.line:0
|
||||
msgid "{'invisible_balance': False}"
|
||||
msgstr ""
|
||||
|
||||
53
account_balance_line/i18n/fr.po
Normal file
53
account_balance_line/i18n/fr.po
Normal file
@@ -0,0 +1,53 @@
|
||||
# Translation of OpenERP Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * account_balance_line
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: OpenERP Server 7.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-06-20 14:11+0000\n"
|
||||
"PO-Revision-Date: 2014-06-20 14:11+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: account_balance_line
|
||||
#: view:account.move.line:0
|
||||
msgid "Account"
|
||||
msgstr "Compte"
|
||||
|
||||
#. module: account_balance_line
|
||||
#: model:ir.model,name:account_balance_line.model_account_move_line
|
||||
msgid "Entry lines"
|
||||
msgstr "Lignes d'écriture"
|
||||
|
||||
#. module: account_balance_line
|
||||
#: code:_description:0
|
||||
#, python-format
|
||||
msgid "Journal Items"
|
||||
msgstr "Écritures comptables"
|
||||
|
||||
#. module: account_balance_line
|
||||
#: field:account.move.line,line_balance:0
|
||||
msgid "Balance"
|
||||
msgstr "Balance"
|
||||
|
||||
#. module: account_balance_line
|
||||
#: view:account.move.line:0
|
||||
msgid "Total Balance"
|
||||
msgstr "Balance Totale"
|
||||
|
||||
#. module: account_balance_line
|
||||
#: view:account.move.line:0
|
||||
msgid "{'group_by': 'account_id', 'invisible_balance': False}"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_balance_line
|
||||
#: view:account.move.line:0
|
||||
msgid "{'invisible_balance': False}"
|
||||
msgstr ""
|
||||
|
||||
31
account_journal_period_close/__init__.py
Normal file
31
account_journal_period_close/__init__.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
#
|
||||
# Authors: Adrien Peiffer
|
||||
# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
|
||||
# All Rights Reserved
|
||||
#
|
||||
# WARNING: This program as such is intended to be used by professional
|
||||
# programmers who take the whole responsibility of assessing all potential
|
||||
# consequences resulting from its eventual inadequacies and bugs.
|
||||
# End users who are looking for a ready-to-use solution with commercial
|
||||
# guarantees and support are strongly advised to contact a Free Software
|
||||
# Service Company.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
#
|
||||
|
||||
from . import model
|
||||
from . import tests
|
||||
62
account_journal_period_close/__openerp__.py
Normal file
62
account_journal_period_close/__openerp__.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
#
|
||||
# Authors: Adrien Peiffer
|
||||
# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
|
||||
# All Rights Reserved
|
||||
#
|
||||
# WARNING: This program as such is intended to be used by professional
|
||||
# programmers who take the whole responsibility of assessing all potential
|
||||
# consequences resulting from its eventual inadequacies and bugs.
|
||||
# End users who are looking for a ready-to-use solution with commercial
|
||||
# guarantees and support are strongly advised to contact a Free Software
|
||||
# Service Company.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
#
|
||||
|
||||
{
|
||||
"name": "Account Journal Period Close",
|
||||
"version": "1.0",
|
||||
"author": "ACSONE SA/NV",
|
||||
"maintainer": "ACSONE SA/NV",
|
||||
"website": "http://www.acsone.eu",
|
||||
"images": [],
|
||||
"category": "Accounting",
|
||||
"depends": [
|
||||
"account"],
|
||||
"description": """
|
||||
Close period per journal
|
||||
========================
|
||||
|
||||
This module allows fine grained control of period closing.
|
||||
Each journal can be closed independently for any period
|
||||
(using buttons on the fiscal period view).
|
||||
|
||||
A common use case is letting accountants close the sale
|
||||
and purchase journals when the VAT declaration is done for
|
||||
a given period, while leaving the miscellaneous journal open.
|
||||
|
||||
From a technical standpoint, the module leverages the
|
||||
account.journal.period model that is present in Odoo core.
|
||||
""",
|
||||
"data": ['view/account_view.xml'],
|
||||
"demo": [],
|
||||
"test": [],
|
||||
"licence": "AGPL-3",
|
||||
"installable": True,
|
||||
"auto_install": False,
|
||||
"application": True,
|
||||
}
|
||||
29
account_journal_period_close/model/__init__.py
Normal file
29
account_journal_period_close/model/__init__.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
#
|
||||
# Authors: Adrien Peiffer
|
||||
# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
|
||||
# All Rights Reserved
|
||||
#
|
||||
# WARNING: This program as such is intended to be used by professional
|
||||
# programmers who take the whole responsibility of assessing all potential
|
||||
# consequences resulting from its eventual inadequacies and bugs.
|
||||
# End users who are looking for a ready-to-use solution with commercial
|
||||
# guarantees and support are strongly advised to contact a Free Software
|
||||
# Service Company.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
#
|
||||
from . import account
|
||||
86
account_journal_period_close/model/account.py
Normal file
86
account_journal_period_close/model/account.py
Normal file
@@ -0,0 +1,86 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
#
|
||||
# Authors: Adrien Peiffer
|
||||
# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
|
||||
# All Rights Reserved
|
||||
#
|
||||
# WARNING: This program as such is intended to be used by professional
|
||||
# programmers who take the whole responsibility of assessing all potential
|
||||
# consequences resulting from its eventual inadequacies and bugs.
|
||||
# End users who are looking for a ready-to-use solution with commercial
|
||||
# guarantees and support are strongly advised to contact a Free Software
|
||||
# Service Company.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
#
|
||||
|
||||
from openerp.osv import orm, fields
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
class account_period(orm.Model):
|
||||
_inherit = 'account.period'
|
||||
_columns = {
|
||||
'journal_period_ids': fields.one2many('account.journal.period',
|
||||
'period_id', 'Journal states'),
|
||||
}
|
||||
|
||||
|
||||
class account_journal_period(orm.Model):
|
||||
_inherit = 'account.journal.period'
|
||||
_order = "type,name"
|
||||
_columns = {
|
||||
'type': fields.related('journal_id', 'type', type='char',
|
||||
relation='account.journal',
|
||||
string='Journal Type',
|
||||
store=True, readonly=True)
|
||||
}
|
||||
|
||||
def _check(self, cr, uid, ids, context=None):
|
||||
return True
|
||||
|
||||
def action_draft(self, cr, uid, ids, context=None):
|
||||
return self.write(cr, uid, ids, {'state': 'draft'})
|
||||
|
||||
def action_done(self, cr, uid, ids, context=None):
|
||||
for journal_period in self.browse(cr, uid, ids, context=context):
|
||||
draft_move_ids = self.pool.get('account.move')\
|
||||
.search(cr, uid, [('period_id', '=',
|
||||
journal_period.period_id.id),
|
||||
('state', '=', "draft"),
|
||||
('journal_id', '=',
|
||||
journal_period.journal_id.id)],
|
||||
context=context)
|
||||
if draft_move_ids:
|
||||
raise orm.except_orm(_('Invalid Action!'),
|
||||
_('In order to close a journal,'
|
||||
' you must first post related'
|
||||
' journal entries.'))
|
||||
return self.write(cr, uid, ids, {'state': 'done'})
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
if 'name' not in values:
|
||||
if values.get('period_id') and values.get('journal_id'):
|
||||
journal = self.pool.get('account.journal')\
|
||||
.browse(cr, uid, values['journal_id'], context=context)
|
||||
period = self.pool.get('account.period')\
|
||||
.browse(cr, uid, values['period_id'], context=context)
|
||||
values.update({'name': (journal.code or journal.name)+':' +
|
||||
(period.name or '')}),
|
||||
return super(account_journal_period, self).create(cr,
|
||||
uid,
|
||||
values,
|
||||
context=context)
|
||||
30
account_journal_period_close/tests/__init__.py
Normal file
30
account_journal_period_close/tests/__init__.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
#
|
||||
# Authors: Adrien Peiffer
|
||||
# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
|
||||
# All Rights Reserved
|
||||
#
|
||||
# WARNING: This program as such is intended to be used by professional
|
||||
# programmers who take the whole responsibility of assessing all potential
|
||||
# consequences resulting from its eventual inadequacies and bugs.
|
||||
# End users who are looking for a ready-to-use solution with commercial
|
||||
# guarantees and support are strongly advised to contact a Free Software
|
||||
# Service Company.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
#
|
||||
|
||||
from . import test_account_journal_period_close
|
||||
@@ -0,0 +1,196 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
#
|
||||
# Authors: Adrien Peiffer
|
||||
# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu)
|
||||
# All Rights Reserved
|
||||
#
|
||||
# WARNING: This program as such is intended to be used by professional
|
||||
# programmers who take the whole responsibility of assessing all potential
|
||||
# consequences resulting from its eventual inadequacies and bugs.
|
||||
# End users who are looking for a ready-to-use solution with commercial
|
||||
# guarantees and support are strongly advised to contact a Free Software
|
||||
# Service Company.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
#
|
||||
|
||||
import openerp.tests.common as common
|
||||
from openerp.osv import orm
|
||||
from datetime import datetime
|
||||
|
||||
DB = common.DB
|
||||
ADMIN_USER_ID = common.ADMIN_USER_ID
|
||||
|
||||
|
||||
def get_simple_account_move_values(self, period_id, journal_id):
|
||||
sale_product_account_id = self.ref('account.a_sale')
|
||||
cash_account_id = self.ref('account.cash')
|
||||
partner_id = self.ref('base.res_partner_2')
|
||||
year = datetime.now().strftime('%Y')
|
||||
return {'partner_id': partner_id,
|
||||
'period_id': period_id,
|
||||
'date': year + '-01-01',
|
||||
'journal_id': journal_id,
|
||||
'line_id': [(0, 0, {'name': 'test',
|
||||
'account_id': cash_account_id,
|
||||
'debit': 50.0,
|
||||
}),
|
||||
(0, 0, {'name': 'test_conterpart',
|
||||
'account_id': sale_product_account_id,
|
||||
'credit': 50.0,
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def close_period(self, period_id, context=None):
|
||||
close_period_wizard_id =\
|
||||
self.registry('account.period.close').create(self.cr,
|
||||
self.uid,
|
||||
{'sure': True
|
||||
},
|
||||
context=context)
|
||||
context.update({'active_ids': [period_id]})
|
||||
self.registry('account.period.close')\
|
||||
.data_save(self.cr,
|
||||
self.uid,
|
||||
[close_period_wizard_id],
|
||||
context=context)
|
||||
|
||||
|
||||
def create_journal_period(self, period_id, journal_id, context):
|
||||
jour_per_obj = self.registry('account.journal.period')
|
||||
journal_period_id = jour_per_obj.create(self.cr,
|
||||
self.uid,
|
||||
{'period_id': period_id,
|
||||
'journal_id': journal_id,
|
||||
},
|
||||
context=context)
|
||||
return journal_period_id
|
||||
|
||||
|
||||
def journal_period_done(self, journal_period_id, context):
|
||||
jour_per_obj = self.registry('account.journal.period')
|
||||
jour_per_obj.action_done(self.cr,
|
||||
self.uid,
|
||||
[journal_period_id],
|
||||
context=context)
|
||||
|
||||
|
||||
def journal_period_draft(self, journal_period_id, context):
|
||||
jour_per_obj = self.registry('account.journal.period')
|
||||
jour_per_obj.action_draft(self.cr,
|
||||
self.uid,
|
||||
[journal_period_id],
|
||||
context=context)
|
||||
|
||||
|
||||
class TestAccountConstraintChronology(common.TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestAccountConstraintChronology, self).setUp()
|
||||
|
||||
def test_close_period_open_journal(self):
|
||||
context = {}
|
||||
journal_id = self.ref('account.sales_journal')
|
||||
period_id = self.ref('account.period_1')
|
||||
close_period(self, period_id, context)
|
||||
journal_period_id = create_journal_period(self,
|
||||
period_id,
|
||||
journal_id,
|
||||
context)
|
||||
journal_period_draft(self, journal_period_id, context)
|
||||
self.registry('account.move')\
|
||||
.create(self.cr,
|
||||
self.uid,
|
||||
get_simple_account_move_values(self,
|
||||
period_id,
|
||||
journal_id),
|
||||
context=context)
|
||||
# Here, no exception should be raised because the journal's state is
|
||||
# draft although the period is closed
|
||||
|
||||
def test_open_period_close_journal(self):
|
||||
context = {}
|
||||
journal_id = self.ref('account.sales_journal')
|
||||
period_id = self.ref('account.period_1')
|
||||
journal_period_id = create_journal_period(self,
|
||||
period_id,
|
||||
journal_id,
|
||||
context)
|
||||
journal_period_done(self, journal_period_id, context)
|
||||
move_values = get_simple_account_move_values(self,
|
||||
period_id,
|
||||
journal_id)
|
||||
# I check if the exception is correctly raised at create of an account
|
||||
# move which is linked with a closed journal
|
||||
self.assertRaises(orm.except_orm,
|
||||
self.registry('account.move').create,
|
||||
self.cr, self.uid, move_values, context=context)
|
||||
|
||||
def test_change_journal_on_move(self):
|
||||
context = {}
|
||||
journal_id = self.ref('account.sales_journal')
|
||||
journal_cash_id = self.ref('account.cash_journal')
|
||||
period_id = self.ref('account.period_1')
|
||||
journal_period_id = create_journal_period(self,
|
||||
period_id,
|
||||
journal_id,
|
||||
context)
|
||||
journal_period_done(self, journal_period_id, context)
|
||||
move_values = get_simple_account_move_values(self,
|
||||
period_id,
|
||||
journal_cash_id)
|
||||
self.registry('account.move').create(self.cr,
|
||||
self.uid,
|
||||
move_values,
|
||||
context=context)
|
||||
# Standard of Odoo doesn't check account_journal_period at write on
|
||||
# account_move
|
||||
# issue on Odoo github : #1633
|
||||
|
||||
# I check if the exception is correctly raised
|
||||
"""self.assertRaises(orm.except_orm,
|
||||
self.registry('account.move').write,
|
||||
self.cr, self.uid, [move_id],
|
||||
{'journal_id': journal_id}, context=context)"""
|
||||
|
||||
def test_draft_move_close_journal(self):
|
||||
context = {}
|
||||
|
||||
jour_per_obj = self.registry('account.journal.period')
|
||||
journal_id = self.ref('account.sales_journal')
|
||||
period_id = self.ref('account.period_1')
|
||||
move_values = get_simple_account_move_values(self,
|
||||
period_id,
|
||||
journal_id)
|
||||
self.registry('account.move').create(self.cr,
|
||||
self.uid,
|
||||
move_values,
|
||||
context=context)
|
||||
journal_period_ids =\
|
||||
jour_per_obj.search(self.cr,
|
||||
self.uid,
|
||||
[('period_id', '=', period_id),
|
||||
('journal_id', '=', journal_id),
|
||||
],
|
||||
context=context)
|
||||
# I check if the exception is correctly raised at closing journal that
|
||||
# contains some draft account move
|
||||
self.assertRaises(orm.except_orm,
|
||||
jour_per_obj.action_done,
|
||||
self.cr, self.uid, journal_period_ids,
|
||||
context=context)
|
||||
33
account_journal_period_close/view/account_view.xml
Normal file
33
account_journal_period_close/view/account_view.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="view_account_period_form" model="ir.ui.view">
|
||||
<field name="name">account.period.form
|
||||
(account_journal_period_close)</field>
|
||||
<field name="model">account.period</field>
|
||||
<field name="inherit_id" ref="account.view_account_period_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//sheet/group" position="after">
|
||||
<notebook>
|
||||
<page string="Journals">
|
||||
<field name="journal_period_ids">
|
||||
<tree editable="bottom">
|
||||
<field name="journal_id" attrs="{'readonly': [('type', '!=', False)]}"/>
|
||||
<field name="type"/>
|
||||
<field name="state" readonly="0"
|
||||
invisible="1" />
|
||||
<button name="action_done"
|
||||
type="object" icon="gtk-cancel"
|
||||
states="draft" help="Close journal for this period"/>
|
||||
<button name="action_draft"
|
||||
type="object" icon="gtk-redo"
|
||||
states="done" help="Reopen journal for this period"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
22
account_move_line_search_extension/i18n/fr.po
Normal file
22
account_move_line_search_extension/i18n/fr.po
Normal file
@@ -0,0 +1,22 @@
|
||||
# Translation of OpenERP Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * account_move_line_search_extension
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: OpenERP Server 7.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-06-27 13:15+0000\n"
|
||||
"PO-Revision-Date: 2014-06-27 13:15+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: account_move_line_search_extension
|
||||
#: model:ir.actions.act_window,name:account_move_line_search_extension.action_account_move_line_search_extension
|
||||
#: model:ir.ui.menu,name:account_move_line_search_extension.menu_account_move_line_search_extension
|
||||
msgid "Journal Items Search All"
|
||||
msgstr "Écritures comptables"
|
||||
Reference in New Issue
Block a user