mirror of
https://github.com/OCA/bank-payment.git
synced 2025-02-02 10:37:31 +02:00
[FIX] account_banking_nl_clieop: description records are not being used
[FIX] account_banking_nl_clieop: wrong position of beneficiary in payments
[FIX] account_banking_nl_clieop: wrong alignment of truncates messages
[IMP] account_banking_nl_clieop: Allow long messages to wrap over multiple
records
[IMP] account_banking: Improved matching of partial payments
[IMP] account_banking: Dutch ING accounts no longer require online IBAN
conversion
This commit is contained in:
@@ -51,7 +51,7 @@ class Field(object):
|
||||
def format(self, value):
|
||||
value = str(value)
|
||||
if len(value) > self.length:
|
||||
return value[len(value) - self.length:]
|
||||
return value[:self.length]
|
||||
return value.ljust(self.length, self.fillchar)
|
||||
|
||||
def take(self, buffer):
|
||||
|
||||
@@ -49,11 +49,12 @@ def get_iban_bic_NL(bank_acc):
|
||||
# calculates accounts, so no need to consult it - calculate our own
|
||||
number = bank_acc.lstrip('0')
|
||||
if len(number) <= 7:
|
||||
iban = IBAN.create(BBAN='INGB' + number.rjust(10, '0'),
|
||||
countrycode='NL'
|
||||
)
|
||||
return struct(
|
||||
iban = IBAN(BBAN='INGB' + number.rjust(10, '0'),
|
||||
countrycode='NL'
|
||||
).replace(' ',''),
|
||||
account = iban,
|
||||
iban = iban.replace(' ',''),
|
||||
account = iban.BBAN[4:],
|
||||
bic = 'INGBNL2A',
|
||||
code = 'INGBNL',
|
||||
bank = 'ING Bank N.V.',
|
||||
|
||||
@@ -33,6 +33,7 @@ import time
|
||||
import wizard
|
||||
import netsvc
|
||||
import base64
|
||||
import datetime
|
||||
from tools import config
|
||||
from tools.translate import _
|
||||
from account_banking.parsers import models
|
||||
@@ -43,6 +44,10 @@ from banktools import *
|
||||
|
||||
bt = models.mem_bank_transaction
|
||||
|
||||
# This variable is used to match supplier invoices with an invoice date after
|
||||
# the real payment date. This can occur with online transactions (web shops).
|
||||
payment_window = datetime.timedelta(days=10)
|
||||
|
||||
def parser_types(*args, **kwargs):
|
||||
'''Delay evaluation of parser types until start of wizard, to allow
|
||||
depending modules to initialize and add their parsers to the list
|
||||
@@ -106,6 +111,7 @@ class banking_import(wizard.interface):
|
||||
self.__state = ''
|
||||
self.__linked_invoices = {}
|
||||
self.__linked_payments = {}
|
||||
self.__multiple_matches = []
|
||||
|
||||
def _fill_results(self, *args, **kwargs):
|
||||
return {'log': self._log}
|
||||
@@ -135,7 +141,9 @@ class banking_import(wizard.interface):
|
||||
)
|
||||
else:
|
||||
if move_line.reconcile_partial_id:
|
||||
partial_ids = [x.id for x in move_line.reconcile_partial_id]
|
||||
partial_ids = [x.id for x in
|
||||
move_line.reconcile_partial_id.line_partial_ids
|
||||
]
|
||||
else:
|
||||
partial_ids = []
|
||||
move_line.reconcile_id = reconcile_obj.create(
|
||||
@@ -284,7 +292,7 @@ class banking_import(wizard.interface):
|
||||
if partner_ids:
|
||||
candidates = [x for x in move_lines
|
||||
if x.partner_id.id in partner_ids and
|
||||
str2date(x.date, '%Y-%m-%d') <= trans.execution_date
|
||||
str2date(x.date, '%Y-%m-%d') <= (trans.execution_date + payment_window)
|
||||
and (not _cached(x) or _remaining(x))
|
||||
]
|
||||
else:
|
||||
@@ -303,8 +311,9 @@ class banking_import(wizard.interface):
|
||||
# reporting this.
|
||||
candidates = [x for x in candidates or move_lines
|
||||
if x.invoice and has_id_match(x.invoice, ref, msg)
|
||||
and str2date(x.invoice.date_invoice, '%Y-%m-%d') <= trans.execution_date
|
||||
and (not _cached(x) or _remaining(x))
|
||||
and str2date(x.invoice.date_invoice, '%Y-%m-%d')
|
||||
<= (trans.execution_date + payment_window)
|
||||
and (not _cached(x) or _remaining(x))
|
||||
]
|
||||
|
||||
# Match on amount expected. Limit this kind of search to known
|
||||
@@ -312,9 +321,10 @@ class banking_import(wizard.interface):
|
||||
if not candidates and partner_ids:
|
||||
candidates = [x for x in move_lines
|
||||
if round(abs(x.credit or x.debit), digits) ==
|
||||
round(abs(trans.transferred_amount), digits) and
|
||||
str2date(x.date, '%Y-%m-%d') <= trans.execution_date and
|
||||
(not _cached(x) or _remaining(x))
|
||||
round(abs(trans.transferred_amount), digits)
|
||||
and str2date(x.date, '%Y-%m-%d') <=
|
||||
(trans.execution_date + payment_window)
|
||||
and (not _cached(x) or _remaining(x))
|
||||
]
|
||||
|
||||
move_line = False
|
||||
@@ -325,8 +335,9 @@ class banking_import(wizard.interface):
|
||||
# TODO: currency coercing
|
||||
best = [x for x in candidates
|
||||
if round(abs(x.credit or x.debit), digits) ==
|
||||
round(abs(trans.transferred_amount), digits) and
|
||||
str2date(x.date, '%Y-%m-%d') <= trans.execution_date
|
||||
round(abs(trans.transferred_amount), digits)
|
||||
and str2date(x.date, '%Y-%m-%d') <=
|
||||
(trans.execution_date + payment_window)
|
||||
]
|
||||
if len(best) == 1:
|
||||
# Exact match
|
||||
@@ -343,8 +354,9 @@ class banking_import(wizard.interface):
|
||||
# transfers first
|
||||
paid = [x for x in move_lines
|
||||
if x.invoice and has_id_match(x.invoice, ref, msg)
|
||||
and str2date(x.invoice.date_invoice, '%Y-%m-%d') <= trans.execution_date
|
||||
and (_cached(x) and not _remaining(x))
|
||||
and str2date(x.invoice.date_invoice, '%Y-%m-%d')
|
||||
<= trans.execution_date
|
||||
and (_cached(x) and not _remaining(x))
|
||||
]
|
||||
if paid:
|
||||
log.append(
|
||||
@@ -364,6 +376,14 @@ class banking_import(wizard.interface):
|
||||
'ref': trans.reference,
|
||||
'no_candidates': len(best) or len(candidates)
|
||||
})
|
||||
log.append(' ' +
|
||||
_('Candidates: %(candidates)s') % {
|
||||
'candidates': ', '.join([x.invoice.number
|
||||
for x in best or candidates
|
||||
])
|
||||
})
|
||||
self.__multiple_matches.append((trans, best or
|
||||
candidates))
|
||||
move_line = False
|
||||
partial = False
|
||||
|
||||
@@ -403,6 +423,15 @@ class banking_import(wizard.interface):
|
||||
x.id for x in bank_account_ids
|
||||
if x.partner_id.id == move_line.partner_id.id
|
||||
]
|
||||
|
||||
# Re-check the cases with multiple candidates again:
|
||||
# later matches may have removed possible candidates.
|
||||
for trans, candidates in self.__multiple_matches:
|
||||
best = [x for x in candidates if not self._cached(x)]
|
||||
if len(best) == 1:
|
||||
# Now an exact match can be made
|
||||
pass
|
||||
|
||||
return (
|
||||
self._get_move_info(cursor, uid, move_line,
|
||||
account_ids and account_ids[0] or False,
|
||||
@@ -411,6 +440,7 @@ class banking_import(wizard.interface):
|
||||
trans2
|
||||
)
|
||||
|
||||
|
||||
return (False, False)
|
||||
|
||||
def _link_canceled_debit(self, cursor, uid, trans, payment_lines,
|
||||
@@ -877,7 +907,7 @@ class banking_import(wizard.interface):
|
||||
i += 1
|
||||
|
||||
results.stat_loaded_cnt += 1
|
||||
|
||||
|
||||
if payment_lines:
|
||||
# As payments lines are treated as individual transactions, the
|
||||
# batch as a whole is only marked as 'done' when all payment lines
|
||||
|
||||
@@ -37,6 +37,14 @@ def eleven_test(s):
|
||||
r += (l-i) * int(c)
|
||||
return (r % 11) == 0
|
||||
|
||||
def chunk(str, length):
|
||||
'''
|
||||
Split a string in equal sized substrings of length <length>
|
||||
'''
|
||||
while str:
|
||||
yield str[:length]
|
||||
str = str[length:]
|
||||
|
||||
class HeaderRecord(record.Record): #{{{
|
||||
'''ClieOp3 header record'''
|
||||
_fields = [
|
||||
@@ -145,7 +153,7 @@ class DescriptionRecord(record.Record):
|
||||
'''Description'''
|
||||
_fields = [
|
||||
record.Filler('recordcode', 4, '0160'),
|
||||
record.Filler('variantcode', 1, 'B'),
|
||||
record.Filler('variantcode', 1, 'A'),
|
||||
record.Field('description', 32),
|
||||
record.Filler('filler', 13),
|
||||
]
|
||||
@@ -209,6 +217,17 @@ class Optional(object):
|
||||
setattr(newitem, attr, value)
|
||||
self._guts.append(newitem)
|
||||
|
||||
def __len__(self):
|
||||
'''Return actual contents'''
|
||||
return len(self._guts)
|
||||
|
||||
def length(self, attr):
|
||||
'''Return length of optional record'''
|
||||
res = [x for x in self._klass._fields if x.name == attr]
|
||||
if res:
|
||||
return res[0].length
|
||||
raise AttributeError(attr)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
'''Only return if used'''
|
||||
if attr[0] == '_':
|
||||
@@ -246,10 +265,15 @@ class Transaction(object):
|
||||
self.transaction.amount = int(amount * 100)
|
||||
if reference:
|
||||
self.paymentreference.paymentreference = reference
|
||||
for msg in messages:
|
||||
# Allow long message lines to redistribute over multiple message
|
||||
# records
|
||||
for msg in chunk(''.join(messages),
|
||||
self.description.length('description')
|
||||
)[:4]:
|
||||
self.description.description = msg
|
||||
self.name.name = name
|
||||
|
||||
|
||||
class DirectDebit(Transaction):
|
||||
'''Direct Debit Payment transaction'''
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -266,8 +290,8 @@ class DirectDebit(Transaction):
|
||||
items = [str(self.transaction)]
|
||||
if self.name:
|
||||
items.append(str(self.name))
|
||||
for kenmerk in self.paymentreference:
|
||||
items.append(str(kenmerk))
|
||||
for reference in self.paymentreference:
|
||||
items.append(str(reference))
|
||||
for description in self.description:
|
||||
items.append(str(description))
|
||||
return '\r\n'.join(items)
|
||||
@@ -289,12 +313,12 @@ class Payment(Transaction):
|
||||
Return self as writeable file content object
|
||||
'''
|
||||
items = [str(self.transaction)]
|
||||
for kenmerk in self.paymentreference:
|
||||
items.append(str(kenmerk))
|
||||
if self.name:
|
||||
items.append(str(self.name))
|
||||
for reference in self.paymentreference:
|
||||
items.append(str(reference))
|
||||
for description in self.description:
|
||||
items.append(str(description))
|
||||
if self.name:
|
||||
items.append(str(self.name))
|
||||
return '\r\n'.join(items)
|
||||
|
||||
class SalaryPayment(Payment):
|
||||
|
||||
Reference in New Issue
Block a user