1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/l10n_jp_zengin/models/account_journal.py
2024-12-10 09:04:09 +07:00

199 lines
9.3 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import contextlib
import re
from datetime import datetime
from odoo import _, fields, models
from odoo.tools import plaintext2html
from odoo.exceptions import UserError
class AccountJournal(models.Model):
_inherit = "account.journal"
l10n_jp_zengin_merge_transactions = fields.Boolean(
string="Merge Transactions",
help="Merge collective payments for Zengin files",
)
def _default_outbound_payment_methods(self):
res = super()._default_outbound_payment_methods()
if self._is_payment_method_available("zengin"):
res |= self.env.ref('l10n_jp_zengin.account_payment_method_zengin_outbound')
return res
def _get_bank_statements_available_import_formats(self):
rslt = super()._get_bank_statements_available_import_formats()
rslt.append('ZENGIN')
return rslt
def _check_zengin(self, zengin_string):
# Match the first 59 characters of the Zengin file, as defined by the zengin specifications
return re.match(r'10[13]0\d{6}\d{6}\d{6}\d{4}[ \uFF5F-\uFF9F]{15}\d{3}[ \uFF5F-\uFF9F]{15}', zengin_string) is not None
def _parse_bank_statement_file(self, attachment):
record_data = False
with contextlib.suppress(UnicodeDecodeError):
record_data = attachment.raw.decode('SHIFT_JIS') # Zengin files are encoded in SHIFT_JIS
if not record_data or not self._check_zengin(record_data):
return super()._parse_bank_statement_file(attachment)
return self._parse_bank_statement_file_zengin(record_data)
def _parse_bank_statement_file_zengin(self, record_data):
def rmspaces(s):
return s.strip()
def parsedate(s):
# Zengin has only 10 years of data, so any year before 26 is in the Reiwa era.
# The Reiwa era starts from 1st May 2019.
# The Heisei era starts from 8th January 1989 and ends on 30th April 2019.
# Japanese years are counted from 1.
# Examples:
# - 260101 -> 2014-01-01 (Heisei 26)
# - 010501 -> 2019-05-01 (Reiwa 1)
def parse_japanese_year(dy):
return dy + 2019 - 1 if dy < 26 else dy + 1989 - 1
dt = str(parse_japanese_year(int(s[0:2].lstrip('0')))) + s[2:6]
return datetime.strptime(dt, '%Y%m%d').strftime('%Y-%m-%d')
def parse_header(line):
result = {
'date': parsedate(line[10:16]),
}
if line[1:3] == '01':
if len(line) != 199:
raise UserError(_('Incorrect header length: %(length)s', length=len(line)))
statement_type = 'transfer'
acc_number = line[60:67]
result['name'] = _("Zengin Transfer - %(date)s", date=parsedate(line[10:16]))
else:
if len(line) != 200:
raise UserError(_('Incorrect header length: %(length)s', length=len(line)))
statement_type = 'deposit_withdrawal'
acc_number = line[66:73]
result['name'] = _("Zengin Deposit/Withdrawal - %(date)s", date=parsedate(line[10:16]))
if balance_start := rmspaces(line[115:129]):
result['balance_start'] = float(balance_start)
return statement_type, acc_number, result
def get_transfer_note(line, exceed_amount: bool):
note = [
_('Bank Name: %(bank)s', bank=rmspaces(line[97:112])),
_('Branch Name: %(branch)s', branch=rmspaces(line[112:127])),
]
if edi_info := rmspaces(line[128:148]) if exceed_amount else rmspaces(line[152:172]):
note.append(_('EDI Information: %(info)s', info=edi_info))
return note
def get_deposit_withdrawal_note(line, deposit_type):
transaction_category_map = {
'10': _('Cash'),
'11': _('Transfer'),
'12': _('Deposit'),
'13': _('Exchange'),
'14': _('Transfer'),
'18': _('Other'),
}
bill_type_map = {
'1': _('Check'),
'2': _('Promissory Note'),
'3': _('Bill of Exchange'),
}
note = []
note.append(_('Transaction Category: %(category)s', category=transaction_category_map.get(line[22:24], _('Unknown'))))
if bill_type := rmspaces(line[60:61]):
note.extend([
_('Bill Type: %(type)s', type=bill_type_map.get(bill_type, _('Unknown'))),
_('Bill Number: %(id)s', id=rmspaces(line[61:68])),
])
if deposit_type in ['regular', 'current', 'savings']:
if sending_bank := rmspaces(line[129:144]):
note.append(_('Sending Bank: %(bank)s', bank=sending_bank))
if sending_bank_branch := rmspaces(line[144:159]):
note.append(_('Sending Bank Branch: %(branch)s', branch=sending_bank_branch))
if summary := rmspaces(line[159:179]):
note.append(_('Note: %(note)s', note=summary))
if edi_info := rmspaces(line[179:199]):
note.append(_('EDI Information: %(info)s', info=edi_info))
else: # notice, term, fixed_deposit
if deposit_date_str := rmspaces(line[71:77]):
note.append(_('Initial Deposit Date: %(date)s', date=parsedate(deposit_date_str)))
note.append(_('Interest Rate: %(integer)s.%(decimal)s%', line[77:79], line[79:83]))
if maturity_date_str := rmspaces(line[83:89]):
note.append(_('Maturity Date: %(date)s', date=parsedate(maturity_date_str)))
if period_str := rmspaces(line[89:95]):
note.append(_('Period: %(date)s', date=parsedate(period_str)))
if periodic_interest_str := rmspaces(line[95:102]):
note.append(_('Periodic Interest: %(amount)s', amount=periodic_interest_str))
if summary := rmspaces(line[171:191]):
note.append(_('Note: %(note)s', note=summary))
return note
def parse_transaction_line(statement_type, line, deposit_type=None):
transaction = {}
if statement_type == 'transfer':
if len(line) != 199:
raise UserError(_('Incorrect transaction length: %(length)s', length=len(line)))
exceed_amount = line[19: 29] == '0000000000'
transaction = {
'date': parsedate(line[7:13]),
'amount': float(line[128:140]) if exceed_amount else float(line[19:29]),
'unique_import_id': line[1:7] + '/' + parsedate(line[7:13]),
'partner_name': rmspaces(line[49:97]),
'payment_ref': 'Transfer - ' + line[1:7],
'narration': plaintext2html('\n'.join(get_transfer_note(line, exceed_amount))),
}
else:
if len(line) != 200:
raise UserError(_('Incorrect transaction length: %(length)s', length=len(line)))
payment_type = 'deposit' if line[21:22] == '1' else 'withdrawal'
sign = -1 if payment_type == 'withdrawal' else 1
transaction = {
'date': parsedate(line[9:15]),
'amount': sign * float(line[24: 36]),
'unique_import_id': line[1:9] + '/' + parsedate(line[9:15]),
'payment_ref': payment_type.capitalize() + ' - ' + line[1:9],
'narration': plaintext2html('\n'.join(get_deposit_withdrawal_note(line, deposit_type))),
}
if sender_name := rmspaces(line[81:129]):
transaction['partner_name'] = sender_name
return transaction
recordlist = record_data.split('\r\n')
statement = {"transactions": []}
statement_type = False
acc_number = False
deposit_type = False
deposit_type_map = {
'1': 'regular',
'2': 'current',
'4': 'savings',
'5': 'notice',
'6': 'term',
'7': 'fixed_deposit',
}
for line in recordlist:
if not line:
pass
elif line[0] == '1': # Header
statement_type, acc_number, result = parse_header(line)
statement.update(result)
if statement_type == 'deposit_withdrawal':
deposit_type = deposit_type_map.get(line[62:63])
if not deposit_type:
raise UserError(_('Unknown deposit type: %(type)s', type=line[62:63]))
elif line[0] == '2': # Detail
if line[22:24] == '19':
continue # Skip correction transactions
transaction = parse_transaction_line(statement_type, line, deposit_type)
statement['transactions'].append(transaction)
elif line[0] == '8': # Footer
if statement_type == 'transfer':
continue
if balance_end := rmspaces(line[115:129]):
statement['balance_end_real'] = float(balance_end)
return 'JPY', acc_number, [statement]