Skip to content

Commit

Permalink
[17.0][ADD] osi_nacha_80_byte.
Browse files Browse the repository at this point in the history
  • Loading branch information
Murtaza-OSI committed Jan 21, 2025
1 parent 36242e4 commit 099b971
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 0 deletions.
22 changes: 22 additions & 0 deletions osi_nacha_80_byte/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3

========
Overview
========

* The Nacha format needs to be updated to match CIBC format and specifications.


=======
Credits
=======

* Open Source Integrators <http://www.opensourceintegrators.com>


Contributors
------------

* Chanakya Soni <[email protected]>
3 changes: 3 additions & 0 deletions osi_nacha_80_byte/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Copyright (C) 2024, Open Source Integrators
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from . import models
21 changes: 21 additions & 0 deletions osi_nacha_80_byte/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright (C) 2024, Open Source Integrators
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
{
"name": "OSI NACHA 80byte",
"countries": ["us"],
"summary": """Export payments as NACHA 80byte""",
"category": "Accounting",
"version": "17.0.1.0.0",
"license": "LGPL-3",
"author": "Open Source Integrators",
"maintainer": "Open Source Integrators",
"website": "https://github.com/ursais/osi-addons",
"depends": [
"l10n_us_payment_nacha",
],
"data": [
"data/ir_sequence.xml",
"views/account_journal_views.xml",
],
"installable": True,
}
9 changes: 9 additions & 0 deletions osi_nacha_80_byte/data/ir_sequence.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="ir_sequence_file_number" model="ir.sequence">
<field name="name">Nacha File Number</field>
<field name="code">nacha.file.sequence</field>
<field name="padding">4</field>
<field name="company_id" eval="False" />
</record>
</odoo>
5 changes: 5 additions & 0 deletions osi_nacha_80_byte/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright (C) 2024, Open Source Integrators
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from . import account_journal
from . import account_batch_payment
167 changes: 167 additions & 0 deletions osi_nacha_80_byte/models/account_batch_payment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Copyright (C) 2024, Open Source Integrators
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from odoo import _, fields, models
from odoo.exceptions import ValidationError


class AccountBatchPayment(models.Model):
_inherit = "account.batch.payment"

def _generate_nacha_batch_trailer_record(self):
entry = []
amount = self._get_total_cents(self.payment_ids)
entry.append("7460")
entry.append("{:06d}".format(len(self.payment_ids)))
entry.append("{:010d}".format(0))
entry.append("{:20.20}".format(""))
entry.append("{:012d}".format(amount))
entry.append("{:28.28}".format(""))
return "".join(entry)

def _generate_nacha_trailer(self):
entry = []
entry.append("9")
entry.append("{:06d}".format(1))
entry.append("{:06d}".format(len(self.payment_ids)))
entry.append("{:67.67}".format(""))
return "".join(entry)

def _generate_nacha_entry_detail(self, payment_nr, payment, is_offset):
if self.journal_id.nacha_file_type == "80_byte":
amount = sum(payment.mapped("amount"))
amount = self._get_total_cents(payment)
entry = []
if len(payment) > 1:
payment = payment[0]
self._validate_bank_for_nacha(payment)

reference = "{}{:011d}".format("CK", payment.id)
entry.append("6C")
entry.append("{:1.1}".format(""))
entry.append(
"{:4.4}".format(str(payment.partner_bank_id.bank_id.bic or ""))
)
entry.append("{:<5.5}".format(payment.partner_bank_id.aba_routing))
entry.append("{:<12.12}".format(payment.partner_bank_id.acc_number))
entry.append("{:5.5}".format(""))

entry.append("{:010d}".format(amount))
entry.append("{:<13.13}".format(reference))
entry.append(
"{}{:<21.21}".format(" ", payment.partner_bank_id.acc_holder_name)
)
entry.append("{:6.6}".format(""))
return "".join(entry)
else:
return super()._generate_nacha_entry_detail(payment_nr, payment, is_offset)

def _generate_nacha_batch_header_record(self, date, batch_nr):
if self.journal_id.nacha_file_type == "80_byte":
batch = []
batch.append("5")
batch.append("{:46.46}".format(""))
batch.append("{:3.3}".format("460"))
batch.append("{:10.10}".format(""))
batch.append("{:6.6}".format(self.date.strftime("%y%m%d")))
batch.append("{:14.14}".format(""))
return "".join(batch)
else:
return super()._generate_nacha_batch_header_record(date, batch_nr)

def _generate_nacha_header(self):
if self.journal_id.nacha_file_type == "80_byte":
# Ensure all mandatory fields are filled
if not self.journal_id.nacha_immediate_destination:
raise ValidationError(
_("Destination Data Center (Immediate Destination) is not filled.")
)
if not self.journal_id.nacha_immediate_origin:
raise ValidationError(
_("Originator Number (Immediate Origin) is not filled.")
)
if not self.journal_id.bank_account_id.bank_id.bic:
raise ValidationError(_("Institution Number (BIC) is not filled."))
if not self.journal_id.bank_account_id.aba_routing:
raise ValidationError(
_("Branch Transit Number (ABA Routing) is not filled.")
)
if not self.journal_id.bank_account_id.acc_number:
raise ValidationError(_("Account Number is not filled."))
if not self.journal_id.nacha_company_identification:
raise ValidationError(_("Originator's Short Name is not filled."))
if not self.currency_id.name:
raise ValidationError(_("Currency Indicator is not filled."))

# Prepare fields for the header
sequence = self.env["ir.sequence"].next_by_code("nacha.file.sequence")
if sequence == "10000":
sequence = "0001"
sequence_id = self.env.ref(
"osi_nacha_80_byte.ir_sequence_file_number",
)
sequence_id.number_next_actual = 2
destination = "{:>5.5}".format(self.journal_id.nacha_immediate_destination)
space1 = "{:>5.5}".format("")
origin = "{:>10.10}".format(self.journal_id.nacha_immediate_origin)
now_in_client_tz = fields.Datetime.context_timestamp(
self, fields.Datetime.now()
)
file_creation_date = "{:6.6}".format(
now_in_client_tz.strftime("%y%m%d")
) # File Creation Date
file_creation_number = str(sequence).zfill(4)
institution_number = "{:>4.4}".format(
self.journal_id.bank_account_id.bank_id.bic
)
branch_transit_number = "{:>5.5}".format(
self.journal_id.bank_account_id.aba_routing
)
account_number = self.journal_id.bank_account_id.acc_number.ljust(11)
space2 = "{:>2.2}".format("")
originator_name = self.journal_id.nacha_company_identification.ljust(15)
currency_indicator = self.currency_id.name.ljust(3)
space3 = "{:>4.4}".format("")

# Combine fields into an 80-character file header
header = (
"1 "
+ destination
+ space1
+ origin
+ file_creation_date
+ file_creation_number
+ " "
+ institution_number
+ branch_transit_number
+ account_number
+ space2
+ originator_name
+ " "
+ currency_indicator
+ space3
).ljust(80)
return header
else:
return super()._generate_nacha_header()

def _generate_nacha_file(self):
if self.journal_id.nacha_file_type == "80_byte":
entries = []
header = self._generate_nacha_header()
batch_nr = 0

entries.append(
self._generate_nacha_batch_header_record(self.date, batch_nr)
)
for date, payments in sorted(
self.payment_ids.grouped("partner_bank_id").items()
):
entries.append(
self._generate_nacha_entry_detail(0, payments, is_offset=False)
)
entries.append(self._generate_nacha_batch_trailer_record())
entries.append(self._generate_nacha_trailer())
return "\r\n".join([header] + entries)
else:
return super()._generate_nacha_file()
14 changes: 14 additions & 0 deletions osi_nacha_80_byte/models/account_journal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright (C) 2024, Open Source Integrators
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from odoo import fields, models


class AccountJournal(models.Model):
_inherit = "account.journal"

nacha_file_type = fields.Selection(
[("80_byte", "80 Byte")],
default="80_byte",
string="File Type",
)
19 changes: 19 additions & 0 deletions osi_nacha_80_byte/views/account_journal_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<odoo>
<record id="view_account_journal_form_inherit_osi_nacha_80" model="ir.ui.view">
<field name="name">account.journal.form.inherit.osi_nacha_80</field>
<field name="model">account.journal</field>
<field name="priority">100</field>
<field name="inherit_id" ref="account.view_account_journal_form" />
<field name="arch" type="xml">
<xpath
expr="//field[@name='outbound_payment_method_line_ids']/tree/field[@name='payment_method_id']"
position="attributes"
>
<attribute name="domain">[]</attribute>
</xpath>
<field name="nacha_immediate_destination" position="before">
<field name="nacha_file_type" />
</field>
</field>
</record>
</odoo>

0 comments on commit 099b971

Please sign in to comment.