16 changed files with 657 additions and 1 deletions
|
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 333 KiB |
After Width: | Height: | Size: 245 KiB |
|
@ -0,0 +1,65 @@ |
|||||
|
OFXHEADER:100 |
||||
|
DATA:OFXSGML |
||||
|
VERSION:102 |
||||
|
SECURITY:NONE |
||||
|
ENCODING:USASCII |
||||
|
CHARSET:1252 |
||||
|
COMPRESSION:NONE |
||||
|
OLDFILEUID:NONE |
||||
|
NEWFILEUID:NONE |
||||
|
|
||||
|
<OFX> |
||||
|
<SIGNONMSGSRSV1> |
||||
|
<SONRS> |
||||
|
<STATUS> |
||||
|
<CODE>0</CODE> |
||||
|
<SEVERITY>INFO</SEVERITY> |
||||
|
</STATUS> |
||||
|
<DTSERVER>20230419</DTSERVER> |
||||
|
<LANGUAGE>ENG</LANGUAGE> |
||||
|
<FI> |
||||
|
<ORG>BANK123</ORG> |
||||
|
</FI> |
||||
|
</SONRS> |
||||
|
</SIGNONMSGSRSV1> |
||||
|
<BANKMSGSRSV1> |
||||
|
<STMTTRNRS> |
||||
|
<TRNUID>1234567890</TRNUID> |
||||
|
<STATUS> |
||||
|
<CODE>0</CODE> |
||||
|
<SEVERITY>INFO</SEVERITY> |
||||
|
</STATUS> |
||||
|
<STMTRS> |
||||
|
<CURDEF>USD</CURDEF> |
||||
|
<BANKACCTFROM> |
||||
|
<BANKID>Bank-2023-04-14/1</BANKID> |
||||
|
<ACCTID>1234567890</ACCTID> |
||||
|
<ACCTTYPE>CHECKING</ACCTTYPE> |
||||
|
</BANKACCTFROM> |
||||
|
<BANKTRANLIST> |
||||
|
<DTSTART>20220101</DTSTART> |
||||
|
<DTEND>20220131</DTEND> |
||||
|
<STMTTRN> |
||||
|
<TRNTYPE>DEBIT</TRNTYPE> |
||||
|
<DTPOSTED>20230419</DTPOSTED> |
||||
|
<TRNAMT>-3000.00</TRNAMT> |
||||
|
<FITID>1234567890-20220102-1</FITID> |
||||
|
<NAME>Deco Addict</NAME> |
||||
|
</STMTTRN> |
||||
|
<STMTTRN> |
||||
|
<TRNTYPE>CREDIT</TRNTYPE> |
||||
|
<DTPOSTED>20220115</DTPOSTED> |
||||
|
<TRNAMT>1000.00</TRNAMT> |
||||
|
<FITID>1234567890-20220115-1</FITID> |
||||
|
<NAME>Ready Mat</NAME> |
||||
|
</STMTTRN> |
||||
|
</BANKTRANLIST> |
||||
|
<LEDGERBAL> |
||||
|
<BALAMT>10044.87</BALAMT> |
||||
|
<DTASOF>20220131</DTASOF> |
||||
|
</LEDGERBAL> |
||||
|
</STMTRS> |
||||
|
</STMTTRNRS> |
||||
|
</BANKMSGSRSV1> |
||||
|
</OFX> |
||||
|
|
@ -0,0 +1,15 @@ |
|||||
|
!Type:Bank |
||||
|
D14/04/2023 |
||||
|
T2000.00 |
||||
|
PBank-2023-02-11/1 |
||||
|
^ |
||||
|
!Type:Bank |
||||
|
D09/04/2023 |
||||
|
T-500.00 |
||||
|
PBank-2023-04-09/2 |
||||
|
^ |
||||
|
!Type:Bank |
||||
|
D19/06/2022 |
||||
|
T1000.00 |
||||
|
PBank-2023-04-21/3 |
||||
|
^ |
Binary file not shown.
@ -0,0 +1,336 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Akhil Ashok (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU LESSER |
||||
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
||||
|
# |
||||
|
# 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
||||
|
# (LGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
import base64 |
||||
|
import codecs |
||||
|
import openpyxl |
||||
|
import os |
||||
|
from datetime import datetime |
||||
|
from io import BytesIO |
||||
|
from odoo import fields, models, _ |
||||
|
from odoo.exceptions import ValidationError |
||||
|
from ofxparse import OfxParser |
||||
|
from qifparse.parser import QifParser |
||||
|
|
||||
|
|
||||
|
class ImportBankStatement(models.TransientModel): |
||||
|
""" A class to import files as bank statement """ |
||||
|
_name = "import.bank.statement" |
||||
|
_description = "Import button" |
||||
|
_rec_name = "file_name" |
||||
|
|
||||
|
attachment = fields.Binary(string="File", required=True, |
||||
|
help="Choose the file to import") |
||||
|
file_name = fields.Char(string="File Name", help="Name of the file") |
||||
|
journal_id = fields.Many2one('account.journal', string="Journal ID", |
||||
|
help="Journal in which the file importing") |
||||
|
|
||||
|
def action_statement_import(self): |
||||
|
"""Function to import csv, xlsx, ofx and qif file format""" |
||||
|
split_tup = os.path.splitext(self.file_name) |
||||
|
if split_tup[1] == '.csv' or split_tup[1] == '.xlsx' or split_tup[ |
||||
|
1] == '.ofx' or split_tup[1] == '.qif': |
||||
|
if split_tup[1] == '.csv': |
||||
|
# Reading csv file |
||||
|
try: |
||||
|
file = base64.b64decode(self.attachment) |
||||
|
file_string = file.decode('utf-8') |
||||
|
file_string = file_string.split('\n') |
||||
|
except: |
||||
|
raise ValidationError(_("Choose correct file")) |
||||
|
# Skipping the first line |
||||
|
firstline = True |
||||
|
for file_item in file_string: |
||||
|
if firstline: |
||||
|
firstline = False |
||||
|
continue |
||||
|
# Reading the content from csv file |
||||
|
if file_item.split(',') != ['']: |
||||
|
if file_item.split(',')[0] and file_item.split(',')[1] \ |
||||
|
and file_item.split(',')[4]: |
||||
|
date_obj = str(fields.date.today()) if not \ |
||||
|
file_item.split(',')[3] else \ |
||||
|
file_item.split(',')[ |
||||
|
3] |
||||
|
transaction_date = datetime.strptime(date_obj, |
||||
|
"%Y-%m-%d") |
||||
|
partner = self.env['res.partner'].search( |
||||
|
[('name', '=', file_item.split(',')[4])]) |
||||
|
# Creating a record in account.bank.statement model |
||||
|
if partner: |
||||
|
statement = self.env[ |
||||
|
'account.bank.statement'].create({ |
||||
|
'name': file_item.split(',')[0], |
||||
|
'line_ids': [ |
||||
|
(0, 0, { |
||||
|
'date': transaction_date, |
||||
|
'payment_ref': 'csv file', |
||||
|
'partner_id': partner.id, |
||||
|
'journal_id': self.journal_id.id, |
||||
|
'amount': file_item.split(',')[1], |
||||
|
'amount_currency': |
||||
|
file_item.split(',')[2], |
||||
|
}), |
||||
|
], |
||||
|
}) |
||||
|
else: |
||||
|
raise ValidationError(_("Partner not exist")) |
||||
|
else: |
||||
|
if not file_item.split(',')[0]: |
||||
|
raise ValidationError( |
||||
|
_("Account name is not set")) |
||||
|
elif not file_item.split(',')[1]: |
||||
|
raise ValidationError( |
||||
|
_("Amount is not set")) |
||||
|
elif not file_item.split(',')[4]: |
||||
|
date_obj = str(fields.date.today()) if not \ |
||||
|
file_item.split(',')[3] else \ |
||||
|
file_item.split(',')[ |
||||
|
3] |
||||
|
transaction_date = datetime.strptime(date_obj, |
||||
|
"%Y-%m-%d") |
||||
|
# Creating a record in account.bank.statement model |
||||
|
statement = self.env[ |
||||
|
'account.bank.statement'].create({ |
||||
|
'name': file_item.split(',')[0], |
||||
|
'line_ids': [ |
||||
|
(0, 0, { |
||||
|
'date': transaction_date, |
||||
|
'payment_ref': 'csv file', |
||||
|
'journal_id': self.journal_id.id, |
||||
|
'amount': file_item.split(',')[ |
||||
|
1], |
||||
|
'amount_currency': |
||||
|
file_item.split(',')[2], |
||||
|
}), |
||||
|
], |
||||
|
}) |
||||
|
return { |
||||
|
'type': 'ir.actions.act_window', |
||||
|
'name': 'Statements', |
||||
|
'view_mode': 'list', |
||||
|
'res_model': 'account.bank.statement', |
||||
|
'res_id': statement.id, |
||||
|
} |
||||
|
elif split_tup[1] == '.xlsx': |
||||
|
# Reading xlsx file |
||||
|
try: |
||||
|
order = openpyxl.load_workbook( |
||||
|
filename=BytesIO(base64.b64decode(self.attachment))) |
||||
|
xl_order = order.active |
||||
|
except: |
||||
|
raise ValidationError(_("Choose correct file")) |
||||
|
for record in xl_order.iter_rows(min_row=2, max_row=None, |
||||
|
min_col=None, |
||||
|
max_col=None, |
||||
|
values_only=True): |
||||
|
line = list(record) |
||||
|
# Reading the content from file |
||||
|
if line[0] and line[1] and line[3]: |
||||
|
partner = self.env['res.partner'].search( |
||||
|
[('name', '=', line[3])]) |
||||
|
date_obj = fields.date.today() if not line[2] else \ |
||||
|
line[2].date() |
||||
|
# Creating record |
||||
|
if partner: |
||||
|
statement = self.env[ |
||||
|
'account.bank.statement'].create({ |
||||
|
'name': line[0], |
||||
|
'line_ids': [ |
||||
|
(0, 0, { |
||||
|
'date': date_obj, |
||||
|
'payment_ref': 'xlsx file', |
||||
|
'partner_id': partner.id, |
||||
|
'journal_id': self.journal_id.id, |
||||
|
'amount': line[1], |
||||
|
}), |
||||
|
], |
||||
|
}) |
||||
|
else: |
||||
|
raise ValidationError(_("Partner not exist")) |
||||
|
else: |
||||
|
if not line[0]: |
||||
|
raise ValidationError( |
||||
|
_("Account name is not set")) |
||||
|
elif not line[1]: |
||||
|
raise ValidationError( |
||||
|
_("Amount is not set")) |
||||
|
elif not line[3]: |
||||
|
date_obj = fields.date.today() if not line[2] else \ |
||||
|
line[2].date() |
||||
|
# Creating record |
||||
|
statement = self.env[ |
||||
|
'account.bank.statement'].create({ |
||||
|
'name': line[0], |
||||
|
'line_ids': [ |
||||
|
(0, 0, { |
||||
|
'date': date_obj, |
||||
|
'payment_ref': 'xlsx file', |
||||
|
'journal_id': self.journal_id.id, |
||||
|
'amount': line[1], |
||||
|
}), |
||||
|
], |
||||
|
}) |
||||
|
return { |
||||
|
'type': 'ir.actions.act_window', |
||||
|
'name': 'Statements', |
||||
|
'view_mode': 'list', |
||||
|
'res_model': 'account.bank.statement', |
||||
|
'res_id': statement.id, |
||||
|
} |
||||
|
elif split_tup[1] == '.ofx': |
||||
|
# Searching the path of the file |
||||
|
file_attachment = self.env["ir.attachment"].search( |
||||
|
['|', ('res_field', '!=', False), |
||||
|
('res_field', '=', False), |
||||
|
('res_id', '=', self.id), |
||||
|
('res_model', '=', 'import.bank.statement')], |
||||
|
limit=1) |
||||
|
file_path = file_attachment._full_path( |
||||
|
file_attachment.store_fname) |
||||
|
# Parsing the file |
||||
|
try: |
||||
|
with codecs.open(file_path) as fileobj: |
||||
|
ofx_file = OfxParser.parse(fileobj) |
||||
|
except: |
||||
|
raise ValidationError(_("Wrong file format")) |
||||
|
if not ofx_file.account: |
||||
|
raise ValidationError( |
||||
|
_("No account information found in OFX file.")) |
||||
|
if not ofx_file.account.statement: |
||||
|
raise ValidationError( |
||||
|
_("No statement information found in OFX file.")) |
||||
|
statement_list = [] |
||||
|
# Reading the content from file |
||||
|
for transaction in ofx_file.account.statement.transactions: |
||||
|
if transaction.type == "debit" and transaction.amount != 0: |
||||
|
payee = transaction.payee |
||||
|
amount = transaction.amount |
||||
|
date = transaction.date |
||||
|
if not date: |
||||
|
date = fields.date.today() |
||||
|
partner = self.env['res.partner'].search( |
||||
|
[('name', '=', payee)]) |
||||
|
if partner: |
||||
|
statement_list.append([partner.id, amount, date]) |
||||
|
else: |
||||
|
raise ValidationError(_("Partner not exist")) |
||||
|
if transaction.type == "credit" and transaction.amount != 0: |
||||
|
payee = transaction.payee |
||||
|
amount = transaction.amount |
||||
|
date = transaction.date |
||||
|
if not date: |
||||
|
date = fields.date.today() |
||||
|
partner = self.env['res.partner'].search( |
||||
|
[('name', '=', payee)]) |
||||
|
if partner: |
||||
|
statement_list.append([partner.id, amount, date]) |
||||
|
else: |
||||
|
raise ValidationError(_("Partner not exist")) |
||||
|
# Creating record |
||||
|
if statement_list: |
||||
|
for item in statement_list: |
||||
|
statement = self.env['account.bank.statement'].create({ |
||||
|
'name': ofx_file.account.routing_number, |
||||
|
'line_ids': [ |
||||
|
(0, 0, { |
||||
|
'date': item[2], |
||||
|
'payment_ref': 'ofx file', |
||||
|
'partner_id': item[0], |
||||
|
'journal_id': self.journal_id.id, |
||||
|
'amount': item[1], |
||||
|
}), |
||||
|
], |
||||
|
}) |
||||
|
return { |
||||
|
'type': 'ir.actions.act_window', |
||||
|
'name': 'Statements', |
||||
|
'view_mode': 'list', |
||||
|
'res_model': 'account.bank.statement', |
||||
|
'res_id': statement.id, |
||||
|
} |
||||
|
else: |
||||
|
raise ValidationError(_("There is no data to import")) |
||||
|
elif split_tup[1] == '.qif': |
||||
|
# Searching the path of qif file |
||||
|
file_attachment = self.env["ir.attachment"].search( |
||||
|
['|', ('res_field', '!=', False), |
||||
|
('res_field', '=', False), |
||||
|
('res_id', '=', self.id), |
||||
|
('res_model', '=', 'import.bank.statement')], |
||||
|
limit=1) |
||||
|
file_path = file_attachment._full_path( |
||||
|
file_attachment.store_fname) |
||||
|
# Parsing the qif file |
||||
|
try: |
||||
|
parser = QifParser() |
||||
|
with open(file_path, 'r') as qiffile: |
||||
|
qif = parser.parse(qiffile) |
||||
|
except: |
||||
|
raise ValidationError(_("Wrong file format")) |
||||
|
file_string = str(qif) |
||||
|
file_item = file_string.split('^') |
||||
|
file_item[-1] = file_item[-1].rstrip('\n') |
||||
|
if file_item[-1] == '': |
||||
|
file_item.pop() |
||||
|
statement_list = [] |
||||
|
for item in file_item: |
||||
|
if not item.startswith('!Type:Bank'): |
||||
|
item = '!Type:Bank' + item |
||||
|
data = item.split('\n') |
||||
|
# Reading the file content |
||||
|
date_entry = data[1][1:] |
||||
|
amount = float(data[2][1:]) |
||||
|
payee = data[3][1:] |
||||
|
if amount and payee: |
||||
|
if not date_entry: |
||||
|
date_entry = str(fields.date.today()) |
||||
|
date_object = datetime.strptime(date_entry, '%d/%m/%Y') |
||||
|
date = date_object.strftime('%Y-%m-%d') |
||||
|
statement_list.append([payee, amount, date]) |
||||
|
else: |
||||
|
if not amount: |
||||
|
raise ValidationError(_("Amount is not set")) |
||||
|
elif not payee: |
||||
|
raise ValidationError(_("Payee is not set")) |
||||
|
# Creating record |
||||
|
if statement_list: |
||||
|
for item in statement_list: |
||||
|
statement = self.env['account.bank.statement'].create({ |
||||
|
'name': item[0], |
||||
|
'line_ids': [ |
||||
|
(0, 0, { |
||||
|
'date': item[2], |
||||
|
'payment_ref': 'qif file', |
||||
|
'journal_id': self.journal_id.id, |
||||
|
'amount': item[1], |
||||
|
}), |
||||
|
], |
||||
|
}) |
||||
|
return { |
||||
|
'type': 'ir.actions.act_window', |
||||
|
'name': 'Statements', |
||||
|
'view_mode': 'list', |
||||
|
'res_model': 'account.bank.statement', |
||||
|
'res_id': statement.id, |
||||
|
} |
||||
|
else: |
||||
|
raise ValidationError(_("Choose correct file")) |
@ -0,0 +1,41 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<!-- Wizard view to upload files --> |
||||
|
<record id="import_bank_statement_view_form" model="ir.ui.view"> |
||||
|
<field name="name">import.bank.statement.view.form</field> |
||||
|
<field name="model">import.bank.statement</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form> |
||||
|
<sheet> |
||||
|
<p> |
||||
|
<center> |
||||
|
<i>Upload csv or xlsx or ofx or qif file format</i> |
||||
|
</center> |
||||
|
</p> |
||||
|
<group> |
||||
|
<group> |
||||
|
<field name="attachment" filename="file_name"/> |
||||
|
<field name="file_name" invisible="1"/> |
||||
|
<field name="journal_id" invisible="1"/> |
||||
|
</group> |
||||
|
</group> |
||||
|
<footer> |
||||
|
<button name="action_statement_import" class="oe_highlight" |
||||
|
string="IMPORT" type="object" |
||||
|
help="Import bank statement in CSV or XLSX format"/> |
||||
|
</footer> |
||||
|
</sheet> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
<!-- Wizard action record --> |
||||
|
<record id="import_bank_statement_view_action" |
||||
|
model="ir.actions.act_window"> |
||||
|
<field name="name">import.bank.statement.view.action</field> |
||||
|
<field name="type">ir.actions.act_window</field> |
||||
|
<field name="res_model">import.bank.statement</field> |
||||
|
<field name="view_mode">list,form</field> |
||||
|
<field name="view_id" ref="base_accounting_kit.import_bank_statement_view_form"/> |
||||
|
<field name="target">new</field> |
||||
|
</record> |
||||
|
</odoo> |
Loading…
Reference in new issue