forked from Mapan/odoo17e
804 lines
54 KiB
Python
804 lines
54 KiB
Python
# -*- encoding: utf-8 -*-
|
||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||
|
||
# Copyright (c) 2012 Noviat nv/sa (www.noviat.be). All rights reserved.
|
||
import time
|
||
import re
|
||
|
||
from odoo import models, fields, tools, _, _lt
|
||
from odoo.exceptions import UserError
|
||
|
||
|
||
class safedict(dict):
|
||
def __init__(self, *args, return_val=None, **kwargs):
|
||
self.__return_val = return_val if return_val is not None else _('Wrong CODA code')
|
||
super().__init__(*args, **kwargs)
|
||
|
||
def __getitem__(self, k):
|
||
return super().__getitem__(k) if k in self else self.__return_val
|
||
|
||
|
||
# Mappings for the structured communication formats
|
||
minimum = safedict({'1': _lt('minimum applicable'), '2': _lt('minimum not applicable')})
|
||
card_scheme = safedict({'1': _lt('Bancontact/Mister Cash'), '2': _lt('Maestro'), '3': _lt('Private'), '5': _lt('TINA'), '9': _lt('Other')})
|
||
transaction_type = safedict({'0': _lt('cumulative'), '1': _lt('withdrawal'), '2': _lt('cumulative on network'), '4': _lt('reversal of purchases'), '5': _lt('POS others'), '7': _lt('distribution sector'), '8': _lt('teledata'), '9': _lt('fuel')})
|
||
product_code = safedict({'00': _lt('unset'), '01': _lt('premium with lead substitute'), '02': _lt('europremium'), '03': _lt('diesel'), '04': _lt('LPG'), '06': _lt('premium plus 98 oct'), '07': _lt('regular unleaded'), '08': _lt('domestic fuel oil'), '09': _lt('lubricants'), '10': _lt('petrol'), '11': _lt('premium 99+'), '12': _lt('Avgas'), '16': _lt('other types')})
|
||
issuing_institution = safedict({'1': 'Mastercard', '2': 'Visa', '3': 'American Express', '4': 'Diners Club', '9': 'Other'})
|
||
type_direct_debit = safedict({'0': _lt('unspecified'), '1': _lt('recurrent'), '2': _lt('one-off'), '3': _lt('1-st (recurrent)'), '4': _lt('last (recurrent)')})
|
||
direct_debit_scheme = safedict({'0': _lt('unspecified'), '1': _lt('SEPA core'), '2': _lt('SEPA B2B')})
|
||
payment_reason = safedict({'0': _lt('paid'), '1': _lt('technical problem'), '2': _lt('reason not specified'), '3': _lt('debtor disagrees'), '4': _lt('debtor’s account problem')})
|
||
sepa_type = safedict({'0': _lt('paid'), '1': _lt('reject'), '2': _lt('return'), '3': _lt('refund'), '4': _lt('reversal'), '5': _lt('cancellation')})
|
||
|
||
|
||
sepa_transaction_type = safedict({
|
||
0: _lt('Simple amount without detailed data'),
|
||
1: _lt('Amount as totalised by the customer'),
|
||
2: _lt('Amount as totalised by the bank'),
|
||
3: _lt('Simple amount with detailed data'),
|
||
5: _lt('Detail of Amount as totalised by the customer'),
|
||
6: _lt('Detail of Amount as totalised by the bank'),
|
||
7: _lt('Detail of Amount as totalised by the bank'),
|
||
8: _lt('Detail of Simple amount with detailed data'),
|
||
9: _lt('Detail of Amount as totalised by the bank'),
|
||
})
|
||
|
||
default_transaction_code = safedict({
|
||
'40': _lt('Codes proper to each bank'), '41': _lt('Codes proper to each bank'), '42': _lt('Codes proper to each bank'), '43': _lt('Codes proper to each bank'), '44': _lt('Codes proper to each bank'), '45': _lt('Codes proper to each bank'), '46': _lt('Codes proper to each bank'), '47': _lt('Codes proper to each bank'), '48': _lt('Codes proper to each bank'),
|
||
'49': _lt('Cancellation or correction'),
|
||
'87': _lt('Reimbursement of costs'),
|
||
'90': _lt('Codes proper to each bank'), '91': _lt('Codes proper to each bank'), '92': _lt('Codes proper to each bank'), '93': _lt('Codes proper to each bank'), '94': _lt('Codes proper to each bank'), '95': _lt('Codes proper to each bank'), '96': _lt('Codes proper to each bank'), '97': _lt('Codes proper to each bank'), '98': _lt('Codes proper to each bank'),
|
||
'99': _lt('Cancellation or correction'),
|
||
})
|
||
transaction_code = safedict(**{
|
||
'return_val': ('', {}),
|
||
'01': (_lt('Domestic or local SEPA credit transfers'), {
|
||
'01': _lt('Individual transfer order'),
|
||
'02': _lt('Individual transfer order initiated by the bank'),
|
||
'03': _lt('Standing order'),
|
||
'05': _lt('Payment of wages, etc.'),
|
||
'07': _lt('Collective transfer'),
|
||
'13': _lt('Transfer from your account'),
|
||
'17': _lt('Financial centralisation'),
|
||
'37': _lt('Costs'),
|
||
'39': _lt('Your issue circular cheque'),
|
||
'50': _lt('Transfer in your favour'),
|
||
'51': _lt('Transfer in your favour – initiated by the bank'),
|
||
'52': _lt('Payment in your favour'),
|
||
'54': _lt('Unexecutable transfer order'),
|
||
'60': _lt('Non-presented circular cheque'),
|
||
'62': _lt('Unpaid postal order'),
|
||
'64': _lt('Transfer to your account'),
|
||
'66': _lt('Financial centralization'),
|
||
}),
|
||
'02': (_lt('Instant SEPA credit transfer'), {
|
||
'01': _lt('Individual transfer order'),
|
||
'02': _lt('Individual transfer order initiated by the bank'),
|
||
'03': _lt('Standing order'),
|
||
'05': _lt('Payment of wages, etc.'),
|
||
'07': _lt('Collective transfer'),
|
||
'13': _lt('Transfer from your account'),
|
||
'17': _lt('Financial centralisation'),
|
||
'37': _lt('Costs'),
|
||
'50': _lt('Transfer in your favour'),
|
||
'51': _lt('Transfer in your favour – initiated by the bank'),
|
||
'52': _lt('Payment in your favour'),
|
||
'54': _lt('Unexecutable transfer order'),
|
||
'64': _lt('Transfer to your account'),
|
||
'66': _lt('Financial centralization'),
|
||
}),
|
||
'03': (_lt('Cheques'), {
|
||
'01': _lt('Payment of your cheque'),
|
||
'05': _lt('Payment of voucher'),
|
||
'09': _lt('Unpaid voucher'),
|
||
'11': _lt('Department store cheque'),
|
||
'15': _lt('Your purchase bank cheque'),
|
||
'17': _lt('Your certified cheque'),
|
||
'37': _lt('Cheque-related costs'),
|
||
'38': _lt('Provisionally unpaid'),
|
||
'40': _lt('Codes proper to each bank'),
|
||
'52': _lt('First credit of cheques, vouchers, luncheon vouchers, postal orders, credit under usual reserve'),
|
||
'58': _lt('Remittance of cheques, vouchers, etc. credit after collection'),
|
||
'60': _lt('Reversal of voucher'),
|
||
'62': _lt('Reversal of cheque'),
|
||
'63': _lt('Second credit of unpaid cheque'),
|
||
'66': _lt('Remittance of cheque by your branch - credit under usual reserve'),
|
||
'87': _lt('Reimbursement of cheque-related costs'),
|
||
}),
|
||
'04': (_lt('Cards'), {
|
||
'01': _lt('Loading a GSM card'),
|
||
'02': _lt('Payment by means of a payment card within the Eurozone'),
|
||
'03': _lt('Settlement credit cards'),
|
||
'04': _lt('Cash withdrawal from an ATM'),
|
||
'05': _lt('Loading Proton'),
|
||
'06': _lt('Payment with tank card'),
|
||
'07': _lt('Payment by GSM'),
|
||
'08': _lt('Payment by means of a payment card outside the Eurozone'),
|
||
'09': _lt('Upload of prepaid card'),
|
||
'10': _lt('Correction for prepaid card'),
|
||
'37': _lt('Costs'),
|
||
'50': _lt('Credit after a payment at a terminal'),
|
||
'51': _lt('Unloading Proton'),
|
||
'52': _lt('Loading GSM cards'),
|
||
'53': _lt('Cash deposit at an ATM'),
|
||
'54': _lt('Download of prepaid card'),
|
||
'55': _lt('Income from payments by GSM'),
|
||
'56': _lt('Correction for prepaid card'),
|
||
'68': _lt('Credit after Proton payments'),
|
||
}),
|
||
'05': (_lt('Direct debit'), {
|
||
'01': _lt('Payment'),
|
||
'03': _lt('Unpaid debt'),
|
||
'05': _lt('Reimbursement'),
|
||
'37': _lt('Costs'),
|
||
'50': _lt('Credit after collection'),
|
||
'52': _lt('Credit under usual reserve'),
|
||
'54': _lt('Reimbursement'),
|
||
'56': _lt('Unexecutable reimbursement'),
|
||
'58': _lt('Reversal'),
|
||
}),
|
||
'07': (_lt('Domestic commercial paper'), {
|
||
'01': _lt('Payment commercial paper'),
|
||
'05': _lt('Commercial paper claimed back'),
|
||
'06': _lt('Extension of maturity date'),
|
||
'07': _lt('Unpaid commercial paper'),
|
||
'08': _lt('Payment in advance'),
|
||
'09': _lt('Agio on supplier\'s bill'),
|
||
'37': _lt('Costs related to commercial paper'),
|
||
'39': _lt('Return of an irregular bill of exchange'),
|
||
'50': _lt('Remittance of commercial paper - credit after collection'),
|
||
'52': _lt('Remittance of commercial paper - credit under usual reserve'),
|
||
'54': _lt('Remittance of commercial paper for discount'),
|
||
'56': _lt('Remittance of supplier\'s bill with guarantee'),
|
||
'58': _lt('Remittance of supplier\'s bill without guarantee'),
|
||
}),
|
||
'09': (_lt('Counter transactions'), {
|
||
'01': _lt('Cash withdrawal'),
|
||
'05': _lt('Purchase of foreign bank notes'),
|
||
'07': _lt('Purchase of gold/pieces'),
|
||
'09': _lt('Purchase of petrol coupons'),
|
||
'13': _lt('Cash withdrawal by your branch or agents'),
|
||
'17': _lt('Purchase of fiscal stamps'),
|
||
'19': _lt('Difference in payment'),
|
||
'25': _lt('Purchase of traveller’s cheque'),
|
||
'37': _lt('Costs'),
|
||
'50': _lt('Cash payment'),
|
||
'52': _lt('Payment night safe'),
|
||
'58': _lt('Payment by your branch/agents'),
|
||
'60': _lt('Sale of foreign bank notes'),
|
||
'62': _lt('Sale of gold/pieces under usual reserve'),
|
||
'68': _lt('Difference in payment'),
|
||
'70': _lt('Sale of traveller’s cheque'),
|
||
}),
|
||
'11': (_lt('Securities'), {
|
||
'01': _lt('Purchase of securities'),
|
||
'02': _lt('Tenders'),
|
||
'03': _lt('Subscription to securities'),
|
||
'04': _lt('Issues'),
|
||
'05': _lt('Partial payment subscription'),
|
||
'06': _lt('Share option plan – exercising an option'),
|
||
'09': _lt('Settlement of securities'),
|
||
'11': _lt('Payable coupons/repayable securities'),
|
||
'13': _lt('Your repurchase of issue'),
|
||
'15': _lt('Interim interest on subscription'),
|
||
'17': _lt('Management fee'),
|
||
'19': _lt('Regularisation costs'),
|
||
'37': _lt('Costs'),
|
||
'50': _lt('Sale of securities'),
|
||
'51': _lt('Tender'),
|
||
'52': _lt('Payment of coupons from a deposit or settlement of coupons delivered over the counter - credit under usual reserve'),
|
||
'58': _lt('Repayable securities from a deposit or delivered at the counter - credit under usual reserve'),
|
||
'62': _lt('Interim interest on subscription'),
|
||
'64': _lt('Your issue'),
|
||
'66': _lt('Retrocession of issue commission'),
|
||
'68': _lt('Compensation for missing coupon'),
|
||
'70': _lt('Settlement of securities'),
|
||
'99': _lt('Cancellation or correction'),
|
||
}),
|
||
'13': (_lt('Credit'), {
|
||
'01': _lt('Short-term loan'),
|
||
'02': _lt('Long-term loan'),
|
||
'05': _lt('Settlement of fixed advance'),
|
||
'07': _lt('Your repayment instalment credits'),
|
||
'11': _lt('Your repayment mortgage loan'),
|
||
'13': _lt('Settlement of bank acceptances'),
|
||
'15': _lt('Your repayment hire-purchase and similar claims'),
|
||
'19': _lt('Documentary import credits'),
|
||
'21': _lt('Other credit applications'),
|
||
'37': _lt('Credit-related costs'),
|
||
'50': _lt('Settlement of instalment credit'),
|
||
'54': _lt('Fixed advance – capital and interest'),
|
||
'55': _lt('Fixed advance – interest only'),
|
||
'56': _lt('Subsidy'),
|
||
'60': _lt('Settlement of mortgage loan'),
|
||
'62': _lt('Term loan'),
|
||
'68': _lt('Documentary export credits'),
|
||
'70': _lt('Settlement of discount bank acceptance'),
|
||
}),
|
||
'30': (_lt('Various transactions'), {
|
||
'01': _lt('Spot purchase of foreign exchange'),
|
||
'03': _lt('Forward purchase of foreign exchange'),
|
||
'05': _lt('Capital and/or interest term investment'),
|
||
'33': _lt('Value (date) correction'),
|
||
'37': _lt('Costs'),
|
||
'39': _lt('Undefined transaction'),
|
||
'50': _lt('Spot sale of foreign exchange'),
|
||
'52': _lt('Forward sale of foreign exchange'),
|
||
'54': _lt('Capital and/or interest term investment'),
|
||
'55': _lt('Interest term investment'),
|
||
'83': _lt('Value (date) correction'),
|
||
'89': _lt('Undefined transaction'),
|
||
}),
|
||
'35': (_lt('Closing (periodical settlements for interest, costs,...)'), {
|
||
'01': _lt('Closing'),
|
||
'37': _lt('Costs'),
|
||
'50': _lt('Closing'),
|
||
}),
|
||
'41': (_lt('International credit transfers - non-SEPA credit transfers'), {
|
||
'01': _lt('Transfer'),
|
||
'03': _lt('Standing order'),
|
||
'05': _lt('Collective payments of wages'),
|
||
'07': _lt('Collective transfers'),
|
||
'13': _lt('Transfer from your account'),
|
||
'17': _lt('Financial centralisation (debit)'),
|
||
'37': _lt('Costs relating to outgoing foreign transfers and non-SEPA transfers'),
|
||
'38': _lt('Costs relating to incoming foreign and non-SEPA transfers'),
|
||
'50': _lt('Transfer'),
|
||
'64': _lt('Transfer to your account'),
|
||
'66': _lt('Financial centralisation (credit)'),
|
||
}),
|
||
'43': (_lt('Foreign cheques'), {
|
||
'01': _lt('Payment of a foreign cheque'),
|
||
'07': _lt('Unpaid foreign cheque'),
|
||
'15': _lt('Purchase of an international bank cheque'),
|
||
'37': _lt('Costs relating to payment of foreign cheques'),
|
||
'52': _lt('Remittance of foreign cheque credit under usual reserve'),
|
||
'58': _lt('Remittance of foreign cheque credit after collection'),
|
||
'62': _lt('Reversal of cheques'),
|
||
}),
|
||
'47': (_lt('Foreign commercial paper'), {
|
||
'01': _lt('Payment of foreign bill'),
|
||
'05': _lt('Bill claimed back'),
|
||
'06': _lt('Extension'),
|
||
'07': _lt('Unpaid foreign bill'),
|
||
'11': _lt('Payment documents abroad'),
|
||
'13': _lt('Discount foreign supplier\'s bills'),
|
||
'14': _lt('Warrant fallen due'),
|
||
'37': _lt('Costs relating to the payment of a foreign bill'),
|
||
'50': _lt('Remittance of foreign bill credit after collection'),
|
||
'52': _lt('Remittance of foreign bill credit under usual reserve'),
|
||
'54': _lt('Discount abroad'),
|
||
'56': _lt('Remittance of guaranteed foreign supplier\'s bill'),
|
||
'58': _lt('Idem without guarantee'),
|
||
'60': _lt('Remittance of documents abroad - credit under usual reserve'),
|
||
'62': _lt('Remittance of documents abroad - credit after collection'),
|
||
'64': _lt('Warrant'),
|
||
}),
|
||
'80': (_lt('Separately charged costs and provisions'), {
|
||
'02': _lt('Costs relating to electronic output'),
|
||
'04': _lt('Costs for holding a documentary cash credit'),
|
||
'06': _lt('Damage relating to bills and cheques'),
|
||
'07': _lt('Insurance costs'),
|
||
'08': _lt('Registering compensation for savings accounts'),
|
||
'09': _lt('Postage'),
|
||
'10': _lt('Purchase of Smartcard'),
|
||
'11': _lt('Costs for the safe custody of correspondence'),
|
||
'12': _lt('Costs for opening a bank guarantee'),
|
||
'13': _lt('Renting of safes'),
|
||
'14': _lt('Handling costs instalment credit'),
|
||
'15': _lt('Night safe'),
|
||
'16': _lt('Bank confirmation to revisor or accountant'),
|
||
'17': _lt('Charge for safe custody'),
|
||
'18': _lt('Trade information'),
|
||
'19': _lt('Special charge for safe custody'),
|
||
'20': _lt('Drawing up a certificate'),
|
||
'21': _lt('Pay-packet charges'),
|
||
'22': _lt('Management/custody'),
|
||
'23': _lt('Research costs'),
|
||
'24': _lt('Participation in and management of interest refund system'),
|
||
'25': _lt('Renting of direct debit box'),
|
||
'26': _lt('Travel insurance premium'),
|
||
'27': _lt('Subscription fee'),
|
||
'29': _lt('Information charges'),
|
||
'31': _lt('Writ service fee'),
|
||
'33': _lt('Miscellaneous fees and commissions'),
|
||
'35': _lt('Costs'),
|
||
'37': _lt('Access right to database'),
|
||
'39': _lt('Surety fee'),
|
||
'41': _lt('Research costs'),
|
||
'43': _lt('Printing of forms'),
|
||
'45': _lt('Documentary credit charges'),
|
||
'47': _lt('Charging fees for transactions'),
|
||
}),
|
||
})
|
||
|
||
|
||
def rmspaces(s):
|
||
return " ".join(s.split())
|
||
|
||
|
||
def parsedate(s):
|
||
if s == '999999':
|
||
return _('No date')
|
||
return f"{s[:2]}/{s[2:4]}/{s[4:]}"
|
||
|
||
|
||
def parsehour(s):
|
||
return f"{s[:2]}:{s[2:]}"
|
||
|
||
|
||
def parsefloat(s, precision):
|
||
return str(float(rmspaces(s) or 0) / (10 ** precision))
|
||
|
||
|
||
def parse_terminal(s):
|
||
return _('Name: %(name)s, Town: %(city)s', name=rmspaces(s[:16]), city=rmspaces(s[16:]))
|
||
|
||
|
||
def parse_operation(tr_type, family, operation, category):
|
||
return "{tr_type}: {family} ({operation})".format(
|
||
tr_type=sepa_transaction_type[tr_type],
|
||
family=transaction_code[family][0],
|
||
operation=transaction_code[family][1].get(operation, default_transaction_code.get(operation, _('undefined')))
|
||
)
|
||
|
||
|
||
def parse_structured_communication(co_type, communication):
|
||
# pylint: disable=C0321,C0326
|
||
# ruff: noqa: E702, E222
|
||
note = []
|
||
p_idx = 0; o_idx = 0
|
||
if co_type == '100': # RF Creditor Reference
|
||
structured_com = rmspaces(communication[:25])
|
||
elif co_type in ('101', '102'): # Credit transfer or cash payment with structured format communication or with reconstituted structured format communication
|
||
structured_com = '+++' + communication[:3] + '/' + communication[3:7] + '/' + communication[7:12] + '+++'
|
||
elif co_type == '103': # number (e.g. of the cheque, of the card, etc.)
|
||
structured_com = rmspaces(communication[:12])
|
||
elif co_type == '105': # Original amount of the transaction
|
||
structured_com = _('Original amount of the transaction')
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('Gross amount in the currency of the account') + ': ' + parsefloat(communication[o_idx:p_idx], 3))
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('Gross amount in the original currency') + ': ' + parsefloat(communication[o_idx:p_idx], 3))
|
||
o_idx = p_idx; p_idx += 12; note.append(_('Detail') + ': ' + _('Rate') + ': ' + parsefloat(communication[o_idx:p_idx], 8))
|
||
o_idx = p_idx; p_idx += 3; note.append(_('Detail') + ': ' + _('Currency') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 12; note.append(_('Detail') + ': ' + _('Structured format communication') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 2; note.append(_('Detail') + ': ' + _('Country code of the principal') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('Equivalent in EUR') + ': ' + parsefloat(communication[o_idx:p_idx], 3))
|
||
elif co_type == '106': # Method of calculation (VAT, withholding tax on income, commission, etc.)
|
||
structured_com = _('Method of calculation (VAT, withholding tax on income, commission, etc.)')
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('equivalent in the currency of the account') + ': ' + parsefloat(communication[o_idx:p_idx], 3))
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('amount on which % is calculated') + ': ' + parsefloat(communication[o_idx:p_idx], 3))
|
||
o_idx = p_idx; p_idx += 12; note.append(_('Detail') + ': ' + _('percent') + ': ' + parsefloat(communication[o_idx:p_idx], 8))
|
||
o_idx = p_idx; p_idx += 1; note.append(_('Detail') + ': ' + _('minimum') + ': ' + minimum[communication[o_idx:p_idx]])
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('equivalent in EUR') + ': ' + parsefloat(communication[o_idx:p_idx], 3))
|
||
elif co_type == '108': # Closing
|
||
structured_com = _('Closing')
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('equivalent in the currency of the account') + ': ' + parsefloat(communication[o_idx:p_idx], 3))
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('interest rates, calculation basis') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 12; note.append(_('Detail') + ': ' + _('interest') + ': ' + parsefloat(communication[o_idx:p_idx], 8))
|
||
o_idx = p_idx; p_idx += 12; note.append(_('Detail') + ': ' + _('period from %s to %s', parsedate(communication[o_idx:o_idx + 6]), parsedate(communication[o_idx + 6:o_idx + 12])))
|
||
elif co_type == '111': # POS credit - Globalisation
|
||
structured_com = _('POS credit – Globalisation')
|
||
o_idx = p_idx; p_idx += 1; note.append(_('Detail') + ': ' + _('card scheme') + ': ' + card_scheme[communication[o_idx:p_idx]])
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('POS number') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 3; note.append(_('Detail') + ': ' + _('period number') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('sequence number of first transaction') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('date of first transaction') + ': ' + parsedate(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('sequence number of last transaction') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('date of last transaction') + ': ' + parsedate(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 1; note.append(_('Detail') + ': ' + _('transaction type') + ': ' + transaction_type[communication[o_idx:p_idx]])
|
||
o_idx = p_idx; p_idx += 26; note.append(_('Detail') + ': ' + _('identification of terminal') + ': ' + parse_terminal(communication[o_idx:p_idx]))
|
||
elif co_type == '113': # ATM/POS debit
|
||
structured_com = _('ATM/POS debit')
|
||
o_idx = p_idx; p_idx += 16; note.append(_('Detail') + ': ' + _('Masked PAN or card number') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 1; note.append(_('Detail') + ': ' + _('card scheme') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('terminal number') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('sequence number of transaction') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('date of transaction') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 4; note.append(_('Detail') + ': ' + _('hour of transaction') + ': ' + parsehour(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 1; note.append(_('Detail') + ': ' + _('transaction type') + ': ' + transaction_type[communication[o_idx:p_idx]])
|
||
o_idx = p_idx; p_idx += 26; note.append(_('Detail') + ': ' + _('identification of terminal') + ': ' + parse_terminal(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('original amount') + ': ' + parsefloat(communication[o_idx:p_idx], 3))
|
||
o_idx = p_idx; p_idx += 12; note.append(_('Detail') + ': ' + _('rate') + ': ' + parsefloat(communication[o_idx:p_idx], 8))
|
||
o_idx = p_idx; p_idx += 3; note.append(_('Detail') + ': ' + _('currency') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 5; note.append(_('Detail') + ': ' + _('volume') + ': ' + parsefloat(communication[o_idx:p_idx], 2))
|
||
o_idx = p_idx; p_idx += 2; note.append(_('Detail') + ': ' + _('product code') + ': ' + product_code[communication[o_idx:p_idx]])
|
||
o_idx = p_idx; p_idx += 5; note.append(_('Detail') + ': ' + _('unit price') + ': ' + parsefloat(communication[o_idx:p_idx], 3))
|
||
elif co_type == '114': # POS credit - individual transaction
|
||
structured_com = _('POS credit - individual transaction')
|
||
o_idx = p_idx; p_idx += 1; note.append(_('Detail') + ': ' + _('card scheme') + ': ' + card_scheme[communication[o_idx:p_idx]])
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('POS number') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 3; note.append(_('Detail') + ': ' + _('period number') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('sequence number of transaction') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('date of transaction') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 4; note.append(_('Detail') + ': ' + _('hour of transaction') + ': ' + parsehour(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 1; note.append(_('Detail') + ': ' + _('transaction type') + ': ' + transaction_type[communication[o_idx:p_idx]])
|
||
o_idx = p_idx; p_idx += 26; note.append(_('Detail') + ': ' + _('identification of terminal') + ': ' + parse_terminal(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 16; note.append(_('Detail') + ': ' + _('reference of transaction') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
elif co_type == '115': # Terminal cash deposit
|
||
structured_com = _('Terminal cash deposit')
|
||
o_idx = p_idx; p_idx += 16; note.append(_('Detail') + ': ' + _('PAN or card number') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 1; note.append(_('Detail') + ': ' + _('card scheme') + ': ' + card_scheme[communication[o_idx:p_idx]])
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('terminal number') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('sequence number of transaction') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('payment day') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 4; note.append(_('Detail') + ': ' + _('hour of payment') + ': ' + parsehour(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('validation date') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('sequence number of validation') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('original amount (given by the customer)') + ': ' + parsefloat(communication[o_idx:p_idx], 3))
|
||
o_idx = p_idx; p_idx += 1; note.append(_('Detail') + ': ' + _('conformity code or blank') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 26; note.append(_('Detail') + ': ' + _('identification of terminal') + ': ' + parse_terminal(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 12; note.append(_('Detail') + ': ' + _('message (structured of free)') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
elif co_type == '121': # Commercial bills
|
||
structured_com = _('Commercial bills')
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('amount of the bill') + ': ' + parsefloat(communication[o_idx:p_idx], 3))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('maturity date of the bill') + ': ' + parsedate(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('conventional maturity date') + ': ' + parsedate(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('date of issue of the bill') + ': ' + parsedate(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 11; note.append(_('Detail') + ': ' + _('company number') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 3; note.append(_('Detail') + ': ' + _('currency') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 3 # blanks
|
||
o_idx = p_idx; p_idx += 13; note.append(_('Detail') + ': ' + _('number of the bill') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 12; note.append(_('Detail') + ': ' + _('exchange rate') + ': ' + parsefloat(communication[o_idx:p_idx], 8))
|
||
elif co_type == '122': # Bills - calculation of interest
|
||
structured_com = _('Bills - calculation of interest')
|
||
o_idx = p_idx; p_idx += 4; note.append(_('Detail') + ': ' + _('number of days') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 12; note.append(_('Detail') + ': ' + _('interest rate') + ': ' + parsefloat(communication[o_idx:p_idx], 8))
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('basic amount of the calculation') + ': ' + parsefloat(communication[o_idx:p_idx], 3))
|
||
o_idx = p_idx; p_idx += 1; note.append(_('Detail') + ': ' + _('minimum rate') + ': ' + minimum[communication[o_idx:p_idx]])
|
||
o_idx = p_idx; p_idx += 13; note.append(_('Detail') + ': ' + _('number of the bill') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('maturity date of the bill') + ': ' + parsedate(communication[o_idx:p_idx]))
|
||
elif co_type == '123': # Fees and commissions
|
||
structured_com = _('Fees and commissions')
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('starting date') + ': ' + parsedate(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('maturity date') + ': ' + parsedate(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('basic amount') + ': ' + parsefloat(communication[o_idx:p_idx], 3))
|
||
o_idx = p_idx; p_idx += 12; note.append(_('Detail') + ': ' + _('percentage') + ': ' + parsefloat(communication[o_idx:p_idx], 8))
|
||
o_idx = p_idx; p_idx += 4; note.append(_('Detail') + ': ' + _('term in days') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 1; note.append(_('Detail') + ': ' + _('minimum rate') + ': ' + minimum[communication[o_idx:p_idx]])
|
||
o_idx = p_idx; p_idx += 13; note.append(_('Detail') + ': ' + _('guarantee number (no. allocated by the bank)') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
elif co_type == '124': # Number of the credit card
|
||
structured_com = _('Number of the credit card')
|
||
o_idx = p_idx; p_idx += 20; note.append(_('Detail') + ': ' + _('Masked PAN or card number') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 1; note.append(_('Detail') + ': ' + _('issuing institution') + ': ' + issuing_institution[communication[o_idx:p_idx]])
|
||
o_idx = p_idx; p_idx += 12; note.append(_('Detail') + ': ' + _('invoice number') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('identification number') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('date') + ': ' + parsedate(communication[o_idx:p_idx]))
|
||
elif co_type == '125': # Credit
|
||
structured_com = _('Credit')
|
||
o_idx = p_idx; p_idx += 12; note.append(_('Detail') + ': ' + _('account number of the credit') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('extension zone of account number of the credit') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('old balance of the credit') + ': ' + parsefloat(communication[o_idx:p_idx], 3))
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('new balance of the credit') + ': ' + parsefloat(communication[o_idx:p_idx], 3))
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('amount (equivalent in foreign currency)') + ': ' + parsefloat(communication[o_idx:p_idx], 3))
|
||
o_idx = p_idx; p_idx += 3; note.append(_('Detail') + ': ' + _('currency') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('starting date') + ': ' + parsedate(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('end date') + ': ' + parsedate(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 12; note.append(_('Detail') + ': ' + _('nominal interest rate or rate of charge') + ': ' + parsefloat(communication[o_idx:p_idx], 8))
|
||
o_idx = p_idx; p_idx += 13; note.append(_('Detail') + ': ' + _('reference of transaction on credit account') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
elif co_type == '126': # Term Investments
|
||
structured_com = _('Term Investments')
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('deposit number') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('deposit amount') + ': ' + parsefloat(communication[o_idx:p_idx], 3))
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('equivalent in the currency of the account') + ': ' + parsefloat(communication[o_idx:p_idx], 3))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('starting date') + ': ' + parsedate(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('end date') + ': ' + parsedate(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 12; note.append(_('Detail') + ': ' + _('interest rate') + ': ' + parsefloat(communication[o_idx:p_idx], 8))
|
||
o_idx = p_idx; p_idx += 15; note.append(_('Detail') + ': ' + _('amount of interest') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 3; note.append(_('Detail') + ': ' + _('currency') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 12; note.append(_('Detail') + ': ' + _('rate') + ': ' + parsefloat(communication[o_idx:p_idx], 8))
|
||
elif co_type == '127': # SEPA
|
||
structured_com = _('SEPA Direct Debit')
|
||
o_idx = p_idx; p_idx += 6; note.append(_('Detail') + ': ' + _('Settlement Date') + ': ' + parsedate(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 1; note.append(_('Detail') + ': ' + _('Type Direct Debit') + ': ' + type_direct_debit[communication[o_idx:p_idx]])
|
||
o_idx = p_idx; p_idx += 1; note.append(_('Detail') + ': ' + _('Direct Debit scheme') + ': ' + direct_debit_scheme[communication[o_idx:p_idx]])
|
||
o_idx = p_idx; p_idx += 1; note.append(_('Detail') + ': ' + _('Paid or reason for refused payment') + ': ' + payment_reason[communication[o_idx:p_idx]])
|
||
o_idx = p_idx; p_idx += 35; note.append(_('Detail') + ': ' + _('Creditor’s identification code') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 35; note.append(_('Detail') + ': ' + _('Mandate reference') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 62; note.append(_('Detail') + ': ' + _('Communicaton') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
o_idx = p_idx; p_idx += 1; note.append(_('Detail') + ': ' + _('Type of R transaction') + ': ' + sepa_type[communication[o_idx:p_idx]])
|
||
o_idx = p_idx; p_idx += 4; note.append(_('Detail') + ': ' + _('Reason') + ': ' + rmspaces(communication[o_idx:p_idx]))
|
||
else:
|
||
structured_com = _('Type of structured communication not supported: ') + co_type
|
||
note.append(communication)
|
||
return structured_com, note
|
||
|
||
|
||
class AccountJournal(models.Model):
|
||
_inherit = 'account.journal'
|
||
|
||
coda_split_transactions = fields.Boolean(
|
||
string="Split Transactions",
|
||
help="Split collective payments for CODA files",
|
||
default=True,
|
||
)
|
||
|
||
def _statement_import_check_bank_account(self, account_number):
|
||
if self.bank_account_id.acc_type == 'iban' and self.bank_account_id.get_bban() == account_number:
|
||
return True
|
||
return super()._statement_import_check_bank_account(account_number)
|
||
|
||
def _get_bank_statements_available_import_formats(self):
|
||
rslt = super(AccountJournal, self)._get_bank_statements_available_import_formats()
|
||
rslt.append('CODA')
|
||
return rslt
|
||
|
||
def _check_coda(self, coda_string):
|
||
# Matches the first 24 characters of a CODA file, as defined by the febelfin specifications
|
||
return re.match(r'0{5}\d{9}05[ D] +', coda_string) is not None
|
||
|
||
def _get_coda_file_statements(self, coda_data):
|
||
recordlist = coda_data.split('\n')
|
||
statements = []
|
||
globalisation_comm = {}
|
||
for line in recordlist:
|
||
if not line:
|
||
pass
|
||
elif line[0] == '0':
|
||
# Begin of a new Bank statement
|
||
statement = {}
|
||
statements.append(statement)
|
||
statement['version'] = line[127]
|
||
if statement['version'] not in ['1', '2']:
|
||
raise UserError(_('Error') + ' R001: ' + _('CODA V%s statements are not supported, please contact your bank', statement['version']))
|
||
statement['globalisation_stack'] = []
|
||
statement['lines'] = []
|
||
statement['date'] = time.strftime(tools.DEFAULT_SERVER_DATE_FORMAT, time.strptime(rmspaces(line[5:11]), '%d%m%y'))
|
||
statement['separateApplication'] = rmspaces(line[83:88])
|
||
elif line[0] == '1':
|
||
# Statement details
|
||
if statement['version'] == '1':
|
||
statement['acc_number'] = rmspaces(line[5:17])
|
||
statement['currency'] = rmspaces(line[18:21])
|
||
elif statement['version'] == '2':
|
||
if line[1] == '0': # Belgian bank account BBAN structure
|
||
statement['acc_number'] = rmspaces(line[5:17])
|
||
# '11' and '14' stand respecively for characters 'B' and 'E', it's a constant for Belgium, that we need to append to the account number before computing the check digits
|
||
statement['acc_number'] = 'BE%02d' % (98 - int(statement['acc_number'] + '111400') % 97) + statement['acc_number']
|
||
statement['currency'] = rmspaces(line[18:21])
|
||
elif line[1] == '1': # foreign bank account BBAN structure
|
||
raise UserError(_('Error') + ' R1001: ' + _('Foreign bank accounts with BBAN structure are not supported '))
|
||
elif line[1] == '2': # Belgian bank account IBAN structure
|
||
statement['acc_number'] = rmspaces(line[5:21])
|
||
statement['currency'] = rmspaces(line[39:42])
|
||
elif line[1] == '3': # foreign bank account IBAN structure
|
||
statement['acc_number'] = rmspaces(line[5:39])
|
||
statement['currency'] = rmspaces(line[39:42])
|
||
else: # Something else, not supported
|
||
raise UserError(_('Error') + ' R1003: ' + _('Unsupported bank account structure '))
|
||
statement['description'] = rmspaces(line[90:125])
|
||
statement['balance_start'] = float(rmspaces(line[43:58])) / 1000
|
||
if line[42] == '1': # 1 = Debit, the starting balance is negative
|
||
statement['balance_start'] = - statement['balance_start']
|
||
statement['balance_start_date'] = time.strftime(tools.DEFAULT_SERVER_DATE_FORMAT, time.strptime(rmspaces(line[58:64]), '%d%m%y')) if rmspaces(line[58:64]) != '000000' else statement['date']
|
||
statement['accountHolder'] = rmspaces(line[64:90])
|
||
statement['paperSeqNumber'] = rmspaces(line[2:5])
|
||
statement['codaSeqNumber'] = rmspaces(line[125:128])
|
||
elif line[0] == '2':
|
||
if line[1] == '1':
|
||
# New statement line
|
||
statementLine = {}
|
||
statementLine['ref'] = rmspaces(line[2:10])
|
||
statementLine['ref_move'] = rmspaces(line[2:6])
|
||
statementLine['ref_move_detail'] = rmspaces(line[6:10])
|
||
statementLine['sequence'] = len(statement['lines']) + 1
|
||
statementLine['transactionRef'] = rmspaces(line[10:31])
|
||
statementLine['debit'] = line[31] # 0 = Credit, 1 = Debit
|
||
statementLine['amount'] = float(rmspaces(line[32:47])) / 1000
|
||
if statementLine['debit'] == '1':
|
||
statementLine['amount'] = - statementLine['amount']
|
||
statementLine['transactionDate'] = time.strftime(tools.DEFAULT_SERVER_DATE_FORMAT, time.strptime(rmspaces(line[47:53]), '%d%m%y')) if rmspaces(line[47:53]) != '000000' else statement['date']
|
||
statementLine['transaction_type'] = int(rmspaces(line[53:54]))
|
||
statementLine['transaction_family'] = rmspaces(line[54:56])
|
||
statementLine['transaction_code'] = rmspaces(line[56:58])
|
||
statementLine['transaction_category'] = rmspaces(line[58:61])
|
||
if line[61] == '1':
|
||
# Structured communication
|
||
statementLine['communication_struct'] = True
|
||
statementLine['communication_type'] = line[62:65]
|
||
statementLine['communication'] = line[65:115]
|
||
else:
|
||
# Non-structured communication
|
||
statementLine['communication_struct'] = False
|
||
statementLine['communication'] = rmspaces(line[62:115])
|
||
statementLine['entryDate'] = time.strftime(tools.DEFAULT_SERVER_DATE_FORMAT, time.strptime(rmspaces(line[115:121]), '%d%m%y')) if rmspaces(line[115:121]) != '000000' else statement['date']
|
||
statementLine['type'] = 'normal'
|
||
statementLine['globalisation'] = int(line[124])
|
||
if statementLine['globalisation'] > 0:
|
||
if statementLine['ref_move'] in statement['globalisation_stack']:
|
||
statement['globalisation_stack'].remove(statementLine['ref_move'])
|
||
else:
|
||
statementLine['type'] = 'globalisation'
|
||
statement['globalisation_stack'].append(statementLine['ref_move'])
|
||
globalisation_comm[statementLine['ref_move']] = statementLine['communication']
|
||
if not statementLine.get('communication'):
|
||
statementLine['communication'] = globalisation_comm.get(statementLine['ref_move'], '')
|
||
statement['lines'].append(statementLine)
|
||
elif line[1] == '2':
|
||
if statement['lines'][-1]['ref'][0:4] != line[2:6]:
|
||
raise UserError(_('Error') + 'R2004: ' + _('CODA parsing error on movement data record 2.2, seq nr %s! Please report this issue via your Odoo support channel.', line[2:10]))
|
||
statement['lines'][-1]['communication'] += line[10:63]
|
||
statement['lines'][-1]['payment_reference'] = rmspaces(line[63:98])
|
||
statement['lines'][-1]['counterparty_bic'] = rmspaces(line[98:109])
|
||
# TODO 113, 114-117, 118-121, 122-125
|
||
elif line[1] == '3':
|
||
if statement['lines'][-1]['ref'][0:4] != line[2:6]:
|
||
raise UserError(_('Error') + 'R2005: ' + _('CODA parsing error on movement data record 2.3, seq nr %s! Please report this issue via your Odoo support channel.', line[2:10]))
|
||
if statement['version'] == '1':
|
||
statement['lines'][-1]['counterpartyNumber'] = rmspaces(line[10:22])
|
||
statement['lines'][-1]['counterpartyName'] = rmspaces(line[47:73])
|
||
statement['lines'][-1]['counterpartyAddress'] = rmspaces(line[73:125])
|
||
statement['lines'][-1]['counterpartyCurrency'] = ''
|
||
else:
|
||
if line[22] == ' ':
|
||
statement['lines'][-1]['counterpartyNumber'] = rmspaces(line[10:22])
|
||
statement['lines'][-1]['counterpartyCurrency'] = rmspaces(line[23:26])
|
||
else:
|
||
statement['lines'][-1]['counterpartyNumber'] = rmspaces(line[10:44])
|
||
statement['lines'][-1]['counterpartyCurrency'] = rmspaces(line[44:47])
|
||
statement['lines'][-1]['counterpartyName'] = rmspaces(line[47:82])
|
||
statement['lines'][-1]['communication'] += line[82:125]
|
||
else:
|
||
# movement data record 2.x (x != 1,2,3)
|
||
raise UserError(_('Error') + 'R2006: ' + _('\nMovement data records of type 2.%s are not supported ', line[1]))
|
||
elif line[0] == '3':
|
||
if line[1] == '1':
|
||
infoLine = {}
|
||
infoLine['entryDate'] = statement['lines'][-1]['entryDate']
|
||
infoLine['type'] = 'information'
|
||
infoLine['sequence'] = len(statement['lines']) + 1
|
||
infoLine['ref'] = rmspaces(line[2:10])
|
||
infoLine['ref_move'] = rmspaces(line[2:6])
|
||
infoLine['ref_move_detail'] = rmspaces(line[6:10])
|
||
infoLine['transactionRef'] = rmspaces(line[10:31])
|
||
infoLine['transaction_family'] = rmspaces(line[32:34])
|
||
infoLine['transaction_code'] = rmspaces(line[34:36])
|
||
infoLine['transaction_category'] = rmspaces(line[36:39])
|
||
if line[39] == '1':
|
||
# Structured communication
|
||
infoLine['communication_struct'] = True
|
||
infoLine['communication_type'] = line[40:43]
|
||
infoLine['communication'] = line[43:113]
|
||
else:
|
||
# Non-structured communication
|
||
infoLine['communication_struct'] = False
|
||
infoLine['communication'] = line[40:113]
|
||
statement['lines'].append(infoLine)
|
||
elif line[1] == '2':
|
||
if infoLine['ref'] != rmspaces(line[2:10]):
|
||
raise UserError(_('Error') + 'R3004: ' + _('CODA parsing error on information data record 3.2, seq nr %s! Please report this issue via your Odoo support channel.', line[2:10]))
|
||
statement['lines'][-1]['communication'] += rmspaces(line[10:115])
|
||
elif line[1] == '3':
|
||
if infoLine['ref'] != rmspaces(line[2:10]):
|
||
raise UserError(_('Error') + 'R3005: ' + _('CODA parsing error on information data record 3.3, seq nr %s! Please report this issue via your Odoo support channel.', line[2:10]))
|
||
statement['lines'][-1]['communication'] += rmspaces(line[10:100])
|
||
elif line[0] == '4':
|
||
comm_line = {}
|
||
comm_line['type'] = 'communication'
|
||
comm_line['sequence'] = len(statement['lines']) + 1
|
||
comm_line['ref'] = rmspaces(line[2:10])
|
||
comm_line['ref_move'] = rmspaces(line[2:6])
|
||
comm_line['ref_move_detail'] = rmspaces(line[6:10])
|
||
comm_line['communication'] = line[32:112]
|
||
statement['lines'].append(comm_line)
|
||
elif line[0] == '8':
|
||
# new balance record
|
||
statement['debit'] = line[41]
|
||
statement['paperSeqNumber'] = rmspaces(line[1:4])
|
||
statement['balance_end_real'] = float(rmspaces(line[42:57])) / 1000
|
||
statement['balance_end_realDate'] = time.strftime(tools.DEFAULT_SERVER_DATE_FORMAT, time.strptime(rmspaces(line[57:63]), '%d%m%y')) if rmspaces(line[57:63]) != '000000' else statement['date']
|
||
if statement['debit'] == '1': # 1=Debit
|
||
statement['balance_end_real'] = - statement['balance_end_real']
|
||
elif line[0] == '9':
|
||
statement['balanceMin'] = float(rmspaces(line[22:37])) / 1000
|
||
statement['balancePlus'] = float(rmspaces(line[37:52])) / 1000
|
||
if not statement.get('balance_end_real'):
|
||
statement['balance_end_real'] = statement['balance_start'] + statement['balancePlus'] - statement['balanceMin']
|
||
return statements
|
||
|
||
def _get_final_statements(self, statements):
|
||
ret_statements = []
|
||
for statement in statements:
|
||
statement['coda_note'] = ''
|
||
statement_line = []
|
||
statement_data = {
|
||
'name': int(statement['paperSeqNumber']),
|
||
'date': statement['date'],
|
||
'balance_start': statement['balance_start'],
|
||
'balance_end_real': statement['balance_end_real'],
|
||
}
|
||
temp_data = {}
|
||
for line in statement['lines']:
|
||
to_add = statement_line and statement_line[-1]['ref'][:4] == line.get('ref_move') and statement_line[-1] or temp_data
|
||
if line['type'] == 'information':
|
||
if line['communication_struct']:
|
||
to_add['narration'] = "\n".join([to_add.get('narration', ''), 'Communication: '] + parse_structured_communication(line['communication_type'], line['communication'])[1])
|
||
else:
|
||
to_add['narration'] = "\n".join([to_add.get('narration', ''), line['communication']])
|
||
elif line['type'] == 'communication':
|
||
statement['coda_note'] = "%s[%s] %s\n" % (statement['coda_note'], str(line['ref']), line['communication'])
|
||
elif line['type'] == 'normal'\
|
||
or (line['type'] == 'globalisation' and line['ref_move'] in statement['globalisation_stack'] and line['transaction_type'] in [1, 2]):
|
||
note = []
|
||
if line.get('counterpartyName'):
|
||
note.append(_('Counter Party') + ': ' + line['counterpartyName'])
|
||
else:
|
||
line['counterpartyName'] = False
|
||
if line.get('counterpartyNumber'):
|
||
try:
|
||
if int(line['counterpartyNumber']) == 0:
|
||
line['counterpartyNumber'] = False
|
||
except ValueError:
|
||
pass
|
||
if (
|
||
line.get('transaction_family', '') in ('01', '02', '41') # Credit transfer
|
||
and line.get('transaction_code', '') == '07' # Collective transfer
|
||
):
|
||
line['counterpartyNumber'] = False
|
||
if line['counterpartyNumber']:
|
||
note.append(_('Counter Party Account') + ': ' + line['counterpartyNumber'])
|
||
else:
|
||
line['counterpartyNumber'] = False
|
||
|
||
if line.get('counterpartyAddress'):
|
||
note.append(_('Counter Party Address') + ': ' + line['counterpartyAddress'])
|
||
structured_com = False
|
||
if line['communication_struct']:
|
||
structured_com, extend_notes = parse_structured_communication(line['communication_type'], line['communication'])
|
||
note.extend(extend_notes)
|
||
elif line.get('communication'):
|
||
note.append(_('Communication') + ': ' + rmspaces(line['communication']))
|
||
if not self.coda_split_transactions and statement_line and line['ref_move'] == statement_line[-1]['ref'][:4]:
|
||
to_add['amount'] = to_add.get('amount', 0) + line['amount']
|
||
to_add['narration'] = to_add.get('narration', '') + "\n" + "\n".join(note)
|
||
else:
|
||
line_data = {
|
||
'payment_ref': structured_com or line.get('communication', '') or '/',
|
||
'narration': "\n".join(note),
|
||
'transaction_type': parse_operation(line['transaction_type'], line['transaction_family'], line['transaction_code'], line['transaction_category']),
|
||
'date': line['entryDate'],
|
||
'amount': line['amount'],
|
||
'account_number': line.get('counterpartyNumber', None),
|
||
'partner_name': line['counterpartyName'],
|
||
'ref': self.coda_split_transactions and line['ref'] or line['ref_move'],
|
||
'sequence': line['sequence'],
|
||
'unique_import_id': str(statement['codaSeqNumber']) + '-' + str(statement['date']) + '-' + str(line['ref']),
|
||
}
|
||
if temp_data.get('narration'):
|
||
line_data['narration'] = temp_data.pop('narration') + '\n' + line_data['narration']
|
||
if temp_data.get('amount'):
|
||
line_data['amount'] += temp_data.pop('amount')
|
||
statement_line.append(line_data)
|
||
if statement['coda_note'] != '':
|
||
statement_data.update({'coda_note': _('Communication: ') + '\n' + statement['coda_note']})
|
||
statement_data.update({'transactions': statement_line})
|
||
ret_statements.append(statement_data)
|
||
return ret_statements
|
||
|
||
def _parse_bank_statement_file(self, attachment):
|
||
pattern = re.compile("[\u0020-\u1EFF\n\r]+") # printable characters
|
||
|
||
# Try different encodings for the file
|
||
for encoding in ('utf_8', 'cp850', 'cp858', 'cp1140', 'cp1252', 'iso8859_15', 'utf_32', 'utf_16', 'windows-1252'):
|
||
try:
|
||
record_data = attachment.raw.decode(encoding)
|
||
except UnicodeDecodeError:
|
||
continue
|
||
if pattern.fullmatch(record_data, re.MULTILINE):
|
||
break # We only have printable characters, stick with this one
|
||
|
||
if not self._check_coda(record_data):
|
||
return super()._parse_bank_statement_file(attachment)
|
||
|
||
statements = self._get_coda_file_statements(record_data)
|
||
ret_statements = self._get_final_statements(statements)
|
||
|
||
# Order the transactions according the newly created statements to ensure valid balances.
|
||
line_sequence = 1
|
||
for statement_vals in reversed(ret_statements):
|
||
for statement_line_vals in reversed(statement_vals.get('transactions', [])):
|
||
statement_line_vals['sequence'] = line_sequence
|
||
line_sequence += 1
|
||
|
||
currency_code = statements and statements[-1]['currency']
|
||
acc_number = statements[0] and statements[0]['acc_number'] or False
|
||
return currency_code, acc_number, ret_statements
|