Skip to content

Fetch bank transactions#33

Open
harshp4114 wants to merge 8 commits into
resilient-tech:version-15from
harshp4114:fetch-bank-transactions
Open

Fetch bank transactions#33
harshp4114 wants to merge 8 commits into
resilient-tech:version-15from
harshp4114:fetch-bank-transactions

Conversation

@harshp4114
Copy link
Copy Markdown
Member

No description provided.

@harshp4114 harshp4114 marked this pull request as ready for review April 6, 2026 06:38
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 6, 2026

Confidence Score: 3/5

Not safe to merge — three P1 defects will cause runtime errors and broken UX.

Two NameError bugs will crash at runtime when get_element() fails in except handlers; a missing completion event silently leaves the UI in a stale state; listener accumulation causes duplicate UI events on every form load.

bank_integration/bank_integration/api/hdfc_bank_api.py and bank_integration/scripts/bank_reconciliation_tool.js

Important Files Changed

Filename Overview
bank_integration/bank_integration/api/hdfc_bank_api.py Major refactor replacing HTML scraping with CSV download and date-range chunking; introduces two NameError bugs in except blocks and a missing completion event for empty results.
bank_integration/scripts/bank_reconciliation_tool.js Adds date range UI, validation, and realtime listeners; listener accumulation in onload causes duplicate toast messages on repeat form visits.
bank_integration/bank_integration/api/transactions.py Clean addition of optional from_date/to_date parameters to get_transactions; no issues found.
bank_integration/public/js/common.js Minor UX improvement to show OTP verification as a toast alert for Bank Reconciliation Tool and Bank Account forms.
bank_integration/patches.txt Routine patch version counter bump.

Reviews (1): Last reviewed commit: "fix: made changes to support fetch and r..." | Re-trigger Greptile

Comment thread bank_integration/bank_integration/api/hdfc_bank_api.py Outdated
Comment thread bank_integration/scripts/bank_reconciliation_tool.js
Comment thread bank_integration/bank_integration/api/hdfc_bank_api.py
Comment thread bank_integration/bank_integration/api/hdfc_bank_api.py Outdated
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 6, 2026

📝 Walkthrough

Walkthrough

The HDFC bank integration was refactored from HTML/enquiry parsing to a statement-download pipeline that chunks date ranges, automates portal downloads, parses CSV-like statements, and creates Bank Transaction records with deduplicated transaction_id hashes. The get_transactions API now accepts optional from_date and to_date. Client-side OTP feedback and the Bank Reconciliation Tool form were updated with new lifecycle handlers, validators, realtime listeners, and UI behaviors. The package patch version comment was adjusted.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess whether it relates to the changeset. Add a description explaining the changes, such as the shift from HTML parsing to statement download automation, new chunking logic, and UI automation updates.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main feature addition: replacing the old enquiry-table parsing approach with a new statement-download pipeline to fetch bank transactions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (5)
bank_integration/bank_integration/api/transactions.py (1)

14-24: Consider adding date validation for the new parameters.

The from_date and to_date parameters are passed through without validation. While downstream code may handle invalid values, validating at the API boundary provides clearer error messages and prevents unnecessary bank portal interactions.

Optional validation example
 `@frappe.whitelist`()
 def get_transactions(uid, from_account, from_date=None, to_date=None):
+    from frappe.utils import getdate
+    if from_date:
+        from_date = str(getdate(from_date))
+    if to_date:
+        to_date = str(getdate(to_date))
+    if from_date and to_date and from_date > to_date:
+        frappe.throw("From Date cannot be greater than To Date")
     bi = frappe.get_doc("Bank Integration Settings", from_account)
bank_integration/scripts/bank_reconciliation_tool.js (1)

28-34: Setting bank_statement_to_date on every onload may overwrite user's selection.

The onload handler unconditionally sets bank_statement_to_date to yesterday. This will reset any user-selected date when the form is refreshed or reloaded, which may be unexpected.

Only set default if field is empty
         frm.toggle_reqd(["bank_statement_to_date","bank_statement_from_date","bank_account"],1)
         let today = frappe.datetime.get_today();
         let yesterday = frappe.datetime.add_days(today, -1);
-        frm.set_value("bank_statement_to_date",yesterday);
+        if (!frm.doc.bank_statement_to_date) {
+            frm.set_value("bank_statement_to_date", yesterday);
+        }
bank_integration/bank_integration/api/hdfc_bank_api.py (3)

1033-1033: Unused parameter from_date.

The from_date parameter is declared but never used. The actual dates are retrieved from self.data.from_date and self.data.to_date. This parameter should be removed to avoid confusion.

Remove unused parameter
-    def fetch_transactions(self, from_date=None):
+    def fetch_transactions(self):

1212-1258: Consider transaction safety for bulk creation.

The method submits each Bank Transaction individually and commits once at the end. If an error occurs mid-way (e.g., validation failure on one transaction), some transactions will be in the database without being committed, while others won't be created at all.

Consider wrapping in a try-except with frappe.db.rollback() on failure, or using frappe.flags.in_import = True to batch operations.


1101-1138: Remove the unused _process_downloaded_statement method.

The _process_downloaded_statement method at lines 1101–1138 is defined but never called anywhere in the codebase. Remove it unless it's intended for future use, in which case document its purpose.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b18d0670-2454-440e-a132-90259ee01a79

📥 Commits

Reviewing files that changed from the base of the PR and between c8ce623 and 639eefa.

📒 Files selected for processing (5)
  • bank_integration/bank_integration/api/hdfc_bank_api.py
  • bank_integration/bank_integration/api/transactions.py
  • bank_integration/patches.txt
  • bank_integration/public/js/common.js
  • bank_integration/scripts/bank_reconciliation_tool.js

Comment on lines +683 to +694
def _estimate_wait_time(self):
"""Estimate wait time in minutes based on the date range."""
from_date = datetime.strptime(self.data.from_date, "%Y-%m-%d")
to_date = datetime.strptime(self.data.to_date, "%Y-%m-%d")
days = (to_date - from_date).days

if days <= 90:
return 1
elif days <= 365:
return 2
else:
return 5
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing null check for date parameters.

_estimate_wait_time directly parses self.data.from_date and self.data.to_date without checking if they are None. If the API is called without dates, this will raise a TypeError.

Proposed fix to add defensive check
     def _estimate_wait_time(self):
         """Estimate wait time in minutes based on the date range."""
+        if not self.data.from_date or not self.data.to_date:
+            return 2  # default wait time
         from_date = datetime.strptime(self.data.from_date, "%Y-%m-%d")
         to_date = datetime.strptime(self.data.to_date, "%Y-%m-%d")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _estimate_wait_time(self):
"""Estimate wait time in minutes based on the date range."""
from_date = datetime.strptime(self.data.from_date, "%Y-%m-%d")
to_date = datetime.strptime(self.data.to_date, "%Y-%m-%d")
days = (to_date - from_date).days
if days <= 90:
return 1
elif days <= 365:
return 2
else:
return 5
def _estimate_wait_time(self):
"""Estimate wait time in minutes based on the date range."""
if not self.data.from_date or not self.data.to_date:
return 2 # default wait time
from_date = datetime.strptime(self.data.from_date, "%Y-%m-%d")
to_date = datetime.strptime(self.data.to_date, "%Y-%m-%d")
days = (to_date - from_date).days
if days <= 90:
return 1
elif days <= 365:
return 2
else:
return 5

Comment on lines +944 to +954
def _get_date_chunks(self):
"""Break self.data.from_date → self.data.to_date into ≤365-day chunks."""
from_dt = datetime.strptime(self.data.from_date, "%Y-%m-%d")
to_dt = datetime.strptime(self.data.to_date, "%Y-%m-%d")
chunks = []
current = from_dt
while current <= to_dt:
end = min(current + timedelta(days=364), to_dt)
chunks.append((current, end))
current = end + timedelta(days=1)
return chunks
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Same null-check concern as _estimate_wait_time.

This method also parses dates without checking for None. Consider adding defensive validation here or ensuring dates are validated before calling these methods.

Comment thread bank_integration/bank_integration/api/hdfc_bank_api.py
Comment thread bank_integration/bank_integration/api/hdfc_bank_api.py
Comment on lines +1149 to +1152
if not rows:
return []

raw_headers = [h.strip().lower() for h in rows[1]]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential IndexError on malformed CSV files.

If the CSV has only one row (or is empty after parsing), accessing rows[1] on line 1152 will raise IndexError. The check on line 1149 only verifies the list is non-empty.

Proposed fix to add bounds check
         if not rows:
             return []

+        if len(rows) < 2:
+            return []
+
         raw_headers = [h.strip().lower() for h in rows[1]]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if not rows:
return []
raw_headers = [h.strip().lower() for h in rows[1]]
if not rows:
return []
if len(rows) < 2:
return []
raw_headers = [h.strip().lower() for h in rows[1]]

Comment on lines +37 to +44
if(frm.doctype == 'Bank Reconciliation Tool' || frm.doctype == 'Bank Account'){
frappe.show_alert({
message: "Verifying OTP!!",
indicator: "green"
},5)
}else{
frappe.msgprint("Verifying OTP!!")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify how frm object is structured when passed to listenForOtp
rg -n -A5 'bi\.listenForOtp' --type=js

Repository: resilient-tech/bank_integration

Length of output: 3055


🏁 Script executed:

sed -n '1,60p' bank_integration/public/js/common.js | cat -n

Repository: resilient-tech/bank_integration

Length of output: 2306


Use consistent doctype access pattern on line 37.

Line 33 correctly uses frm?.doc?.doctype || frm?.doctype to handle both form objects (where doctype is at frm.doc.doctype) and listview objects (where doctype is at frm.doctype). However, line 37's condition only checks frm.doctype, which is undefined for form objects, causing the condition to always evaluate to false and skip the intended frappe.show_alert behavior. This affects most calls to listenForOtp which pass form objects (from payment_entry.js, bank_reconciliation_tool.js, and bank_integration_settings.js).

Proposed fix to align with the existing pattern
+			const doctype = frm?.doc?.doctype || frm?.doctype;
-			if(frm.doctype == 'Bank Reconciliation Tool' || frm.doctype == 'Bank Account'){
+			if(doctype == 'Bank Reconciliation Tool' || doctype == 'Bank Account'){
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if(frm.doctype == 'Bank Reconciliation Tool' || frm.doctype == 'Bank Account'){
frappe.show_alert({
message: "Verifying OTP!!",
indicator: "green"
},5)
}else{
frappe.msgprint("Verifying OTP!!")
}
const doctype = frm?.doc?.doctype || frm?.doctype;
if(doctype == 'Bank Reconciliation Tool' || doctype == 'Bank Account'){
frappe.show_alert({
message: "Verifying OTP!!",
indicator: "green"
},5)
}else{
frappe.msgprint("Verifying OTP!!")
}

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
bank_integration/scripts/bank_reconciliation_tool.js (1)

30-32: Only default bank_statement_to_date when it's blank.

Reopening the tool currently replaces any previously selected/saved end date with yesterday, which can silently shift the next sync window.

♻️ Proposed fix
         let today = frappe.datetime.get_today();
         let yesterday = frappe.datetime.add_days(today, -1);
-        frm.set_value("bank_statement_to_date",yesterday);
+        if (!frm.doc.bank_statement_to_date) {
+            frm.set_value("bank_statement_to_date", yesterday);
+        }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3443d3fd-7a88-43c9-a32b-3350f727ab1c

📥 Commits

Reviewing files that changed from the base of the PR and between 639eefa and 744ed51.

📒 Files selected for processing (2)
  • bank_integration/bank_integration/api/hdfc_bank_api.py
  • bank_integration/scripts/bank_reconciliation_tool.js

Comment on lines +696 to +698
def _click_statement_download(self, from_date_ui, to_date_ui):
"""Find the statement row matching the date range in the recent downloads
table and click its download button. Returns True if found, False otherwise."""
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't re-request a statement that's already pending.

A matching recent-download row can exist without a download button while HDFC is still generating the file. Returning False here sends _fetch_chunk() down the fresh-download path, so each retry can submit another statement request for the same range.

🐛 Proposed fix
-        """Find the statement row matching the date range in the recent downloads
-        table and click its download button. Returns True if found, False otherwise."""
+        """Find the statement row matching the date range in the recent downloads
+        table and click its download button.
+        Returns True when a download starts, None when a matching row is still
+        pending, and False when no matching row exists."""
@@
-        if not file_download_btn:
-            return False
+        if not file_download_btn:
+            return None
@@
-        if self._click_statement_download(from_date_ui, to_date_ui):
+        download_status = self._click_statement_download(from_date_ui, to_date_ui)
+        if download_status is True:
             filename, content = self.wait_for_download()
             if filename and content:
                 return self._parse_statement_file(content)
             return []
+        elif download_status is None:
+            return None

Also applies to: 764-768, 1006-1041

Comment on lines +1059 to +1067
frappe.publish_realtime(
"show_alert",
{
"message": "Checking statement for {} to {}...".format(
chunk_from.strftime("%d %b %Y"), chunk_to.strftime("%d %b %Y")
)
},
user=frappe.session.user,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "show_alert publishers:"
rg -n -C2 'publish_realtime\("show_alert"' --type=py

echo
echo "show_alert listeners:"
rg -n -C2 'realtime\.on\("show_alert"' --type=js

Repository: resilient-tech/bank_integration

Length of output: 97


🏁 Script executed:

# First, verify the file exists and check the specific lines
file_path="bank_integration/bank_integration/api/hdfc_bank_api.py"
if [ -f "$file_path" ]; then
  echo "=== File exists ==="
  echo "Total lines: $(wc -l < "$file_path")"
  echo ""
  echo "=== Lines 1055-1070 (context around 1059-1067) ==="
  sed -n '1055,1070p' "$file_path"
  echo ""
  echo "=== Lines 1088-1110 (context around 1092-1106) ==="
  sed -n '1088,1110p' "$file_path"
  echo ""
  echo "=== Lines 1182-1195 (context around 1186-1190) ==="
  sed -n '1182,1195p' "$file_path"
else
  echo "File not found at $file_path"
  # Try to find it
  find . -name "hdfc_bank_api.py" -type f 2>/dev/null
fi

Repository: resilient-tech/bank_integration

Length of output: 2231


🏁 Script executed:

# Search for all show_alert occurrences
echo "=== All 'show_alert' occurrences in Python files ==="
rg "show_alert" --type=py -n -B2 -A2

echo ""
echo "=== All 'show_alert' occurrences in JavaScript files ==="
rg "show_alert" --type=js -n -B2 -A2

Repository: resilient-tech/bank_integration

Length of output: 2906


🏁 Script executed:

# Look for sync_transactions for comparison
echo "=== 'sync_transactions' context (mentioned in review as comparison) ==="
rg "sync_transactions" --type=py -n -B2 -A2 | head -50

Repository: resilient-tech/bank_integration

Length of output: 644


🏁 Script executed:

# Search for uid usage in the context of these functions
echo "=== 'uid' usage in bank_reconciliation or hdfc_bank_api ==="
rg "uid" --type=py -n | grep -E "(bank_reconciliation|hdfc_bank)" | head -20

Repository: resilient-tech/bank_integration

Length of output: 828


🏁 Script executed:

# Verify self.uid is available in the context of these methods
echo "=== Methods containing show_alert calls ==="
rg -B 20 'publish_realtime\(\s*"show_alert"' --type=py | grep -E "(def |self\.uid)" | head -20

echo ""
echo "=== Confirm sync_transactions uses uid ==="
sed -n '1076,1082p' bank_integration/bank_integration/api/hdfc_bank_api.py

Repository: resilient-tech/bank_integration

Length of output: 440


Add uid parameter to all show_alert events to prevent progress bleed across concurrent syncs.

The three show_alert publishers lack uid while sync_transactions (line 1080) includes it. The JavaScript listener in bank_reconciliation_tool.js (line 35) doesn't filter by uid, so progress alerts from unrelated syncs can overwrite active form messages.

Affected locations
  • Line 1060: show_alert during chunk processing
  • Line 1093: show_alert for pending statement
  • Line 1187: show_alert for creating bank transactions

Comment on lines +1174 to +1176
txn["reference_number"] = str(txn.get("reference_number", "")).replace(
".0", ""
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Only trim a terminal numeric .0 from reference_number.

replace(".0", "") removes every .0 substring, so a legitimate value like 12.01 becomes 121. That also changes the dedupe hash built in _create_bank_transactions().

🐛 Proposed fix
-            txn["reference_number"] = str(txn.get("reference_number", "")).replace(
-                ".0", ""
-            )
+            reference_number = str(txn.get("reference_number", "")).strip()
+            txn["reference_number"] = (
+                reference_number[:-2]
+                if reference_number.endswith(".0") and reference_number[:-2].isdigit()
+                else reference_number
+            )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
txn["reference_number"] = str(txn.get("reference_number", "")).replace(
".0", ""
)
reference_number = str(txn.get("reference_number", "")).strip()
txn["reference_number"] = (
reference_number[:-2]
if reference_number.endswith(".0") and reference_number[:-2].isdigit()
else reference_number
)

Comment on lines +1191 to +1227
existing_ids = set(
item["transaction_id"]
for item in frappe.get_all(
"Bank Transaction",
filters={"bank_account": bank_account},
fields="transaction_id",
)
)

# Remaining rows are data
for row in rows[1:]:
cells = row.find_all("td")
count = 0
for txn in transactions:
hash_str = "{date}{narration}{debit}{credit}{closing_balance}{reference_number}".format(
**txn
)
transaction_id = hashlib.sha224(hash_str.encode()).hexdigest()

# NOTE: Will not happen right?
if len(cells) != len(headers):
continue # Skip incomplete rows
if transaction_id in existing_ids:
continue

transaction = {
header: (cell.text.strip() or 0) for header, cell in zip(headers, cells)
}
bank_transaction = frappe.get_doc({"doctype": "Bank Transaction"})
bank_transaction.update(
{
"transaction_id": transaction_id,
"date": txn["date"],
"description": txn["narration"],
"withdrawal": txn["debit"],
"deposit": txn["credit"],
"reference_number": txn["reference_number"],
"closing_balance": txn["closing_balance"],
"bank_account": bank_account,
"unallocated_amount": abs(txn["credit"] - txn["debit"]),
}
)
bank_transaction.submit()
count += 1

transactions.append(transaction)
existing_ids.add(transaction_id)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Schema / migrations related to transaction_id uniqueness:"
rg -n -C3 '"fieldname"\s*:\s*"transaction_id"|add_unique|CREATE UNIQUE|unique"\s*:\s*(1|true)' -g '*.json' -g '*.py'

echo
echo "Existing duplicate-insert handling:"
rg -n -C3 'DuplicateEntryError|transaction_id' --type=py

Repository: resilient-tech/bank_integration

Length of output: 6333


🏁 Script executed:

#!/bin/bash

# Find and check the Bank Transaction doctype definition
echo "=== Bank Transaction doctype definition ==="
find . -name "*bank_transaction*" -type f | grep -E '\.(json|py)$'

echo
echo "=== Checking for Bank Transaction JSON doctype ==="
if [ -f "bank_integration/bank_integration/doctype/bank_transaction/bank_transaction.json" ]; then
  rg -n "transaction_id|unique" bank_integration/bank_integration/doctype/bank_transaction/bank_transaction.json
fi

echo
echo "=== Looking for any unique constraint on transaction_id in all JSON files ==="
rg -n '"fieldname"\s*:\s*"transaction_id"' -A 10 -g '*.json'

echo
echo "=== Checking for error handling around submit() in hdfc_bank_api.py ==="
sed -n '1200,1230p' bank_integration/bank_integration/api/hdfc_bank_api.py

Repository: resilient-tech/bank_integration

Length of output: 1478


🏁 Script executed:

#!/bin/bash

echo "=== Search for Bank Transaction doctype definition in Python ==="
rg -n "doctype.*Bank Transaction|\"Bank Transaction\"" --type=py | head -30

echo
echo "=== Check install.py for complete Bank Transaction configuration ==="
rg -n -B5 -A15 '"doctype": "Bank Transaction"' bank_integration/install.py

echo
echo "=== List all files related to bank_transaction ==="
find . -iname "*bank*transaction*" -type f

echo
echo "=== Check for frappe.get_doc doctype calls for Bank Transaction ==="
rg -n "get_doc.*Bank Transaction" --type=py

Repository: resilient-tech/bank_integration

Length of output: 2407


🏁 Script executed:

#!/bin/bash

echo "=== Check if Bank Transaction is a standard Frappe doctype ==="
rg -n "Bank Transaction" bank_integration/ --type=py | head -20

echo
echo "=== Search for any schema/migration/unique constraint files ==="
find . -name "*.py" -type f | xargs grep -l "unique\|constraint" | head -10

echo
echo "=== Check if there are any doctypes folder ==="
find . -type d -name "doctype"

echo
echo "=== Look for all JSON doctype definitions ==="
find . -name "*.json" -type f | xargs grep -l '"doctype"' | head -10

Repository: resilient-tech/bank_integration

Length of output: 1495


🏁 Script executed:

#!/bin/bash

echo "=== List all items in doctype folder ==="
ls -la bank_integration/bank_integration/doctype/

echo
echo "=== Find all JSON files in doctype folder ==="
find bank_integration/bank_integration/doctype/ -name "*.json" -type f

echo
echo "=== Check install.py for complete property setter configuration ==="
cat bank_integration/install.py | head -60

echo
echo "=== Search for permission records in install.py ==="
sed -n '244,280p' bank_integration/install.py

Repository: resilient-tech/bank_integration

Length of output: 2663


🏁 Script executed:

#!/bin/bash

echo "=== Check complete custom_fields definition ==="
rg -n "custom_fields\s*=" -A 50 bank_integration/install.py | head -100

echo
echo "=== Check if Bank Transaction is standard Frappe doctype ==="
rg -n "Bank Transaction" --type=py | grep -E "create|define|doctype" | head -10

echo
echo "=== Check entire hdfc_bank_api.py function for error handling ==="
sed -n '1175,1235p' bank_integration/bank_integration/api/hdfc_bank_api.py

Repository: resilient-tech/bank_integration

Length of output: 4295


🏁 Script executed:

#!/bin/bash

echo "=== Check if Bank Transaction is defined anywhere in custom_fields ==="
rg -n "Bank Transaction" bank_integration/install.py | grep -v doctype_or_docfield

echo
echo "=== Search for any migrations or database setup ==="
find . -name "*.py" | xargs grep -l "migrate\|migration\|alter\|constraint" 2>/dev/null

echo
echo "=== Verify Bank Transaction is standard Frappe doctype by checking if it's imported ==="
rg -n "from frappe" bank_integration/bank_integration/api/hdfc_bank_api.py | head -5

Repository: resilient-tech/bank_integration

Length of output: 510


🏁 Script executed:

#!/bin/bash

echo "=== Confirm Bank Transaction is from Frappe core (not defined locally) ==="
rg -n "class Bank Transaction" --type=py

echo
echo "=== Check entire install.py for Bank Transaction custom field definitions ==="
sed -n '244,260p' bank_integration/install.py

echo
echo "=== Verify submit() could raise DuplicateEntryError ==="
rg -n "DuplicateEntryError" bank_integration/install.py

echo
echo "=== Check if there's any unique constraint setup anywhere ==="
rg -n "unique.*=.*1|unique.*True" bank_integration/install.py

Repository: resilient-tech/bank_integration

Length of output: 705


Move duplicate prevention out of the in-memory snapshot.

existing_ids is only a point-in-time read. Two overlapping syncs can both miss the same hash and both submit the same Bank Transaction before either request updates this set. Add a unique constraint on transaction_id and wrap bank_transaction.submit() with explicit duplicate-insert error handling using frappe.DuplicateEntryError.

Comment on lines 3 to +10
frm.add_custom_button(__("Sync Transactions"), () => {
frappe.confirm(`Sync Transactions for <strong>${frm.doc.bank_account}</strong> ?`, () => {
frm.doc.uid = frappe.utils.get_random(7);
frappe.confirm(`Sync Transactions for <strong>${frm.doc.bank_account}</strong> using HDFC Netbanking Statement download?`, () => {
frm._uid = frappe.utils.get_random(7);
frappe.call({
method: "bank_integration.bank_integration.api.transactions.get_transactions",
args: { uid: frm.doc.uid, from_account: frm.doc.bank_account },
});
frappe.msgprint("Syncing Transactions&ensp; <i class='fa fa-refresh fa-spin'></i>")
frappe.realtime.on("sync_transactions", function (data) {
if (data.uid != frm.doc.uid) return;
frappe.update_msgprint(
`Synced <strong>${data.count}</strong> Transaction${(data.count == 1) ? "" : "s"} from <strong>${data.after_date}</strong>.`
)
if (data.count) {
frm.set_value("bank_statement_closing_balance", data.closing_balance);
}
frm.save();
frm.trigger("make_reconciliation_tool");
args: { uid: frm._uid, from_account: frm.doc.bank_account ,from_date: frm.doc.bank_statement_from_date, to_date: frm.doc.bank_statement_to_date},
});
show_msg("Syncing Transactions&ensp; <i class='fa fa-refresh fa-spin'></i>")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Block sync until the statement range is complete.

toggle_reqd only affects saves; this custom button can still call the API with empty dates. The new HDFC sync path parses both values immediately, so clicking Sync Transactions before both dates are filled will fail server-side.

🐛 Proposed fix
 frm.add_custom_button(__("Sync Transactions"), () => {
+        if (
+            !frm.doc.bank_account ||
+            !frm.doc.bank_statement_from_date ||
+            !frm.doc.bank_statement_to_date
+        ) {
+            frappe.msgprint(
+                __("Please select Bank Account, From Date, and To Date before syncing transactions.")
+            );
+            return;
+        }
         frappe.confirm(`Sync Transactions for <strong>${frm.doc.bank_account}</strong> using HDFC Netbanking Statement download?`, () => {
                 frm._uid = frappe.utils.get_random(7);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant