[FIX] a number of bugs

[ADD] payment order reconciliation
This commit is contained in:
OpenERP instance user
2011-12-18 23:45:27 +01:00
parent db48a96e72
commit ac8ee4b4e9
9 changed files with 159 additions and 70 deletions

View File

@@ -182,7 +182,7 @@
<data>
<field name="period_id" position="replace"/>
<xpath expr="/form/notebook/page[@string='Transaction']/field/tree" position="attributes">
<attribute name="colors">black:state == 'confirmed';red:duplicate == True;purple:match_multi == True;grey:state == 'draft';</attribute>
<attribute name="colors">black:state == 'confirmed';darkmagenta:match_multi == True;grey:state=='draft';crimson:duplicate == True;</attribute>
</xpath>
<xpath expr="/form/notebook/page[@string='Transaction']/field/tree/field[@name='name']" position="replace">
<field name="name" required="1"/>
@@ -468,7 +468,7 @@
<field name="model">account.bank.statement.line</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Statement lines" colors="black:state == 'confirmed';red:duplicate == True;purple:match_multi == True;grey:state == 'draft';">
<tree string="Statement lines" colors="black:state == 'confirmed';darkmagenta:match_multi == True;crimson:duplicate == True;grey:state=='draft';">
<field name="sequence" readonly="1" invisible="1"/>
<field name="date" groups="base.group_extended"/>
<field name="name"/>

View File

@@ -138,15 +138,16 @@ class banking_import_transaction(osv.osv):
candidates = [x for x in orders if
is_zero(x.total - trans.transferred_amount)]
if len(candidates) > 0:
# retrieve the common account_id, if any
account_id = False
for order in candidates:
for line in order_line.debit_move_line_id.move_id.line_id:
for line in order.line_ids[0].debit_move_line_id.move_id.line_id:
if line.account_id.type == 'other':
if account_id != line.account_id.id:
if account_id and account_id != line.account_id.id:
account_id = False
break
else:
account_id = line.account_id
account_id = line.account_id.id
# TODO at statement line confirm
# this action generates a reconcile object
@@ -536,7 +537,7 @@ class banking_import_transaction(osv.osv):
payment_line_obj = self.pool.get('payment.line')
transaction = self.browse(cr, uid, transaction_id, context=context)
if not transaction.payment_line_id:
osv.except_osv(
raise osv.except_osv(
_("Cannot link with storno"),
_("No direct debit order item"))
return payment_line_obj.debit_storno(
@@ -547,6 +548,49 @@ class banking_import_transaction(osv.osv):
transaction.storno_retry,
context=context)
def _reconcile_payment_order(
self, cr, uid, transaction_id, context=None):
"""
Creation of the reconciliation has been delegated to
*a* direct debit module, to allow for various direct debit styles
"""
payment_order_obj = self.pool.get('payment.order')
transaction = self.browse(cr, uid, transaction_id, context=context)
if not transaction.payment_order_id:
raise osv.except_osv(
_("Cannot reconcile"),
_("Cannot reconcile: no direct debit order"))
if transaction.payment_order_id.payment_order_type != 'debit':
raise osv.except_osv(
_("Cannot reconcile"),
_("Reconcile payment order not implemented"))
return payment_order_obj.debit_reconcile_transfer(
cr, uid,
transaction.payment_order_id.id,
transaction.statement_line_id.amount,
transaction.statement_line_id.currency,
context=context)
def _cancel_payment_order(
self, cr, uid, transaction_id, context=None):
"""
"""
payment_order_obj = self.pool.get('payment.order')
transaction = self.browse(cr, uid, transaction_id, context=context)
if not transaction.payment_order_id:
raise osv.except_osv(
_("Cannot unreconcile"),
_("Cannot unreconcile: no direct debit order"))
if transaction.payment_order_id.payment_order_type != 'debit':
raise osv.except_osv(
_("Cannot unreconcile"),
_("Unreconcile payment order not implemented"))
return payment_order_obj.debit_unreconcile_transfer(
cr, uid, transaction.payment_order_id.id,
transaction.statement_line_id.reconcile_id.id,
transaction.statement_line_id.amount,
transaction.statement_line_id.currency)
def _cancel_move(
self, cr, uid, transaction_id, context=None):
statement_line_obj = self.pool.get('account.bank.statement.line')
@@ -572,11 +616,11 @@ class banking_import_transaction(osv.osv):
transaction = self.browse(cr, uid, transaction_id, context=context)
if not transaction.payment_line_id:
osv.except_osv(
raise osv.except_osv(
_("Cannot cancel link with storno"),
_("No direct debit order item"))
if not transaction.payment_line_id.storno:
osv.except_osv(
raise osv.except_osv(
_("Cannot cancel link with storno"),
_("The direct debit order item is not marked for storno"))
@@ -591,7 +635,7 @@ class banking_import_transaction(osv.osv):
cancel_line = line
break
if not cancel_line: # debug
osv.except_osv(
raise osv.except_osv(
_("Cannot cancel link with storno"),
_("Line id not found"))
reconcile = cancel_line.reconcile_id or cancel_line.reconcile_partial_id
@@ -618,13 +662,14 @@ class banking_import_transaction(osv.osv):
'storno': _cancel_storno,
'invoice': _cancel_move,
'move': _cancel_move,
'payment_order': _cancel_payment_order,
}
def cancel(self, cr, uid, ids, context=None):
if ids and isinstance(ids, (int, float)):
ids = [ids]
for transaction in self.browse(cr, uid, ids, context):
if transaction.match_type not in self.cancel_map:
osv.except_osv(
raise osv.except_osv(
_("Cannot cancel type %s" % transaction.match_type),
_("No method found to cancel this type"))
self.cancel_map[transaction.match_type](self, cr, uid, transaction.id, context)
@@ -633,6 +678,7 @@ class banking_import_transaction(osv.osv):
reconcile_map = {
'storno': _reconcile_storno,
'invoice': _reconcile_move,
'payment_order': _reconcile_payment_order,
'move': _reconcile_move,
}
def reconcile(self, cr, uid, ids, context=None):
@@ -640,7 +686,7 @@ class banking_import_transaction(osv.osv):
ids = [ids]
for transaction in self.browse(cr, uid, ids, context):
if transaction.match_type not in self.reconcile_map:
osv.except_osv(
raise osv.except_osv(
_("Cannot reconcile type %s" % transaction.match_type),
_("No method found to reconcile this type"))
# run the method that is appropriate for this match type
@@ -731,7 +777,7 @@ class banking_import_transaction(osv.osv):
for key in self.signal_duplicate_keys]
ids = self.search(cr, uid, search_vals, context=context)
if len(ids) < 1:
osv.except_osv(_('Cannot check for duplicate'),
raise osv.except_osv(_('Cannot check for duplicate'),
_("I can't find myself..."))
if len(ids) > 1:
self.write(
@@ -1050,8 +1096,12 @@ class banking_import_transaction(osv.osv):
country_code = iban.countrycode
elif transaction.remote_owner_country_code:
country_code = transaction.remote_owner_country_code
elif hasattr(parser, 'country_code') and parser.country_code:
country_code = parser.country_code
# TODO: pass the parser's local country code to the transaction
# elif hasattr(parser, 'country_code') and parser.country_code:
# country_code = parser.country_code
# For now, substituted by the company's country
elif company.partner_id and company.partner_id.country:
country_code = company.partner_id.country.code
else:
country_code = None
partner_id = get_or_create_partner(
@@ -1104,7 +1154,8 @@ class banking_import_transaction(osv.osv):
if remainder:
injected.append(remainder)
if not move_info:
account_id = move_info and move_info.get('account_id', False)
if not account_id:
# Use the default settings, but allow individual partner
# settings to overrule this. Note that you need to change
# the internal type of these accounts to either 'payable'
@@ -1125,17 +1176,15 @@ class banking_import_transaction(osv.osv):
if len(partner_banks) != 1 or not account_id or account_id == def_rec_account_id:
account_id = (account_info.default_debit_account_id and
account_info.default_debit_account_id.id)
else:
account_id = move_info['account_id']
results['trans_matched_cnt'] += 1
values = {}
self_values = {}
if move_info:
results['trans_matched_cnt'] += 1
self_values['match_type'] = move_info['match_type']
self_values['payment_line_id'] = move_info.get('payment_line_id', False)
self_values['move_line_ids'] = [(6, 0, move_info.get('move_line_ids', []))]
self_values['invoice_ids'] = [(6, 0, move_info.get('invoice_ids', []))]
self_values['payment_order_ids'] = [(6, 0, move_info.get('payment_order_ids', []))]
self_values['move_line_ids'] = [(6, 0, move_info.get('move_line_ids') or [])]
self_values['invoice_ids'] = [(6, 0, move_info.get('invoice_ids') or [])]
self_values['payment_order_ids'] = [(6, 0, move_info.get('payment_order_ids') or [])]
self_values['payment_order_id'] = (move_info.get('payment_order_ids', False) and
len(move_info['payment_order_ids']) == 1 and
move_info['payment_order_ids'][0]
@@ -1254,7 +1303,7 @@ class banking_import_transaction(osv.osv):
'transferred_amount': fields.float('transferred_amount'),
'message': fields.char('message', size=1024),
'remote_owner': fields.char('remote_owner', size=24),
'remote_address': fields.char('remote_address', size=24),
'remote_owner_address': fields.char('remote_owner_address', size=24),
'remote_owner_city': fields.char('remote_owner_city', size=24),
'remote_owner_postalcode': fields.char('remote_owner_postalcode', size=24),
'remote_owner_country_code': fields.char('remote_owner_country_code', size=24),
@@ -1592,7 +1641,7 @@ class account_bank_statement_line(osv.osv):
if st_line.state != 'confirmed':
continue
if st_line.statement_id.state != 'draft':
osv.except_osv(
raise osv.except_osv(
_("Cannot cancel bank transaction"),
_("The bank statement that this transaction belongs to has " +
"already been confirmed"))

View File

@@ -519,7 +519,6 @@ class banking_import(osv.osv_memory):
# TODO: code _link_canceled_debit
return False
def import_statements_file(self, cursor, uid, ids, context):
'''
Import bank statements / bank transactions file.

View File

@@ -30,12 +30,12 @@
</tree>
</field>
</page>
<page attrs="{'invisible': [('state', '!=', 'ready')]}" string="Statements">
<field name="statement_ids" colspan="4" nolabel="1"/>
</page>
<page attrs="{'invisible': [('state', '=', 'init'')]}" string="Log">
<field name="log" colspan="4" nolabel="1" width="500"/>
</page>
<page attrs="{'invisible': [('state', '!=', 'ready')]}" string="Statements">
<field name="statement_ids" colspan="4" nolabel="1"/>
</page>
</notebook>
<group colspan="2" >
<button icon="gtk-cancel"

View File

@@ -136,11 +136,12 @@ def get_or_create_partner(pool, cursor, uid, name, address, postal_code, city,
)
country_id = country_ids and country_ids[0] or False
filter.append(('country_id', '=', country_id))
if address:
# disable for now. Apparently, address is an array of lines.
if address and False:
if len(address) >= 1:
filter.append(('street', 'ilike', addres[0]))
filter.append(('street', 'ilike', address[0]))
if len(address) > 1:
filter.append(('street2', 'ilike', addres[1]))
filter.append(('street2', 'ilike', address[1]))
if city:
filter.append(('city', 'ilike', city))
if postal_code:

View File

@@ -50,9 +50,6 @@ class transaction_message(object):
'date', 'remote_owner', 'local_account', 'remote_account',
'transfer_type', 'debcred', 'transferred_amount',
'transfer_type_verbose', 'message'
# 'date', 'local_account', 'transferred_amount', 'debcred',
# 'remote_owner', 'remote_account', 'transfer_type', 'reference',
]
def __init__(self, values, subno):
@@ -70,9 +67,9 @@ class transaction_message(object):
if self.debcred == 'Af':
self.transferred_amount = -self.transferred_amount
self.execution_date = self.effective_date = str2date(self.date, '%Y%m%d')
# Set statement_id based on week number
self.statement_id = self.effective_date.strftime('%Yw%W')
self.statement_id = '' #self.effective_date.strftime('%Yw%W')
self.id = str(subno).zfill(4)
self.reference = ''
# Normalize basic account numbers
self.remote_account = self.remote_account.replace('.', '').zfill(10)
self.local_account = self.local_account.replace('.', '').zfill(10)
@@ -84,7 +81,7 @@ class transaction(models.mem_bank_transaction):
attrnames = ['local_account', 'remote_account',
'remote_owner', 'transferred_amount',
'execution_date', 'effective_date', 'transfer_type',
'id', #'reference',
'id', 'reference', 'statement_id', 'message',
]
"""
@@ -121,9 +118,6 @@ class transaction(models.mem_bank_transaction):
super(transaction, self).__init__(*args, **kwargs)
# Copy attributes from auxiliary class to self.
for attr in self.attrnames:
#if attr == 'reference':
# setattr(self, 'reference', False)
#else:
setattr(self, attr, getattr(line, attr))
# self.message = ''
# Decompose structured messages

View File

@@ -36,6 +36,7 @@
'view/account_payment.xml',
'view/account_invoice.xml',
'workflow/account_invoice.xml',
'workflow/account_payment.xml',
'data/account_payment_term.xml',
],
'demo_xml': [],

View File

@@ -19,15 +19,6 @@ class payment_mode(osv.osv):
help=('Journal to write payment entries when confirming ' +
'a debit order of this mode'),
),
# 'reference_filter': fields.char(
# 'Reference filter', size=16,
# help=(
# 'Optional substring filter on move line references. ' +
# 'You can use this in combination with a specific journal ' +
# 'for items that you want to handle with this mode. Use ' +
# 'a separate sequence for the journal with a distinguished ' +
# 'prefix or suffix and enter that character string here.'),
# ),
'payment_term_ids': fields.many2many(
'account.payment.term', 'account_payment_order_terms_rel',
'mode_id', 'term_id', 'Payment terms',
@@ -67,17 +58,12 @@ class payment_order(osv.osv):
return res
def debit_reconcile_transfer(self, cr, uid, payment_order_id,
amount, log, context=None):
amount, currency, context=None):
"""
During import of bank statements, create the reconcile on the transfer
account containing all the move lines on the transfer account.
account containing all the open move lines on the transfer account.
"""
move_line_obj = self.pool.get('account.move.line')
def is_zero(move_line, total):
return self.pool.get('res.currency').is_zero(
cr, uid, move_line.company_id.currency_id, total)
order = self.browse(cr, uid, payment_order_id, context)
line_ids = []
reconcile_id = False
@@ -85,8 +71,9 @@ class payment_order(osv.osv):
for line in order_line.debit_move_line_id.move_id.line_id:
if line.account_id.type == 'other' and not line.reconcile_id:
line_ids.append(line.id)
if is_zero(order.line_ids[0].debit_move_line_id,
move_line_obj.get_balance(cr, uid, line_ids) - amount):
if self.pool.get('res.currency').is_zero(
cr, uid, currency,
move_line_obj.get_balance(cr, uid, line_ids) - amount):
reconcile_id = self.pool.get('account.move.reconcile').create(
cr, uid,
{'type': 'auto', 'line_id': [(6, 0, line_ids)]},
@@ -95,9 +82,44 @@ class payment_order(osv.osv):
wf_service = netsvc.LocalService('workflow')
wf_service.trg_validate(
uid, 'payment.order', payment_order_id, 'done', cr)
return reconcile_id
def debit_unreconcile_transfer(self, cr, uid, payment_order_id, reconcile_id,
amount, currency, context=None):
"""
Due to a cancelled bank statements import, unreconcile the move on
the transfer account. Delegate the conditions to the workflow, but
rip out the reconcile first or the workflow may not allow the undo.
Raise on failure for rollback.
"""
self.pool.get('account.move.reconcile').unlink(
cr, uid, reconcile_id, context=context)
wkf_ok = netsvc.LocalService('workflow').trg_validate(
uid, 'payment.order', payment_order_id, 'undo_done', cr)
if not wkf_ok:
raise osv.except_osv(
_("Cannot unreconcile"),
_("Cannot unreconcile debit order: "+
"Workflow will not allow it."))
return True
def test_undo_done(self, cr, uid, ids, context=None):
"""
Called from the workflow. Used to unset done state on
payment orders that were reconciled with bank transfers
which are being cancelled
"""
for order in self.browse(cr, uid, ids, context=context):
if order.payment_order_type == 'debit':
for line in order.line_ids:
if line.storno:
return False
else:
# TODO: define conditions for 'payment' orders
return False
return True
def action_sent(self, cr, uid, ids, context=None):
"""
Create the moves that pay off the move lines from
@@ -407,7 +429,8 @@ class payment_order_create(osv.osv_memory):
search_due_date = data['duedate']
### start account_direct_debit ###
payment = self.pool.get('payment.order').browse(cr, uid, context['active_id'], context=context)
payment = self.pool.get('payment.order').browse(
cr, uid, context['active_id'], context=context)
# Search for move line to pay:
if payment.payment_order_type == 'debit':
domain = [
@@ -415,22 +438,19 @@ class payment_order_create(osv.osv_memory):
('account_id.type', '=', 'receivable'),
('amount_to_receive', '>', 0),
]
# cannot filter on properties of (searchable)
# function fields. Needs work in expression.expression.parse()
# Currently gives an SQL error.
# apply payment term filter
if payment.mode.payment_term_ids:
term_ids = [term.id for term in payment.mode.payment_term_ids]
domain = domain + [
'|', ('invoice', '=', False),
('payment_term_id', 'in', term_ids),
]
else:
domain = [
('reconcile_id', '=', False),
('account_id.type', '=', 'payable'),
('amount_to_pay', '>', 0)
]
# apply payment term filter
if payment.mode.payment_term_ids:
domain = domain + [
('payment_term_id', 'in',
[term.id for term in payment.mode.payment_term_ids]
)
]
# domain = [('reconcile_id', '=', False), ('account_id.type', '=', 'payable'), ('amount_to_pay', '>', 0)]
### end account_direct_debit ###
@@ -439,7 +459,7 @@ class payment_order_create(osv.osv_memory):
context.update({'line_ids': line_ids})
model_data_ids = mod_obj.search(cr, uid,[('model', '=', 'ir.ui.view'), ('name', '=', 'view_create_payment_order_lines')], context=context)
resource_id = mod_obj.read(cr, uid, model_data_ids, fields=['res_id'], context=context)[0]['res_id']
return {'name': ('Entrie Lines'),
return {'name': ('Entry Lines'),
'context': context,
'view_type': 'form',
'view_mode': 'form',

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!--
Transition to undo the payment order and reset to
sent, triggered by
cancelling a bank transaction with which the order
was reconciled.
For this, we need to cancel the flow stop on the done state,
unfortunately.
TODO: what is below is not enough. We need to inject
another state, 'sent_wait' between sent and done.
-->
<record id="account_payment.act_done" model="workflow.activity">
<field name="flow_stop">False</field>
</record>
<record id="trans_done_sent" model="workflow.transition">
<field name="act_from" ref="account_payment.act_done"/>
<field name="act_to" ref="account_banking.act_sent"/>
<field name="condition">test_undo_done()</field>
<field name="signal">undo_done</field>
</record>
</data>
</openerp>