From 9fcc39b7d2c0fd64c774f4d60009dafcb86fbeb2 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 16 Dec 2024 17:10:40 +0530 Subject: [PATCH 01/83] refactor: enable payment request creation for adhoc payment. --- india_banking/hooks.py | 56 ++++++++++++---------------------------- india_banking/install.py | 19 ++++++++++++++ 2 files changed, 36 insertions(+), 39 deletions(-) create mode 100644 india_banking/install.py diff --git a/india_banking/hooks.py b/india_banking/hooks.py index 9d8b6bb..bf613ed 100644 --- a/india_banking/hooks.py +++ b/india_banking/hooks.py @@ -28,45 +28,23 @@ # page_js = {"page" : "public/js/file.js"} fixtures = [ - { - "dt": "Workflow", - "filters": [ - [ - "name", "in", [ - "Bank Account Approval" - ] - ] - ]}, - { - "dt": "Workflow State", - "filters": [ - [ - "name", "in", [ - "Approved", "Rejected" - ] - ] - ]}, + {"dt": "Workflow", "filters": [["name", "in", ["Bank Account Approval"]]]}, + {"dt": "Workflow State", "filters": [["name", "in", ["Approved", "Rejected"]]]}, { "dt": "Workflow Action Master", - "filters": [ - [ - "name", "in", [ - "Pending", "Approve", "Reject" - ] - ] - ]} + "filters": [["name", "in", ["Pending", "Approve", "Reject"]]], + }, ] doctype_js = { - "Payment Order" : "public/js/payment_order.js", - "Purchase Order" : "public/js/purchase_order.js", + "Payment Order": "public/js/payment_order.js", + "Purchase Order": "public/js/purchase_order.js", "Purchase Invoice": "public/js/purchase_invoice.js", "Payment Type": "public/js/payment_type.js", - "Bank Account": "public/js/bank_account.js" - + "Bank Account": "public/js/bank_account.js", } -doctype_list_js = {"Payment Order" : "public/js/payment_order_list.js"} +doctype_list_js = {"Payment Order": "public/js/payment_order_list.js"} # include js in doctype views # doctype_js = {"doctype" : "public/js/doctype.js"} @@ -110,7 +88,12 @@ # before_install = "india_banking.install.before_install" # after_install = "india_banking.install.after_install" -after_install = "india_banking.india_banking.install.after_install" +after_install = [ + "india_banking.india_banking.install.after_install", + "india_banking.install.after_install", +] + +before_uninstall = "india_banking.uninstall.before_uninstall" # Uninstallation # ------------ @@ -163,7 +146,7 @@ override_doctype_class = { "Payment Order": "india_banking.india_banking.override.payment_order.CustomPaymentOrder", "Payment Entry": "india_banking.india_banking.override.payment_entry.CustomPaymentEntry", - "Bank": "india_banking.india_banking.override.bank.CustomBank" + "Bank": "india_banking.india_banking.override.bank.CustomBank", } @@ -186,7 +169,7 @@ } # accounting_dimension_doctypes = ['Bank Payment Request', 'Payment Order', 'Payment Order Reference', 'Payment Order Summary'] -accounting_dimension_doctypes = ['Bank Payment Request'] +accounting_dimension_doctypes = ["Bank Payment Request"] # Scheduled Tasks # --------------- @@ -209,11 +192,7 @@ # ], # } -scheduler_events = { - "daily": [ - "india_banking.tasks.daily" - ] -} +scheduler_events = {"daily": ["india_banking.tasks.daily"]} # Testing # ------- @@ -295,4 +274,3 @@ # default_log_clearing_doctypes = { # "Logging DocType Name": 30 # days to retain logs # } - diff --git a/india_banking/install.py b/india_banking/install.py new file mode 100644 index 0000000..388a522 --- /dev/null +++ b/india_banking/install.py @@ -0,0 +1,19 @@ +import click +import frappe + + +def after_install(): + toggle_payment_request_creation(True) + + +def before_uninstall(): + toggle_payment_request_creation(False) + + +def toggle_payment_request_creation(allow=True): + click.secho( + "* {} Payment Request Creation...".format("Enabling" if allow else "Disabling") + ) + frappe.db.set_value( + "DocType", "Payment Request", {"in_create": not allow, "track_changes": allow} + ) From 50baba54f1baec57f83df09f9a84e85e42e68fdd Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 16 Dec 2024 17:15:19 +0530 Subject: [PATCH 02/83] refactor: override the payment request's "make_payment_request_function" --- india_banking/hooks.py | 2 +- india_banking/override/__init__.py | 0 india_banking/override/payment_request.py | 6 ++++++ 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 india_banking/override/__init__.py create mode 100644 india_banking/override/payment_request.py diff --git a/india_banking/hooks.py b/india_banking/hooks.py index bf613ed..4dc4fe5 100644 --- a/india_banking/hooks.py +++ b/india_banking/hooks.py @@ -207,7 +207,7 @@ # } override_whitelisted_methods = { - "erpnext.accounts.doctype.payment_request.payment_request.make_payment_request": "india_banking.india_banking.override.payment_request.make_payment_request" + "erpnext.accounts.doctype.payment_request.payment_request.make_payment_request": "india_banking.override.payment_request.make_payment_request" } # diff --git a/india_banking/override/__init__.py b/india_banking/override/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/india_banking/override/payment_request.py b/india_banking/override/payment_request.py new file mode 100644 index 0000000..51b8cef --- /dev/null +++ b/india_banking/override/payment_request.py @@ -0,0 +1,6 @@ +import frappe + + +@frappe.whitelist() +def make_payment_request(): + pass From 4dd718708bb70f32bcc73cbcc45c6820434077c3 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 16 Dec 2024 19:22:05 +0530 Subject: [PATCH 03/83] refactor: remove the overrite payment request. --- india_banking/hooks.py | 4 ---- india_banking/override/payment_request.py | 7 +------ 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/india_banking/hooks.py b/india_banking/hooks.py index 4dc4fe5..7afb649 100644 --- a/india_banking/hooks.py +++ b/india_banking/hooks.py @@ -206,10 +206,6 @@ # "frappe.desk.doctype.event.event.get_events": "india_banking.event.get_events" # } -override_whitelisted_methods = { - "erpnext.accounts.doctype.payment_request.payment_request.make_payment_request": "india_banking.override.payment_request.make_payment_request" -} - # # each overriding function accepts a `data` argument; # generated from the base implementation of the doctype dashboard, diff --git a/india_banking/override/payment_request.py b/india_banking/override/payment_request.py index 51b8cef..daa730a 100644 --- a/india_banking/override/payment_request.py +++ b/india_banking/override/payment_request.py @@ -1,6 +1 @@ -import frappe - - -@frappe.whitelist() -def make_payment_request(): - pass +# import frappe From 7100cc4dc26038ac8fadb7ec8ba1d01f183c6314 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 17 Dec 2024 10:09:26 +0530 Subject: [PATCH 04/83] refactor: Create a custom payment field for both payment orders and requests. --- india_banking/hooks.py | 12 +- india_banking/install.py | 303 ++++++++++++++++++ india_banking/override/payment_request.py | 1 - .../{override => overrides}/__init__.py | 0 india_banking/overrides/payment_request.py | 107 +++++++ 5 files changed, 420 insertions(+), 3 deletions(-) delete mode 100644 india_banking/override/payment_request.py rename india_banking/{override => overrides}/__init__.py (100%) create mode 100644 india_banking/overrides/payment_request.py diff --git a/india_banking/hooks.py b/india_banking/hooks.py index 7afb649..a82d208 100644 --- a/india_banking/hooks.py +++ b/india_banking/hooks.py @@ -95,6 +95,8 @@ before_uninstall = "india_banking.uninstall.before_uninstall" +accounting_dimension_doctypes = ["Payment Order Reference", "Payment Order Summary"] + # Uninstallation # ------------ @@ -147,6 +149,7 @@ "Payment Order": "india_banking.india_banking.override.payment_order.CustomPaymentOrder", "Payment Entry": "india_banking.india_banking.override.payment_entry.CustomPaymentEntry", "Bank": "india_banking.india_banking.override.bank.CustomBank", + "Payment Request": "india_banking.overrides.payment_request.BankPaymentRequest", } @@ -168,8 +171,13 @@ } } -# accounting_dimension_doctypes = ['Bank Payment Request', 'Payment Order', 'Payment Order Reference', 'Payment Order Summary'] -accounting_dimension_doctypes = ["Bank Payment Request"] +accounting_dimension_doctypes = [ + "Bank Payment Request", + "Payment Order", + "Payment Order Reference", + "Payment Order Summary", +] + # Scheduled Tasks # --------------- diff --git a/india_banking/install.py b/india_banking/install.py index 388a522..3260e09 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -1,13 +1,35 @@ import click import frappe +from frappe import make_property_setter +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields +from frappe.custom.doctype.property_setter.property_setter import delete_property_setter def after_install(): toggle_payment_request_creation(True) + make_custom_fields() + create_property_setter() + toggle_reqd_for_reference_in_payment_order(False) def before_uninstall(): + delete_custom_fields() toggle_payment_request_creation(False) + delete_propert_setters() + toggle_reqd_for_reference_in_payment_order(True) + + +def make_custom_fields(): + create_payment_request_custom_fields() + create_payment_custom_fields_in_payment_order() + + +def create_property_setter(): + create_payment_request_property_setter() + + +def delete_propert_setters(): + delete_payment_request_property_setter() def toggle_payment_request_creation(allow=True): @@ -17,3 +39,284 @@ def toggle_payment_request_creation(allow=True): frappe.db.set_value( "DocType", "Payment Request", {"in_create": not allow, "track_changes": allow} ) + + +def create_payment_request_custom_fields(): + click.secho("* Installing Payment and Tax Custom Fields in Payment Request") + fields = { + "Payment Request": [ + { + "label": "Payment Type", + "fieldname": "payment_type", + "fieldtype": "Link", + "options": "Payment Type", + "mandatory_depends_on": "eval:doc.mode_of_payment == 'Wire Transfer' && doc.reference_doctype != 'Purchase Invoice' && doc.payment_request_type == 'Outward'", + "insert_after": "mode_of_payment", + }, + { + "label": "Is Adhoc", + "fieldname": "is_adhoc", + "fieldtype": "Check", + "depends_on": "eval:doc.mode_of_payment == 'Wire Transfer' && doc.payment_request_type == 'Outward'", + "insert_after": "payment_type", + }, + { + "label": "Net Total", + "fieldname": "net_total", + "fieldtype": "Currency", + "reqd": 1, + "insert_after": "transaction_details", + }, + { + "label": "Taxes Deducted", + "fieldname": "taxes_deducted", + "fieldtype": "Currency", + "depends_on": "eval:doc.tax_withholding_category", + "insert_after": "net_total", + "read_only": 1, + }, + { + "label": "Apply Tax Withholding Amount", + "fieldname": "apply_tax_withholding_amount", + "fieldtype": "Check", + "depends_on": "eval:doc.party_type == 'Supplier' && doc.reference_doctype != 'Purchase Invoice' && doc.payment_request_type == 'Outward'", + "insert_after": "currency", + }, + { + "label": "Tax Withholding Category", + "fieldname": "tax_withholding_category", + "fieldtype": "Link", + "options": "Tax Withholding Category", + "depends_on": "eval:doc.apply_tax_withholding_amount", + "insert_after": "apply_tax_withholding_amount", + }, + { + "label": "Payment Term", + "fieldname": "payment_term", + "fieldtype": "Link", + "options": "Payment Term", + "depends_on": "eval:doc.apply_tax_withholding_amount", + "insert_after": "tax_withholding_category", + }, + { + "label": "", + "fieldname": "remark_section", + "fieldtype": "Section Break", + "insert_after": "amended_from", + }, + { + "label": "Remarks", + "fieldname": "remarks", + "fieldtype": "Small Text", + "depends_on": "eval:doc.payment_request_type == 'Outward'", + "insert_after": "remark_section", + }, + ] + } + create_custom_fields(fields) + + +def delete_custom_fields(): + fieldnames = { + "Payment Request": [ + "payment_type", + "is_adhoc", + "net_total", + "taxes_deducted", + "apply_tax_withholding_amount", + "tax_withholding_category", + "payment_term", + ], + "Payment Order": [ + "get_summary", + "payment_summary", + "is_party_wise", + "summary", + "total", + "status", + ], + "Payment Order Reference": [ + "party_type", + "party", + "tax_withholding_category", + "is_adhoc", + "payment_term", + "remarks", + ], + } + + for doctype, fieldnames in fieldnames.items(): + click.secho(f"* Uninstalling Custom Fields from {doctype}") + for fieldname in fieldnames: + frappe.db.delete("Custom Field", {"name": f"{doctype}-" + fieldname}) + + frappe.clear_cache(doctype=doctype) + + +properties = [ + { + "doctype_or_field": "DocField", + "doctype": "Payment Request", + "fieldname": "grand_total", + "property": "read_only", + "property_type": "Check", + "value": 1, + }, + { + "doctype_or_field": "DocField", + "doctype": "Payment Request", + "fieldname": "grand_total", + "property": "reqd", + "property_type": "Check", + "value": 0, + }, +] + + +def create_payment_request_property_setter(): + for _property in properties: + click.echo(f'* Updating {_property.get("doctype", "")} Property') + make_property_setter(_property) + + +def delete_payment_request_property_setter(): + data = [ + ( + _property.get("doctype", ""), + _property.get("property", ""), + _property.get("fieldname", ""), + ) + for _property in properties + ] + for doctype, property, fieldname in data: + click.echo(f"* Updating {doctype} Property") + delete_property_setter(doctype, property, fieldname) + + +def create_payment_custom_fields_in_payment_order(): + click.secho("* Installing Payment Fields in Payment Order") + fields = { + "Payment Order": [ + { + "label": "Get Summary", + "fieldname": "get_summary", + "fieldtype": "Button", + "insert_after": "references", + }, + { + "label": "Payment Summary", + "fieldname": "payment_summary", + "fieldtype": "Section Break", + "insert_after": "get_summary", + }, + { + "label": "Is Party Wise", + "fieldname": "is_party_wise", + "fieldtype": "Check", + "read_only": 1, + "hidden": 1, + "insert_after": "payment_summary", + }, + { + "label": "Summary", + "fieldname": "summary", + "fieldtype": "Table", + "options": "Payment Order Summary", + "insert_after": "is_party_wise", + "no_copy": 1, + }, + { + "label": "Total", + "fieldname": "total", + "fieldtype": "Currency", + "insert_after": "summary", + }, + { + "label": "Status", + "fieldname": "status", + "fieldtype": "Select", + "options": "\nPending\nPending Approval\nPartially Approved\nApproved\nPartially Initiated\nInitiated\nRejected\nFailed", + "read_only": 1, + "insert_after": "posting_date", + }, + ], + "Payment Order Reference": [ + { + "label": "Party Type", + "fieldname": "party_type", + "fieldtype": "Link", + "options": "DocType", + "insert_after": "column_break_4", + }, + { + "label": "Party", + "fieldname": "party", + "fieldtype": "Dynamic Link", + "options": "party_type", + "insert_after": "party_type", + }, + { + "label": "Tax Withholding Category", + "fieldname": "tax_withholding_category", + "fieldtype": "Link", + "options": "Tax Withholding Category", + "depends_on": 'eval:doc.party_type == "Supplier"', + "insert_after": "party", + }, + { + "label": "Is Adhoc", + "fieldname": "is_adhoc", + "fieldtype": "Check", + "insert_after": "tax_withholding_category", + }, + { + "label": "Payment Term", + "fieldname": "payment_term", + "fieldtype": "Link", + "options": "Payment Term", + "insert_after": "amount", + }, + { + "label": "remarks", + "fieldname": "remarks", + "fieldtype": "Small Text", + "insert_after": "payment_term", + }, + { + "label": "Cost Center", + "fieldname": "cost_center", + "fieldtype": "Link", + "options": "Cost Center", + "insert_after": "remarks", + }, + { + "label": "Project", + "fieldname": "project", + "fieldtype": "Link", + "options": "Project", + "insert_after": "cost_center", + }, + ], + } + + create_custom_fields(fields) + + +def toggle_reqd_for_reference_in_payment_order(reqd=False): + frappe.db.set_value( + "DocField", + {"parent": "Payment Order Reference", "fieldname": "reference_doctype"}, + "reqd", + reqd, + ) + frappe.db.set_value( + "DocField", + {"parent": "Payment Order Reference", "fieldname": "reference_name"}, + "reqd", + reqd, + ) + frappe.db.set_value( + "DocField", + {"parent": "Payment Order Reference", "fieldname": "amount"}, + {"reqd": reqd, "read_only": reqd}, + ) diff --git a/india_banking/override/payment_request.py b/india_banking/override/payment_request.py deleted file mode 100644 index daa730a..0000000 --- a/india_banking/override/payment_request.py +++ /dev/null @@ -1 +0,0 @@ -# import frappe diff --git a/india_banking/override/__init__.py b/india_banking/overrides/__init__.py similarity index 100% rename from india_banking/override/__init__.py rename to india_banking/overrides/__init__.py diff --git a/india_banking/overrides/payment_request.py b/india_banking/overrides/payment_request.py new file mode 100644 index 0000000..9e83741 --- /dev/null +++ b/india_banking/overrides/payment_request.py @@ -0,0 +1,107 @@ +import frappe +from erpnext.accounts.doctype.payment_request.payment_request import PaymentRequest +from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( + get_party_tax_withholding_details, +) + + +class BankPaymentRequest(PaymentRequest): + def validate(self): + if not self.net_total: + self.net_total = self.grand_total + if ( + self.apply_tax_withholding_amount + and self.tax_withholding_category + and self.payment_request_type == "Outward" + ): + tds_amount = self.calculate_pr_tds(self.net_total) + + self.taxes_deducted = tds_amount + self.grand_total = self.net_total - self.taxes_deducted + else: + if self.net_total and not self.grand_total: + self.grand_total = self.net_total + if ( + self.grand_total + and self.net_total != self.grand_total + and not self.apply_tax_withholding_amount + ): + self.grand_total = self.net_total + + if not self.is_adhoc: + super().validate() + else: + if self.is_new(): + self.status = "Draft" + + if self.reference_doctype or self.reference_name: + frappe.throw("Payments with references cannot be marked as ad-hoc") + + if self.remarks: + self.remarks = self.remarks[:48] + + self.valdidate_bank_for_wire_transfer() + + def on_submit(self): + if not self.grand_total: + frappe.throw("Amount cannot be zero") + + debit_account = None + if self.payment_type: + debit_account = frappe.db.get_value( + "Payment Type", self.payment_type, "account" + ) + elif self.reference_doctype == "Purchase Invoice": + debit_account = frappe.db.get_value( + self.reference_doctype, self.reference_name, "credit_to" + ) + + if not debit_account: + frappe.throw( + "Debit account for Payment Type {} cannot be determined".format( + self.payment_type or "" + ) + ) + if not self.is_adhoc: + super().on_submit() + else: + if self.payment_request_type == "Outward": + self.db_set("status", "Initiated") + return + + def create_payment_entry(self, submit=True): + payment_entry = super().create_payment_entry(submit=submit) + payment_entry.source_doctype = self.payment_order_type + if payment_entry.docstatus != 1 and self.payment_type: + payment_entry.paid_to = ( + frappe.db.get_value("Payment Type", self.payment_type, "account") or "" + ) + + return payment_entry + + def calculate_pr_tds(self, amount): + doc = self + doc.supplier = self.party + doc.company = self.company + doc.base_tax_withholding_net_total = amount + doc.tax_withholding_net_total = amount + doc.taxes = [] + taxes = get_party_tax_withholding_details(doc, self.tax_withholding_category) + if taxes: + return taxes["tax_amount"] + else: + return 0 + + def valdidate_bank_for_wire_transfer(self): + if self.mode_of_payment == "Wire Transfer" and not self.bank_account: + frappe.throw(frappe._("Bank Account is missing for Wire Transfer Payments")) + + try: + status = frappe.db.get_value( + "Bank Account", self.bank_account, "workflow_state" + ) + + if self.mode_of_payment == "Wire Transfer" and status != "Approved": + frappe.throw("Cannot proceed with un-approved bank account") + except Exception: + frappe.throw("Workflow Not Found for Bank Account") From 6a982d76d58dd0e322ef3125ca1fe61586b65f57 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 17 Dec 2024 11:57:52 +0530 Subject: [PATCH 05/83] refactor: enable create payment request button in purchase order and purchase invoice --- india_banking/hooks.py | 1 + india_banking/overrides/payment_request.py | 1 - india_banking/public/js/payment_request.js | 5 +++++ india_banking/public/js/purchase_invoice.js | 1 - india_banking/public/js/purchase_order.js | 1 - 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/india_banking/hooks.py b/india_banking/hooks.py index a82d208..d15b4fd 100644 --- a/india_banking/hooks.py +++ b/india_banking/hooks.py @@ -42,6 +42,7 @@ "Purchase Invoice": "public/js/purchase_invoice.js", "Payment Type": "public/js/payment_type.js", "Bank Account": "public/js/bank_account.js", + "Payment Request": "public/js/payment_request.js", } doctype_list_js = {"Payment Order": "public/js/payment_order_list.js"} diff --git a/india_banking/overrides/payment_request.py b/india_banking/overrides/payment_request.py index 9e83741..348eae6 100644 --- a/india_banking/overrides/payment_request.py +++ b/india_banking/overrides/payment_request.py @@ -71,7 +71,6 @@ def on_submit(self): def create_payment_entry(self, submit=True): payment_entry = super().create_payment_entry(submit=submit) - payment_entry.source_doctype = self.payment_order_type if payment_entry.docstatus != 1 and self.payment_type: payment_entry.paid_to = ( frappe.db.get_value("Payment Type", self.payment_type, "account") or "" diff --git a/india_banking/public/js/payment_request.js b/india_banking/public/js/payment_request.js index 723bd0a..ae54826 100644 --- a/india_banking/public/js/payment_request.js +++ b/india_banking/public/js/payment_request.js @@ -10,6 +10,11 @@ frappe.ui.form.on('Payment Request', { } }; }); + if(frm.doc.docstatus == 1){ + cur_frm.add_custom_button('Goto Payment Order', function(){ + frappe.set_route('List', 'Payment Order') + }) + } }, company (frm) { frm.set_query("payment_type", function() { diff --git a/india_banking/public/js/purchase_invoice.js b/india_banking/public/js/purchase_invoice.js index 672367f..06ada3f 100644 --- a/india_banking/public/js/purchase_invoice.js +++ b/india_banking/public/js/purchase_invoice.js @@ -10,7 +10,6 @@ frappe.ui.form.on('Purchase Invoice', { ); } setTimeout(() => { - cur_frm.remove_custom_button("Payment Request", "Create") cur_frm.remove_custom_button("Payment", "Create") }, 500); } diff --git a/india_banking/public/js/purchase_order.js b/india_banking/public/js/purchase_order.js index cfa216f..4ab7756 100644 --- a/india_banking/public/js/purchase_order.js +++ b/india_banking/public/js/purchase_order.js @@ -29,7 +29,6 @@ frappe.ui.form.on('Purchase Order', { }, toggle_custom_button(frm) { - cur_frm.remove_custom_button("Payment Request", "Create") cur_frm.remove_custom_button("Payment", "Create") }, }) From 96cb5621445077e22553e5b10daf8eee08aad58b Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 17 Dec 2024 12:05:13 +0530 Subject: [PATCH 06/83] refactor: modify payment_order_type option with journal entries --- india_banking/install.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/india_banking/install.py b/india_banking/install.py index 3260e09..0f71b7d 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -320,3 +320,9 @@ def toggle_reqd_for_reference_in_payment_order(reqd=False): {"parent": "Payment Order Reference", "fieldname": "amount"}, {"reqd": reqd, "read_only": reqd}, ) + frappe.db.set_value( + "DocField", + {"parent": "Payment Order", "fieldname": "payment_order_type"}, + "options", + "\nPayment Request\nPayment Entry\nJournal Entry", + ) From 2c416db304172ce38d835c305cdb5adb5899e4a6 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 17 Dec 2024 12:40:39 +0530 Subject: [PATCH 07/83] refactor: add default bank --- india_banking/default.py | 36 +++++++++++++++++++++++++++++++ india_banking/install.py | 46 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 india_banking/default.py diff --git a/india_banking/default.py b/india_banking/default.py new file mode 100644 index 0000000..a9a9b38 --- /dev/null +++ b/india_banking/default.py @@ -0,0 +1,36 @@ +DEFAULT_MODE_OF_TRANSFERS = [ + { + "mode": "IMPS", + "minimum_limit": 0, + "maximum_limit": 200000, + "start_time": "0:00:00", + "end_time": "23:59:59", + "priority": "1" + }, + { + "mode": "RTGS", + "minimum_limit": 200000, + "maximum_limit": 50000000, + "start_time": "0:00:00", + "end_time": "23:59:59", + "priority": "1" + }, + { + "mode": "NEFT", + "minimum_limit": 0, + "maximum_limit": 100000000000, + "start_time": "0:00:00", + "end_time": "23:59:59", + "priority": "1" + }, + { + "mode": "A2A/FT/Internal", + "minimum_limit": 0, + "maximum_limit": 0, + "start_time": "0:00:00", + "end_time": "23:59:59", + "priority": "1" + } +] + +STD_BANK_LIST = ['Yes Bank', 'HDFC Bank', 'ICICI Bank', 'Axis Bank', 'Kotak Mahindra Bank'] \ No newline at end of file diff --git a/india_banking/install.py b/india_banking/install.py index 0f71b7d..c0ec6d4 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -3,7 +3,7 @@ from frappe import make_property_setter from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.custom.doctype.property_setter.property_setter import delete_property_setter - +from india_banking.default import DEFAULT_MODE_OF_TRANSFERS, STD_BANK_LIST def after_install(): toggle_payment_request_creation(True) @@ -22,6 +22,7 @@ def before_uninstall(): def make_custom_fields(): create_payment_request_custom_fields() create_payment_custom_fields_in_payment_order() + create_bank_doc_custom_fields() def create_property_setter(): @@ -40,6 +41,35 @@ def toggle_payment_request_creation(allow=True): "DocType", "Payment Request", {"in_create": not allow, "track_changes": allow} ) +def create_bank_doc_custom_fields(): + click.secho("* Installing Bank DOC Custom Fields...") + fields = { + "Bank": [ + { + "label": "Standard", + "fieldname": "is_standard", + "fieldtype": "Check", + "read_only": 1, + "insert_after": "bank", + }] + } + + create_custom_fields(fields) + +def create_supplier_custom_fields(): + click.secho("* Installing Supplier Custom Fields...") + fields = { + "Supplier": [ + { + "label": "LEI Number", + "fieldname": "lei_number", + "fieldtype": "Data", + "owner": "Administrator", + "insert_after": "tax_id", + }] + } + + create_custom_fields(fields) def create_payment_request_custom_fields(): click.secho("* Installing Payment and Tax Custom Fields in Payment Request") @@ -143,6 +173,12 @@ def delete_custom_fields(): "payment_term", "remarks", ], + "Supplier": [ + "lei_number" + ], + "Bank": [ + "is_standard" + ] } for doctype, fieldnames in fieldnames.items(): @@ -326,3 +362,11 @@ def toggle_reqd_for_reference_in_payment_order(reqd=False): "options", "\nPayment Request\nPayment Entry\nJournal Entry", ) + +def create_default_bank(): + for bank in STD_BANK_LIST: + if not frappe.db.exists("Bank", bank): + bank_doc = frappe.new_doc('Bank') + bank_doc.bank = bank + bank_doc.is_standard = 1 + bank_doc.save() \ No newline at end of file From 59a753e2a1b3211e9a11d7868a7be35ebf80b392 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 17 Dec 2024 13:12:30 +0530 Subject: [PATCH 08/83] refactor: create default mode of transfer --- india_banking/default.py | 76 +++++++++++++++++++++++----------------- india_banking/install.py | 36 ++++++++++++------- 2 files changed, 67 insertions(+), 45 deletions(-) diff --git a/india_banking/default.py b/india_banking/default.py index a9a9b38..e7b842e 100644 --- a/india_banking/default.py +++ b/india_banking/default.py @@ -1,36 +1,46 @@ DEFAULT_MODE_OF_TRANSFERS = [ - { - "mode": "IMPS", - "minimum_limit": 0, - "maximum_limit": 200000, - "start_time": "0:00:00", - "end_time": "23:59:59", - "priority": "1" - }, - { - "mode": "RTGS", - "minimum_limit": 200000, - "maximum_limit": 50000000, - "start_time": "0:00:00", - "end_time": "23:59:59", - "priority": "1" - }, - { - "mode": "NEFT", - "minimum_limit": 0, - "maximum_limit": 100000000000, - "start_time": "0:00:00", - "end_time": "23:59:59", - "priority": "1" - }, - { - "mode": "A2A/FT/Internal", - "minimum_limit": 0, - "maximum_limit": 0, - "start_time": "0:00:00", - "end_time": "23:59:59", - "priority": "1" - } + { + "mode": "IMPS", + "minimum_limit": 0, + "maximum_limit": 200000, + "start_time": "0:00:00", + "end_time": "23:59:59", + "disabled": 1, + "priority": "1", + }, + { + "mode": "RTGS", + "minimum_limit": 200000, + "maximum_limit": 50000000, + "start_time": "0:00:00", + "end_time": "23:59:59", + "disabled": 1, + "priority": "1", + }, + { + "mode": "NEFT", + "minimum_limit": 0, + "maximum_limit": 100000000000, + "start_time": "0:00:00", + "end_time": "23:59:59", + "disabled": 1, + "priority": "1", + }, + { + "mode": "A2A/FT/Internal", + "minimum_limit": 0, + "maximum_limit": 0, + "start_time": "0:00:00", + "end_time": "23:59:59", + "disabled": 1, + "priority": "1", + }, ] -STD_BANK_LIST = ['Yes Bank', 'HDFC Bank', 'ICICI Bank', 'Axis Bank', 'Kotak Mahindra Bank'] \ No newline at end of file +STD_BANK_LIST = [ + "Yes Bank", + "HDFC Bank", + "ICICI Bank", + "Axis Bank", + "Kotak Mahindra Bank", +] diff --git a/india_banking/install.py b/india_banking/install.py index c0ec6d4..1e36131 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -3,13 +3,16 @@ from frappe import make_property_setter from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.custom.doctype.property_setter.property_setter import delete_property_setter + from india_banking.default import DEFAULT_MODE_OF_TRANSFERS, STD_BANK_LIST + def after_install(): toggle_payment_request_creation(True) make_custom_fields() create_property_setter() toggle_reqd_for_reference_in_payment_order(False) + create_default_mode_of_transfers() def before_uninstall(): @@ -41,6 +44,7 @@ def toggle_payment_request_creation(allow=True): "DocType", "Payment Request", {"in_create": not allow, "track_changes": allow} ) + def create_bank_doc_custom_fields(): click.secho("* Installing Bank DOC Custom Fields...") fields = { @@ -51,11 +55,13 @@ def create_bank_doc_custom_fields(): "fieldtype": "Check", "read_only": 1, "insert_after": "bank", - }] - } + } + ] + } create_custom_fields(fields) + def create_supplier_custom_fields(): click.secho("* Installing Supplier Custom Fields...") fields = { @@ -66,11 +72,13 @@ def create_supplier_custom_fields(): "fieldtype": "Data", "owner": "Administrator", "insert_after": "tax_id", - }] - } + } + ] + } create_custom_fields(fields) + def create_payment_request_custom_fields(): click.secho("* Installing Payment and Tax Custom Fields in Payment Request") fields = { @@ -173,12 +181,8 @@ def delete_custom_fields(): "payment_term", "remarks", ], - "Supplier": [ - "lei_number" - ], - "Bank": [ - "is_standard" - ] + "Supplier": ["lei_number"], + "Bank": ["is_standard"], } for doctype, fieldnames in fieldnames.items(): @@ -363,10 +367,18 @@ def toggle_reqd_for_reference_in_payment_order(reqd=False): "\nPayment Request\nPayment Entry\nJournal Entry", ) + def create_default_bank(): for bank in STD_BANK_LIST: if not frappe.db.exists("Bank", bank): - bank_doc = frappe.new_doc('Bank') + bank_doc = frappe.new_doc("Bank") bank_doc.bank = bank bank_doc.is_standard = 1 - bank_doc.save() \ No newline at end of file + bank_doc.save() + + +def create_default_mode_of_transfers(): + for mot_details in DEFAULT_MODE_OF_TRANSFERS: + if not frappe.db.exists("Mode of Transfer", mot_details.get("mode")): + mot_details.update({"doctype": "Mode of Transfer"}) + frappe.get_doc(mot_details).insert(ignore_permissions=True) From 4cca4f20d557a2aaf1d9ddda86f3ac56a343db55 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 17 Dec 2024 13:19:37 +0530 Subject: [PATCH 09/83] refactor: add default payment type --- india_banking/install.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/india_banking/install.py b/india_banking/install.py index 1e36131..3800f6a 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -13,6 +13,7 @@ def after_install(): create_property_setter() toggle_reqd_for_reference_in_payment_order(False) create_default_mode_of_transfers() + create_default_payment_type() def before_uninstall(): @@ -382,3 +383,10 @@ def create_default_mode_of_transfers(): if not frappe.db.exists("Mode of Transfer", mot_details.get("mode")): mot_details.update({"doctype": "Mode of Transfer"}) frappe.get_doc(mot_details).insert(ignore_permissions=True) + + +def create_default_payment_type(): + if not frappe.db.exists("Payment Type", "Pay"): + frappe.get_doc({"doctype": "Payment Type", "payment_type": "Pay"}).insert( + ignore_permissions=True, ignore_mandatory=True + ) From ab6574fe3a38a54b3d1ba07becb40c3c1ac4a690 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 17 Dec 2024 13:37:08 +0530 Subject: [PATCH 10/83] refactor: Place uninstall hooks in a different file. --- india_banking/hooks.py | 5 +- india_banking/india_banking/default.py | 67 ----------------------- india_banking/india_banking/install.py | 74 -------------------------- india_banking/install.py | 50 ----------------- india_banking/uninstall.py | 73 +++++++++++++++++++++++++ 5 files changed, 74 insertions(+), 195 deletions(-) delete mode 100644 india_banking/india_banking/default.py delete mode 100644 india_banking/india_banking/install.py create mode 100644 india_banking/uninstall.py diff --git a/india_banking/hooks.py b/india_banking/hooks.py index d15b4fd..93b344a 100644 --- a/india_banking/hooks.py +++ b/india_banking/hooks.py @@ -89,10 +89,7 @@ # before_install = "india_banking.install.before_install" # after_install = "india_banking.install.after_install" -after_install = [ - "india_banking.india_banking.install.after_install", - "india_banking.install.after_install", -] +after_install = ["india_banking.install.after_install"] before_uninstall = "india_banking.uninstall.before_uninstall" diff --git a/india_banking/india_banking/default.py b/india_banking/india_banking/default.py deleted file mode 100644 index eeb7e69..0000000 --- a/india_banking/india_banking/default.py +++ /dev/null @@ -1,67 +0,0 @@ -DEFAULT_MODE_OF_TRANSFERS = [ - { - "mode": "IMPS", - "minimum_limit": 0, - "maximum_limit": 200000, - "start_time": "0:00:00", - "end_time": "23:59:59", - "priority": "1" - }, - { - "mode": "RTGS", - "minimum_limit": 200000, - "maximum_limit": 50000000, - "start_time": "0:00:00", - "end_time": "23:59:59", - "priority": "1" - }, - { - "mode": "NEFT", - "minimum_limit": 0, - "maximum_limit": 100000000000, - "start_time": "0:00:00", - "end_time": "23:59:59", - "priority": "1" - }, - { - "mode": "A2A/FT/Internal", - "minimum_limit": 0, - "maximum_limit": 0, - "start_time": "0:00:00", - "end_time": "23:59:59", - "priority": "1" - } -] - -STD_BANK_LIST = [ - { - 'bank_name': 'Yes Bank', - 'swift_number': '', - 'app_name': 'yes_integration_server', - 'is_standard': True - }, - { - 'bank_name': 'HDFC Bank', - 'swift_number': '', - 'app_name': 'hdfc_integration_server', - 'is_standard': True - }, - { - 'bank_name': 'ICICI Bank', - 'swift_number': '', - 'app_name': 'icici_integration_server', - 'is_standard': True - }, - { - 'bank_name': 'Axis Bank', - 'swift_number': '', - 'app_name': 'axis_integration_server', - 'is_standard': True - }, - { - 'bank_name': 'Kotak Mahindra Bank', - 'swift_number': '', - 'app_name': 'kotak_integration_server', - 'is_standard': True - } -] diff --git a/india_banking/india_banking/install.py b/india_banking/india_banking/install.py deleted file mode 100644 index 9fc304d..0000000 --- a/india_banking/india_banking/install.py +++ /dev/null @@ -1,74 +0,0 @@ -import frappe -from india_banking.india_banking.default import DEFAULT_MODE_OF_TRANSFERS, STD_BANK_LIST - -def execute(): - update_payment_order_fields_options() - -def after_install(): - # As a part of the integration, for making ad-hoc payments, we are enabling creation of it. - disable_reqd_for_reference_in_payment_order() - update_payment_order_fields_options() - create_lei_number_field() - create_default_bank() - create_default_mode_of_transfers() - create_default_payment_type() - -def create_default_mode_of_transfers(): - for mot in DEFAULT_MODE_OF_TRANSFERS: - if not frappe.db.exists("Mode of Transfer",mot["mode"]): - frappe.get_doc({ - "doctype": "Mode of Transfer", - "mode": mot["mode"], - "minimum_limit": mot["minimum_limit"], - "maximum_limit": mot["maximum_limit"], - "start_time": mot["start_time"], - "end_time": mot["end_time"], - "priority": mot["priority"] - }).insert(ignore_permissions=True) - -def update_payment_order_fields_options(): - payment_order_type = frappe.db.get_value("DocField", {"parent": "Payment Order", "fieldname": "payment_order_type"}) - frappe.db.set_value( - "DocField", - payment_order_type, - "options", - "\nBank Payment Request\nPayment Request\nPayment Entry\nPayroll Entry\nJournal Entry\n" - ) - -def disable_reqd_for_reference_in_payment_order(): - po_type = frappe.db.get_value("DocField", {"parent": "Payment Order Reference", "fieldname": "reference_doctype"}) - po_doc = frappe.db.get_value("DocField", {"parent": "Payment Order Reference", "fieldname": "reference_name"}) - po_amount = frappe.db.get_value("DocField", {"parent": "Payment Order Reference", "fieldname": "amount"}) - frappe.db.set_value("DocField", po_type, "reqd", 0) - frappe.db.set_value("DocField", po_doc, "reqd", 0) - frappe.db.set_value("DocField", po_amount, "reqd", 0) - frappe.db.set_value("DocField", po_amount, "read_only", 0) - -def create_lei_number_field(): - lei_number_field = frappe.db.get_value("Custom Field", {"dt": "Supplier", "fieldname": "lei_number"}) - if lei_number_field: - return - - from frappe.custom.doctype.custom_field.custom_field import create_custom_field - df = { - "owner":"Administrator", - "label":"LEI Number", - "fieldname":"lei_number", - "insert_after":"tax_id", - "fieldtype":"Data" - } - create_custom_field("Supplier", df) - -def create_default_bank(): - for bank_details in STD_BANK_LIST: - if not frappe.db.exists("Bank", bank_details.get("bank_name")): - bank_doc = frappe.new_doc('Bank') - bank_doc.bank = bank_details.get("bank_name") - bank_doc.save() - -def create_default_payment_type(): - if not frappe.db.exists("Payment Type", "Pay"): - frappe.get_doc({ - "doctype": "Payment Type", - "payment_type": "Pay" - }).insert(ignore_permissions=True, ignore_mandatory=True) \ No newline at end of file diff --git a/india_banking/install.py b/india_banking/install.py index 3800f6a..74b8e6c 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -16,13 +16,6 @@ def after_install(): create_default_payment_type() -def before_uninstall(): - delete_custom_fields() - toggle_payment_request_creation(False) - delete_propert_setters() - toggle_reqd_for_reference_in_payment_order(True) - - def make_custom_fields(): create_payment_request_custom_fields() create_payment_custom_fields_in_payment_order() @@ -33,10 +26,6 @@ def create_property_setter(): create_payment_request_property_setter() -def delete_propert_setters(): - delete_payment_request_property_setter() - - def toggle_payment_request_creation(allow=True): click.secho( "* {} Payment Request Creation...".format("Enabling" if allow else "Disabling") @@ -155,45 +144,6 @@ def create_payment_request_custom_fields(): create_custom_fields(fields) -def delete_custom_fields(): - fieldnames = { - "Payment Request": [ - "payment_type", - "is_adhoc", - "net_total", - "taxes_deducted", - "apply_tax_withholding_amount", - "tax_withholding_category", - "payment_term", - ], - "Payment Order": [ - "get_summary", - "payment_summary", - "is_party_wise", - "summary", - "total", - "status", - ], - "Payment Order Reference": [ - "party_type", - "party", - "tax_withholding_category", - "is_adhoc", - "payment_term", - "remarks", - ], - "Supplier": ["lei_number"], - "Bank": ["is_standard"], - } - - for doctype, fieldnames in fieldnames.items(): - click.secho(f"* Uninstalling Custom Fields from {doctype}") - for fieldname in fieldnames: - frappe.db.delete("Custom Field", {"name": f"{doctype}-" + fieldname}) - - frappe.clear_cache(doctype=doctype) - - properties = [ { "doctype_or_field": "DocField", diff --git a/india_banking/uninstall.py b/india_banking/uninstall.py new file mode 100644 index 0000000..84365b0 --- /dev/null +++ b/india_banking/uninstall.py @@ -0,0 +1,73 @@ +import click +import frappe +from frappe.custom.doctype.property_setter.property_setter import delete_property_setter + +from india_banking.install import ( + properties, + toggle_payment_request_creation, + toggle_reqd_for_reference_in_payment_order, +) + + +def before_uninstall(): + delete_custom_fields() + toggle_payment_request_creation(False) + delete_propert_setters() + toggle_reqd_for_reference_in_payment_order(True) + + +def delete_custom_fields(): + fieldnames = { + "Payment Request": [ + "payment_type", + "is_adhoc", + "net_total", + "taxes_deducted", + "apply_tax_withholding_amount", + "tax_withholding_category", + "payment_term", + ], + "Payment Order": [ + "get_summary", + "payment_summary", + "is_party_wise", + "summary", + "total", + "status", + ], + "Payment Order Reference": [ + "party_type", + "party", + "tax_withholding_category", + "is_adhoc", + "payment_term", + "remarks", + ], + "Supplier": ["lei_number"], + "Bank": ["is_standard"], + } + + for doctype, fieldnames in fieldnames.items(): + click.secho(f"* Uninstalling Custom Fields from {doctype}") + for fieldname in fieldnames: + frappe.db.delete("Custom Field", {"name": f"{doctype}-" + fieldname}) + + frappe.clear_cache(doctype=doctype) + + +def delete_propert_setters(): + delete_payment_request_property_setter() + + +def delete_payment_request_property_setter(): + data = [ + ( + _property.get("doctype", ""), + _property.get("property", ""), + _property.get("fieldname", ""), + ) + for _property in properties + ] + for doctype, property, fieldname in data: + click.echo(f"* Updating {doctype} Property") + delete_property_setter(doctype, property, fieldname) From 053ec3b78af3afa3ff332b4f8566b6f2b562ad00 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 17 Dec 2024 14:00:06 +0530 Subject: [PATCH 11/83] fix: remove payment order copy file refactor: update install file path --- .../india_banking/doc_events/payment_order.py | 771 +++++++++++------ .../doc_events/payment_order_copy.py | 780 ------------------ india_banking/patches.txt | 1 - .../patches/migrate/after_migrate.py | 6 +- 4 files changed, 522 insertions(+), 1036 deletions(-) delete mode 100644 india_banking/india_banking/doc_events/payment_order_copy.py diff --git a/india_banking/india_banking/doc_events/payment_order.py b/india_banking/india_banking/doc_events/payment_order.py index 14dbddb..c504c0a 100644 --- a/india_banking/india_banking/doc_events/payment_order.py +++ b/india_banking/india_banking/doc_events/payment_order.py @@ -1,24 +1,31 @@ -from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions -import frappe -from frappe.utils import nowdate, getdate, now import json +import random +import uuid + +import frappe import frappe.utils +import requests +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_accounting_dimensions, +) +from erpnext.accounts.doctype.payment_entry.payment_entry import get_split_invoice_rows +from frappe.utils import getdate, now, nowdate from frappe.utils.data import comma_and, cstr -import uuid, requests -import random -from india_banking.india_banking.install import STD_BANK_LIST +from india_banking.india_banking.doctype.india_banking_request_log.india_banking_request_log import ( + create_api_log, +) +from india_banking.install import STD_BANK_LIST from india_banking.utils import get_bank_address_details -from india_banking.india_banking.doctype.india_banking_request_log.india_banking_request_log import create_api_log - -from erpnext.accounts.doctype.payment_entry.payment_entry import get_split_invoice_rows @frappe.whitelist() def get_bank_balance(bank_name): bank_doc = frappe.get_doc("Bank Account", bank_name) - bank_connector_exists = frappe.db.exists("Bank Connector", {"company": bank_doc.company, "bank": bank_doc.bank}) + bank_connector_exists = frappe.db.exists( + "Bank Connector", {"company": bank_doc.company, "bank": bank_doc.bank} + ) if not bank_connector_exists: frappe.throw("Bank Connector is not initialized") @@ -39,23 +46,26 @@ def get_bank_balance(bank_name): "Content-Type": "application/json", } - payload = json.dumps({ - "bank_account_number": bank_doc.bank_account_no - }) + payload = json.dumps({"bank_account_number": bank_doc.bank_account_no}) - response = requests.request("POST", url, headers=headers, data= payload) + response = requests.request("POST", url, headers=headers, data=payload) - #create api request log - create_api_log(response, 'Get Bank Balance', "Bank Account", bank_doc.name) + # create api request log + create_api_log(response, "Get Bank Balance", "Bank Account", bank_doc.name) if response.status_code == 200: response = json.loads(response.text) - response_data = frappe._dict((response.get('message') or {})) - if response_data.get('server_status') == "Success": + response_data = frappe._dict((response.get("message") or {})) + if response_data.get("server_status") == "Success": if response_data.balance or response_data.balance == 0: - frappe.db.set_value("Bank Account", bank_doc.name, "bank_balance", response_data.balance) + frappe.db.set_value( + "Bank Account", bank_doc.name, "bank_balance", response_data.balance + ) else: - frappe.msgprint(title= "API Failed", msg="Balance Fetch Failed", indicator='red') + frappe.msgprint( + title="API Failed", msg="Balance Fetch Failed", indicator="red" + ) + @frappe.whitelist() def generate_payment_otp(docname): @@ -65,50 +75,55 @@ def generate_payment_otp(docname): payment_order_doc.reload() # Fetch the connector information - bank_connector_exists = frappe.db.exists("Bank Connector", + bank_connector_exists = frappe.db.exists( + "Bank Connector", { "company": payment_order_doc.company, - "bank_account": payment_order_doc.company_bank_account - } + "bank_account": payment_order_doc.company_bank_account, + }, ) if not bank_connector_exists: frappe.throw("Bank Connector is not initialized") - + bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) payment_payload = frappe._dict() - #payload reference to this payment. - payment_payload['doc'] = payment_order_doc.as_dict(convert_dates_to_str=True) + # payload reference to this payment. + payment_payload["doc"] = payment_order_doc.as_dict(convert_dates_to_str=True) if not payment_order_doc.company_account_number: frappe.throw("Source bank account number is missing") - url = f"{bank_connector.url}/api/method/india_banking_connector.api.connect" - payment_payload.method = 'generate_otp' + payment_payload.method = "generate_otp" payment_payload.bulk_transaction = bank_connector.bulk_transaction api_key = bank_connector.api_key api_secret = bank_connector.get_password("api_secret") headers = { "Authorization": f"token {api_key}:{api_secret}", - "Content-Type": "application/json" + "Content-Type": "application/json", } - response = requests.request("POST", url, headers=headers,data= json.dumps(payment_payload)) + response = requests.request( + "POST", url, headers=headers, data=json.dumps(payment_payload) + ) - #create api response log - create_api_log(response, 'Generate Otp' , payment_order_doc.doctype, payment_order_doc.name) + # create api response log + create_api_log( + response, "Generate Otp", payment_order_doc.doctype, payment_order_doc.name + ) if response.ok: - response_details = response.json().get('message') - if response_details.get('status') == 'success': - frappe.msgprint(response_details.get('message'), alert=1, indicator='green') + response_details = response.json().get("message") + if response_details.get("status") == "success": + frappe.msgprint(response_details.get("message"), alert=1, indicator="green") else: - frappe.msgprint(response_details.get('message'), alert=1, indicator='red') + frappe.msgprint(response_details.get("message"), alert=1, indicator="red") else: - frappe.throw('Invalid Request') + frappe.throw("Invalid Request") + @frappe.whitelist() def make_bank_payment(docname, otp=None): @@ -118,11 +133,12 @@ def make_bank_payment(docname, otp=None): payment_order_doc = frappe.get_doc("Payment Order", docname) # Fetch the connector information - bank_connector_exists = frappe.db.exists("Bank Connector", + bank_connector_exists = frappe.db.exists( + "Bank Connector", { "company": payment_order_doc.company, - "bank_account": payment_order_doc.company_bank_account - } + "bank_account": payment_order_doc.company_bank_account, + }, ) if not bank_connector_exists: @@ -130,23 +146,41 @@ def make_bank_payment(docname, otp=None): bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - if payment_order_doc.company_bank == 'ICICI Bank' and bank_connector.bulk_transaction and not otp: - frappe.throw(title='Invalid OTP', msg='Cannot Initiate Payment without OTP') - - if payment_order_doc.company_bank == 'ICICI Bank'and bank_connector.bulk_transaction: + if ( + payment_order_doc.company_bank == "ICICI Bank" + and bank_connector.bulk_transaction + and not otp + ): + frappe.throw(title="Invalid OTP", msg="Cannot Initiate Payment without OTP") + + if ( + payment_order_doc.company_bank == "ICICI Bank" + and bank_connector.bulk_transaction + ): payment_response = process_bulk_payment(payment_order_doc, otp) - if payment_response.get('status') == 'ACCEPTED': + if payment_response.get("status") == "ACCEPTED": frappe.db.set_value("Payment Order", docname, "status", "Initiated") - frappe.db.set_value("Payment Order", docname, "file_sequence_number", payment_response.get('file_sequence_number')) + frappe.db.set_value( + "Payment Order", + docname, + "file_sequence_number", + payment_response.get("file_sequence_number"), + ) for row in payment_order_doc.summary: - frappe.db.set_value("Payment Order Summary", row.name, "payment_initiated", 1) - frappe.db.set_value("Payment Order Summary", row.name, "payment_status", "Initiated") - frappe.db.set_value("Payment Order Summary", row.name, "payment_date", nowdate()) + frappe.db.set_value( + "Payment Order Summary", row.name, "payment_initiated", 1 + ) + frappe.db.set_value( + "Payment Order Summary", row.name, "payment_status", "Initiated" + ) + frappe.db.set_value( + "Payment Order Summary", row.name, "payment_date", nowdate() + ) - elif payment_response.get('status') == 'Failed': - return {"message": "Failed - "+ cstr(payment_response.get('message'))} + elif payment_response.get("status") == "Failed": + return {"message": "Failed - " + cstr(payment_response.get("message"))} return {"message": "Payment Initiated"} @@ -154,75 +188,103 @@ def make_bank_payment(docname, otp=None): count = 0 for i in payment_order_doc.summary: if not i.payment_initiated and i.payment_status == "Pending": - payment_response = process_payment( - i, payment_order_doc - ) - - if payment_response and "payment_status" in payment_response and payment_response["payment_status"] == "Initiated": - frappe.db.set_value("Payment Order Summary", i.name, "payment_initiated", 1) - frappe.db.set_value("Payment Order Summary", i.name, "payment_status", "Initiated") - frappe.db.set_value("Payment Order Summary", i.name, "payment_date", nowdate()) + payment_response = process_payment(i, payment_order_doc) + + if ( + payment_response + and "payment_status" in payment_response + and payment_response["payment_status"] == "Initiated" + ): + frappe.db.set_value( + "Payment Order Summary", i.name, "payment_initiated", 1 + ) + frappe.db.set_value( + "Payment Order Summary", i.name, "payment_status", "Initiated" + ) + frappe.db.set_value( + "Payment Order Summary", i.name, "payment_date", nowdate() + ) count += 1 - elif payment_response and "payment_status" in payment_response and payment_response["payment_status"] == "": + elif ( + payment_response + and "payment_status" in payment_response + and payment_response["payment_status"] == "" + ): if "message" in payment_response: - frappe.db.set_value("Payment Order Summary", i.name, "message", payment_response["message"]) + frappe.db.set_value( + "Payment Order Summary", + i.name, + "message", + payment_response["message"], + ) else: - frappe.db.set_value("Payment Order Summary", i.name, "payment_status", "Failed") + frappe.db.set_value( + "Payment Order Summary", i.name, "payment_status", "Failed" + ) payment_entry_doc = frappe.get_doc("Payment Entry", i.payment_entry) if payment_entry_doc.docstatus == 1: payment_entry_doc.cancel() - + process_bank_payment_requests(i.name) if payment_response and "message" in payment_response: - frappe.db.set_value("Payment Order Summary", i.name, "message", payment_response["message"]) + frappe.db.set_value( + "Payment Order Summary", + i.name, + "message", + payment_response["message"], + ) payment_order_doc.reload() processed_count = 0 for i in payment_order_doc.summary: if i.payment_initiated: processed_count += 1 - + if processed_count == len(payment_order_doc.summary): frappe.db.set_value("Payment Order", docname, "status", "Initiated") return {"message": f"{count} payments initiated"} + def process_bulk_payment(payment_order_doc, otp): # Fetch the connector information - bank_connector_exists = frappe.db.exists("Bank Connector", + bank_connector_exists = frappe.db.exists( + "Bank Connector", { - "company": payment_order_doc.company, - "bank_account": payment_order_doc.company_bank_account - } + "company": payment_order_doc.company, + "bank_account": payment_order_doc.company_bank_account, + }, ) if not bank_connector_exists: frappe.throw("Bank Connector is not initialized") - + bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - + payment_payload = frappe._dict() - #payment payload. - payment_payload['doc'] = payment_order_doc.as_dict(convert_dates_to_str=True) - payment_payload['doc']['otp'] = otp - payment_payload.method = 'make_payment' + # payment payload. + payment_payload["doc"] = payment_order_doc.as_dict(convert_dates_to_str=True) + payment_payload["doc"]["otp"] = otp + payment_payload.method = "make_payment" payment_payload.bulk_transaction = bank_connector.bulk_transaction payment_account_list = [] - #Lei number validation + # Lei number validation for ref in payment_order_doc.summary: if ref.mode_of_transfer == "RTGS" and ref.amount >= 500000000: lei_number = frappe.db.get_value(ref.party_type, ref.party, "lei_number") - payment_account_list.append(ref.account_name + '-' + lei_number) + payment_account_list.append(ref.account_name + "-" + lei_number) if not lei_number: frappe.throw("LEI Number required for payment > 50 Cr") else: - payment_account_list.append(ref.account_name + '-' + ref.bank_account_no) + payment_account_list.append(ref.account_name + "-" + ref.bank_account_no) - payment_payload['doc']['desc'] = f"Payment to {comma_and(payment_account_list)} via {payment_order_doc.name}" + payment_payload["doc"][ + "desc" + ] = f"Payment to {comma_and(payment_account_list)} via {payment_order_doc.name}" if not payment_order_doc.company_account_number: frappe.throw("Source bank account number is missing") @@ -236,24 +298,30 @@ def process_bulk_payment(payment_order_doc, otp): "Content-Type": "application/json", } - response = requests.request("POST", url, headers=headers, data=json.dumps(payment_payload)) + response = requests.request( + "POST", url, headers=headers, data=json.dumps(payment_payload) + ) - #create api request log - create_api_log(response, 'Make Payment', payment_order_doc.doctype, payment_order_doc.name) + # create api request log + create_api_log( + response, "Make Payment", payment_order_doc.doctype, payment_order_doc.name + ) if response.ok: - payment_details =response.json() - return payment_details.get('message') + payment_details = response.json() + return payment_details.get("message") + + frappe.throw("Invalid payment request") - frappe.throw('Invalid payment request') @frappe.whitelist() def get_bulk_payment_status(payment_order_doc): - bank_connector_exists = frappe.db.exists("Bank Connector", + bank_connector_exists = frappe.db.exists( + "Bank Connector", { "company": payment_order_doc.company, - "bank_account": payment_order_doc.company_bank_account - } + "bank_account": payment_order_doc.company_bank_account, + }, ) if not bank_connector_exists: @@ -263,9 +331,9 @@ def get_bulk_payment_status(payment_order_doc): payment_payload = frappe._dict() - #payload reference to get payment status - payment_payload['doc'] = payment_order_doc.as_dict(convert_dates_to_str=True) - payment_payload.method = 'get_payment_status' + # payload reference to get payment status + payment_payload["doc"] = payment_order_doc.as_dict(convert_dates_to_str=True) + payment_payload.method = "get_payment_status" payment_payload.bulk_transaction = bank_connector.bulk_transaction url = f"{bank_connector.url}/api/method/india_banking_connector.api.connect" @@ -277,65 +345,97 @@ def get_bulk_payment_status(payment_order_doc): "Content-Type": "application/json", } - response = requests.request("POST", url, headers=headers, data=json.dumps(payment_payload)) + response = requests.request( + "POST", url, headers=headers, data=json.dumps(payment_payload) + ) - #create api request log - create_api_log(response, 'Get Payment Status', payment_order_doc.doctype, payment_order_doc.name) + # create api request log + create_api_log( + response, + "Get Payment Status", + payment_order_doc.doctype, + payment_order_doc.name, + ) if response.ok: - response_details = frappe._dict(response.json().get('message', {})) + response_details = frappe._dict(response.json().get("message", {})) payment_status_details = response_details.payment_status_details - if response_details.get('status') == 'Processed': - frappe.msgprint(response_details.get('message'), response_details.get('file_status')) - fs = response_details.get('file_status') - if response_details.get('file_status') in ['FAL', 'REJ', 'REC']: + if response_details.get("status") == "Processed": + frappe.msgprint( + response_details.get("message"), response_details.get("file_status") + ) + fs = response_details.get("file_status") + if response_details.get("file_status") in ["FAL", "REJ", "REC"]: for row in payment_order_doc.summary: - frappe.db.set_value("Payment Order Summary", row.name, - "payment_status", 'Failed' if fs == 'FAL' else 'Rejected' + frappe.db.set_value( + "Payment Order Summary", + row.name, + "payment_status", + "Failed" if fs == "FAL" else "Rejected", + ) + payment_entry_doc = frappe.get_doc( + "Payment Entry", row.payment_entry ) - payment_entry_doc = frappe.get_doc("Payment Entry", row.payment_entry) if payment_entry_doc.docstatus == 1: payment_entry_doc.cancel() process_bank_payment_requests(row.name) if payment_status_details: for row in payment_order_doc.summary: - row_payment_status = frappe._dict(payment_status_details.get(row.name, {})) + row_payment_status = frappe._dict( + payment_status_details.get(row.name, {}) + ) if row.payment_status == "Initiated" and row_payment_status: - if row_payment_status.transaction_status == 'SUC': - frappe.db.set_value("Payment Order Summary", row.name, + if row_payment_status.transaction_status == "SUC": + frappe.db.set_value( + "Payment Order Summary", + row.name, { "reference_number": row_payment_status.host_reference_number, - "payment_status": 'Processed', - "message": row_payment_status.host_response_message - } + "payment_status": "Processed", + "message": row_payment_status.host_response_message, + }, ) - frappe.db.set_value("Payment Entry", row.payment_entry, - "reference_no", row_payment_status.host_reference_number + frappe.db.set_value( + "Payment Entry", + row.payment_entry, + "reference_no", + row_payment_status.host_reference_number, ) - elif row_payment_status.transaction_status == 'FAL': - frappe.db.set_value("Payment Order Summary", row.name, - "payment_status", 'Failed' + elif row_payment_status.transaction_status == "FAL": + frappe.db.set_value( + "Payment Order Summary", + row.name, + "payment_status", + "Failed", + ) + payment_entry_doc = frappe.get_doc( + "Payment Entry", row.payment_entry ) - payment_entry_doc = frappe.get_doc("Payment Entry", row.payment_entry) if payment_entry_doc.docstatus == 1: payment_entry_doc.cancel() process_bank_payment_requests(row.name) - elif row_payment_status.transaction_status in ['RVS', 'REJ']: - frappe.db.set_value("Payment Order Summary", row.name, - "payment_status", 'Rejected' + elif row_payment_status.transaction_status in ["RVS", "REJ"]: + frappe.db.set_value( + "Payment Order Summary", + row.name, + "payment_status", + "Rejected", + ) + payment_entry_doc = frappe.get_doc( + "Payment Entry", row.payment_entry ) - payment_entry_doc = frappe.get_doc("Payment Entry", row.payment_entry) if payment_entry_doc.docstatus == 1: payment_entry_doc.cancel() process_bank_payment_requests(row.name) update_payment_status(payment_order_doc) else: - frappe.throw(msg=response_details.server_message, title='Failed') + frappe.throw(msg=response_details.server_message, title="Failed") else: - frappe.throw('Invalid Request') + frappe.throw("Invalid Request") + def update_payment_status(payment_order_doc): try: @@ -344,50 +444,51 @@ def update_payment_status(payment_order_doc): rejected_count = 0 for ref in payment_order_doc.summary: status = frappe.db.get_value( - "Payment Order Summary", ref.name, - "payment_status" + "Payment Order Summary", ref.name, "payment_status" ) - if status == 'Processed': + if status == "Processed": success_count += 1 - if status == 'Failed': + if status == "Failed": faild_count += 1 - if status == 'Rejected': + if status == "Rejected": rejected_count += 1 if success_count == len(payment_order_doc.summary): - frappe.db.set_value("Payment Order", - payment_order_doc.name, - "status", 'Approved' + frappe.db.set_value( + "Payment Order", payment_order_doc.name, "status", "Approved" ) elif faild_count == len(payment_order_doc.summary): - frappe.db.set_value("Payment Order", - payment_order_doc.name, - "status", 'Failed' + frappe.db.set_value( + "Payment Order", payment_order_doc.name, "status", "Failed" ) elif rejected_count == len(payment_order_doc.summary): - frappe.db.set_value("Payment Order", - payment_order_doc.name, - "status", 'Rejected' + frappe.db.set_value( + "Payment Order", payment_order_doc.name, "status", "Rejected" ) - elif success_count > 1 and success_count + faild_count + rejected_count == len(payment_order_doc.summary): - frappe.db.set_value("Payment Order", - payment_order_doc.name, - "status", 'Partially Approved' + elif success_count > 1 and success_count + faild_count + rejected_count == len( + payment_order_doc.summary + ): + frappe.db.set_value( + "Payment Order", payment_order_doc.name, "status", "Partially Approved" ) except Exception as e: - frappe.log_error(title='Payment Order Status Update Error', message=frappe.get_traceback()) + frappe.log_error( + title="Payment Order Status Update Error", message=frappe.get_traceback() + ) + @frappe.whitelist() def get_payment_status(docname): payment_order_doc = frappe.get_doc("Payment Order", docname) # Fetch the connector information - bank_connector_exists = frappe.db.exists("Bank Connector", + bank_connector_exists = frappe.db.exists( + "Bank Connector", { "company": payment_order_doc.company, - "bank_account": payment_order_doc.company_bank_account - } + "bank_account": payment_order_doc.company_bank_account, + }, ) if not bank_connector_exists: @@ -395,17 +496,23 @@ def get_payment_status(docname): bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - if payment_order_doc.company_bank == 'ICICI Bank' and bank_connector.bulk_transaction: + if ( + payment_order_doc.company_bank == "ICICI Bank" + and bank_connector.bulk_transaction + ): get_bulk_payment_status(payment_order_doc) else: for i in payment_order_doc.summary: if i.payment_status == "Initiated": - get_response(i, payment_order_doc.company_bank_account, payment_order_doc.company) + get_response( + i, payment_order_doc.company_bank_account, payment_order_doc.company + ) payment_order_doc.reload() update_payment_status(payment_order_doc) + @frappe.whitelist() def make_payment_entries(docname): payment_order_doc = frappe.get_doc("Payment Order", docname) @@ -440,75 +547,130 @@ def make_payment_entries(docname): pe.source_doctype = payment_order_doc.payment_order_type for dimension in get_accounting_dimensions(): - pe.update({dimension: payment_order_doc.get(dimension, '')}) + pe.update({dimension: payment_order_doc.get(dimension, "")}) if row.tax_withholding_category: net_total = 0 for reference in payment_order_doc.references: - if reference.party_type == row.party_type and \ - reference.party == row.party and \ - reference.cost_center == row.cost_center and \ - reference.project == row.project and \ - reference.bank_account == row.bank_account and \ - reference.account == row.account and \ - reference.tax_withholding_category == row.tax_withholding_category and \ - reference.reference_doctype == row.reference_doctype: - net_total += frappe.db.get_value("Bank Payment Request", reference.bank_payment_request, "net_total") + if ( + reference.party_type == row.party_type + and reference.party == row.party + and reference.cost_center == row.cost_center + and reference.project == row.project + and reference.bank_account == row.bank_account + and reference.account == row.account + and reference.tax_withholding_category + == row.tax_withholding_category + and reference.reference_doctype == row.reference_doctype + ): + net_total += frappe.db.get_value( + "Bank Payment Request", + reference.bank_payment_request, + "net_total", + ) pe.paid_amount = net_total pe.received_amount = net_total pe.apply_tax_withholding_amount = 1 pe.tax_withholding_category = row.tax_withholding_category for reference in payment_order_doc.references: if not reference.is_adhoc: - filter_condition = ( reference.party_type == row.party_type and reference.party == row.party and reference.cost_center == row.cost_center - and reference.project == row.project and reference.bank_account == row.bank_account and reference.account == row.account - and reference.tax_withholding_category == row.tax_withholding_category and reference.reference_doctype == row.reference_doctype ) + filter_condition = ( + reference.party_type == row.party_type + and reference.party == row.party + and reference.cost_center == row.cost_center + and reference.project == row.project + and reference.bank_account == row.bank_account + and reference.account == row.account + and reference.tax_withholding_category + == row.tax_withholding_category + and reference.reference_doctype == row.reference_doctype + ) if not payment_order_doc.is_party_wise: - filter_condition = filter_condition and (reference.reference_doctype == row.reference_doctype and reference.reference_name == row.reference_name) + filter_condition = filter_condition and ( + reference.reference_doctype == row.reference_doctype + and reference.reference_name == row.reference_name + ) if filter_condition: - reference_amount = frappe.db.get_value("Bank Payment Request", reference.bank_payment_request, "net_total") + reference_amount = frappe.db.get_value( + "Bank Payment Request", + reference.bank_payment_request, + "net_total", + ) payment_term = "" try: - payment_term = frappe.db.get_value("Bank Payment Request", reference.bank_payment_request, "payment_term") + payment_term = frappe.db.get_value( + "Bank Payment Request", + reference.bank_payment_request, + "payment_term", + ) if not payment_term: - if template := frappe.db.get_value(reference.reference_doctype, reference.reference_name, "payment_terms_template"): - splited_invoice_rows = get_split_invoice_rows(frappe._dict( - { - "voucher_no": reference.reference_name - }), - template, - exc_rates= - { - reference.reference_name: frappe.get_doc("Purchase Invoice", reference.reference_name) - } - ) + if template := frappe.db.get_value( + reference.reference_doctype, + reference.reference_name, + "payment_terms_template", + ): + splited_invoice_rows = get_split_invoice_rows( + frappe._dict( + {"voucher_no": reference.reference_name} + ), + template, + exc_rates={ + reference.reference_name: frappe.get_doc( + "Purchase Invoice", reference.reference_name + ) + }, + ) - is_term_applied = frappe.db.get_value("Payment Terms Template", template, "allocate_payment_based_on_payment_terms") + is_term_applied = frappe.db.get_value( + "Payment Terms Template", + template, + "allocate_payment_based_on_payment_terms", + ) if splited_invoice_rows and is_term_applied: term_row = 0 - while reference_amount>0: - term_paid = frappe.get_value("Payment Entry Reference", { - "reference_doctype": reference.reference_doctype, - "reference_name": reference.reference_name, - "payment_term": splited_invoice_rows[term_row].get('payment_term'), - "docstatus": 1 - - }, 'sum(allocated_amount)' - ) or 0 - - per = frappe.db.get_value("Payment Term", splited_invoice_rows[term_row].get('payment_term'), "invoice_portion") / 100 - invoice_amount = frappe.db.get_value(reference.reference_doctype, reference.reference_name, "grand_total") + while reference_amount > 0: + term_paid = ( + frappe.get_value( + "Payment Entry Reference", + { + "reference_doctype": reference.reference_doctype, + "reference_name": reference.reference_name, + "payment_term": splited_invoice_rows[ + term_row + ].get("payment_term"), + "docstatus": 1, + }, + "sum(allocated_amount)", + ) + or 0 + ) + + per = ( + frappe.db.get_value( + "Payment Term", + splited_invoice_rows[term_row].get( + "payment_term" + ), + "invoice_portion", + ) + / 100 + ) + invoice_amount = frappe.db.get_value( + reference.reference_doctype, + reference.reference_name, + "grand_total", + ) to_be_pay = per * invoice_amount if (reference_amount + term_paid) <= to_be_pay: paid_amount = reference_amount reference_amount -= paid_amount else: - paid_amount = (to_be_pay - term_paid) + paid_amount = to_be_pay - term_paid reference_amount -= paid_amount if paid_amount: @@ -519,10 +681,12 @@ def make_payment_entries(docname): "reference_name": reference.reference_name, "total_amount": invoice_amount, "allocated_amount": paid_amount, - "payment_term": splited_invoice_rows[term_row].get('payment_term') + "payment_term": splited_invoice_rows[ + term_row + ].get("payment_term"), }, ) - term_row +=1 + term_row += 1 else: pe.append( "references", @@ -530,7 +694,7 @@ def make_payment_entries(docname): "reference_doctype": reference.reference_doctype, "reference_name": reference.reference_name, "total_amount": reference_amount, - "allocated_amount": reference_amount + "allocated_amount": reference_amount, }, ) else: @@ -540,7 +704,7 @@ def make_payment_entries(docname): "reference_doctype": reference.reference_doctype, "reference_name": reference.reference_name, "total_amount": reference_amount, - "allocated_amount": reference_amount + "allocated_amount": reference_amount, }, ) else: @@ -551,11 +715,13 @@ def make_payment_entries(docname): "reference_name": reference.reference_name, "total_amount": reference_amount, "allocated_amount": reference_amount, - "payment_term": payment_term + "payment_term": payment_term, }, ) except: - frappe.log_error("Error in Payment Terms Template", frappe.get_traceback()) + frappe.log_error( + "Error in Payment Terms Template", frappe.get_traceback() + ) pe.update( { @@ -574,6 +740,7 @@ def make_payment_entries(docname): pe.submit() frappe.db.set_value("Payment Order Summary", row.name, "payment_entry", pe.name) + def group_by_invoices(self): grouped_references = {} if self.references: @@ -586,12 +753,15 @@ def group_by_invoices(self): self.references = list(grouped_references.values()) + def process_payment(payment_info, payment_order_doc): # Fetch the connector information - bank_connector_exists = frappe.db.exists("Bank Connector", { + bank_connector_exists = frappe.db.exists( + "Bank Connector", + { "company": payment_order_doc.company, - "bank_account": payment_order_doc.company_bank_account - } + "bank_account": payment_order_doc.company_bank_account, + }, ) if not bank_connector_exists: @@ -601,9 +771,13 @@ def process_payment(payment_info, payment_order_doc): payment_payload = frappe._dict(payment_info.as_dict(convert_dates_to_str=True)) - party_field_name = "supplier_name" if payment_info.party_type == "Supplier" else "employee_name" + party_field_name = ( + "supplier_name" if payment_info.party_type == "Supplier" else "employee_name" + ) - party_name = frappe.db.get_value(payment_info.party_type, payment_info.party, party_field_name) + party_name = frappe.db.get_value( + payment_info.party_type, payment_info.party, party_field_name + ) payment_payload.party_name = party_name payment_payload.desc = f"Payment to {payment_info.party} via {payment_info.parent}" @@ -611,7 +785,9 @@ def process_payment(payment_info, payment_order_doc): party_address = get_bank_address_details(payment_info.bank_account) bank_link = frappe.utils.get_link_to_form("Bank Account", payment_info.bank_account) if not party_address: - frappe.throw(f"Address not found for the selected bank account {bank_link} at Row #{payment_info.idx}") + frappe.throw( + f"Address not found for the selected bank account {bank_link} at Row #{payment_info.idx}" + ) payment_payload.address = json.dumps(party_address) @@ -628,17 +804,20 @@ def process_payment(payment_info, payment_order_doc): "Authorization": f"token {api_key}:{api_secret}", "Content-Type": "application/json", } - payment_payload.method = 'intiate_payment' - + payment_payload.method = "intiate_payment" - response = requests.request("POST", url, headers=headers, data=json.dumps(payment_payload)) + response = requests.request( + "POST", url, headers=headers, data=json.dumps(payment_payload) + ) - #create api request log - create_api_log(response, 'Make Payment', payment_info.parenttype, payment_info.parent) + # create api request log + create_api_log( + response, "Make Payment", payment_info.parenttype, payment_info.parent + ) if response.status_code == 200: response = json.loads(response.text) - response_data = frappe._dict((response.get('message') or {})) + response_data = frappe._dict((response.get("message") or {})) if not response_data.status: return {"payment_status": "", "message": str(response)} @@ -654,33 +833,64 @@ def process_payment(payment_info, payment_order_doc): else: return {"payment_status": "", "message": ""} + def get_bank_info(bank_name): for bank in STD_BANK_LIST: - if bank['bank_name'] == bank_name: + if bank["bank_name"] == bank_name: return bank return {} + def notify_party(payment_info, response_data): - if not frappe.get_value("India Banking Settings", "India Banking Settings", "notify_party"): + if not frappe.get_value( + "India Banking Settings", "India Banking Settings", "notify_party" + ): return if payment_info.payment_entry: - default_email_format= frappe.get_single("India Banking Settings").default_email_format or "Payment Advice" + default_email_format = ( + frappe.get_single("India Banking Settings").default_email_format + or "Payment Advice" + ) if default_email_format: try: - payment_entry = frappe.get_doc("Payment Entry", payment_info.payment_entry) + payment_entry = frappe.get_doc( + "Payment Entry", payment_info.payment_entry + ) frappe.sendmail( - recipients=[payment_info.email or frappe.db.get_value('Bank Account', payment_info.bank_account, "email")], - subject="Payment Notification", - message="Payment for {0} is completed. Please check the attachment for details".format(payment_info.party), - attachments=[{"fname": "payment_details.pdf", "fcontent": frappe.get_print("Payment Entry", payment_entry.name, default_email_format, as_pdf=True)}] - ) + recipients=[ + payment_info.email + or frappe.db.get_value( + "Bank Account", payment_info.bank_account, "email" + ) + ], + subject="Payment Notification", + message="Payment for {0} is completed. Please check the attachment for details".format( + payment_info.party + ), + attachments=[ + { + "fname": "payment_details.pdf", + "fcontent": frappe.get_print( + "Payment Entry", + payment_entry.name, + default_email_format, + as_pdf=True, + ), + } + ], + ) except Exception as e: - frappe.log_error("Payment Email Notification Failed", frappe.get_traceback()) + frappe.log_error( + "Payment Email Notification Failed", frappe.get_traceback() + ) + def get_response(payment_info, company_bank_account, company): payment_order_doc = frappe.get_doc("Payment Order", payment_info.parent) - bank_connector_exists = frappe.db.exists("Bank Connector", {"company": company, "bank_account": company_bank_account}) + bank_connector_exists = frappe.db.exists( + "Bank Connector", {"company": company, "bank_account": company_bank_account} + ) if not bank_connector_exists: frappe.throw("Bank Connector is not initialized") @@ -699,96 +909,148 @@ def get_response(payment_info, company_bank_account, company): payment_info_payload = frappe._dict(payment_info.as_dict(convert_dates_to_str=True)) payment_info_payload.doc = payment_order_doc.as_dict(convert_dates_to_str=True) - payment_info_payload.method = 'get_payment_status' + payment_info_payload.method = "get_payment_status" - response = requests.request("POST", url, headers=headers, data=json.dumps(payment_info_payload)) + response = requests.request( + "POST", url, headers=headers, data=json.dumps(payment_info_payload) + ) - #create api request log - create_api_log(response, 'Get Payment Status', payment_info.parenttype, payment_info.parent) + # create api request log + create_api_log( + response, "Get Payment Status", payment_info.parenttype, payment_info.parent + ) if response.status_code == 200: response = json.loads(response.text) - response_data = frappe._dict((response.get('message') or {})) + response_data = frappe._dict((response.get("message") or {})) if response_data: if response_data.status == "Processed": if response_data.utr_number: - frappe.db.set_value("Payment Order Summary", payment_info.name, "reference_number", response_data.utr_number) + frappe.db.set_value( + "Payment Order Summary", + payment_info.name, + "reference_number", + response_data.utr_number, + ) if payment_info.payment_entry: - frappe.db.set_value("Payment Entry", payment_info.payment_entry, "reference_no", response_data.utr_number) + frappe.db.set_value( + "Payment Entry", + payment_info.payment_entry, + "reference_no", + response_data.utr_number, + ) if payment_info.journal_entry_account: - frappe.db.set_value("Journal Entry Account", + frappe.db.set_value( + "Journal Entry Account", payment_info.journal_entry_account, { "payment_status": "Paid", - "reference_number": response_data.utr_number - } + "reference_number": response_data.utr_number, + }, ) notify_party(payment_info, response_data) - frappe.db.set_value("Payment Order Summary", payment_info.name, "payment_status", "Processed") + frappe.db.set_value( + "Payment Order Summary", + payment_info.name, + "payment_status", + "Processed", + ) elif response_data.status == "Pending": - frappe.db.set_value("Payment Order Summary", payment_info.name, "message", response_data.message) - + frappe.db.set_value( + "Payment Order Summary", + payment_info.name, + "message", + response_data.message, + ) + elif response_data.status == "Failed": if payment_info.journal_entry_account: - frappe.db.set_value("Journal Entry Account", payment_info.journal_entry_account , "payment_status", "Failed") + frappe.db.set_value( + "Journal Entry Account", + payment_info.journal_entry_account, + "payment_status", + "Failed", + ) - frappe.db.set_value("Payment Order Summary", + frappe.db.set_value( + "Payment Order Summary", payment_info.name, { "payment_status": response_data.status, - "message": response_data.message - } + "message": response_data.message, + }, ) if payment_info.payment_entry: - payment_entry_doc = frappe.get_doc("Payment Entry", payment_info.payment_entry) + payment_entry_doc = frappe.get_doc( + "Payment Entry", payment_info.payment_entry + ) if payment_entry_doc.docstatus == 1: payment_entry_doc.cancel() process_bank_payment_requests(payment_info.name) - + elif response_data.status == "Rejected": if payment_info.journal_entry_account: - frappe.db.set_value("Journal Entry Account", payment_info.journal_entry_account , "payment_status", "Failed") + frappe.db.set_value( + "Journal Entry Account", + payment_info.journal_entry_account, + "payment_status", + "Failed", + ) - frappe.db.set_value("Payment Order Summary", + frappe.db.set_value( + "Payment Order Summary", payment_info.name, { "payment_status": response_data.status, - "message": response_data.message - } + "message": response_data.message, + }, ) if payment_info.payment_entry: - payment_entry_doc = frappe.get_doc("Payment Entry", payment_info.payment_entry) + payment_entry_doc = frappe.get_doc( + "Payment Entry", payment_info.payment_entry + ) if payment_entry_doc.docstatus == 1: payment_entry_doc.cancel() process_bank_payment_requests(payment_info.name) + def process_bank_payment_requests(payment_order_summary): pos = frappe.get_doc("Payment Order Summary", payment_order_summary) payment_order_doc = frappe.get_doc("Payment Order", pos.parent) key = ( - pos.party_type, pos.party, pos.bank_account, pos.account, - pos.cost_center, pos.project, pos.tax_withholding_category, - pos.reference_doctype + pos.party_type, + pos.party, + pos.bank_account, + pos.account, + pos.cost_center, + pos.project, + pos.tax_withholding_category, + pos.reference_doctype, ) failed_prs = [] for ref in payment_order_doc.references: ref_key = ( - ref.party_type, ref.party, ref.bank_account, ref.account, - ref.cost_center, ref.project, ref.tax_withholding_category, - ref.reference_doctype + ref.party_type, + ref.party, + ref.bank_account, + ref.account, + ref.cost_center, + ref.project, + ref.tax_withholding_category, + ref.reference_doctype, ) if key == ref_key: failed_prs.append(ref.bank_payment_request) - + for pr in failed_prs: pr_doc = frappe.get_doc("Bank Payment Request", pr) if pr_doc.docstatus == 1: @@ -796,19 +1058,24 @@ def process_bank_payment_requests(payment_order_summary): pr_doc.set_as_cancelled() pr_doc.db_set("docstatus", 2) + def get_refrence_number_for_bank_entry(payment_info): - ref_name = frappe.db.sql(f""" - SELECT + ref_name = frappe.db.sql( + f""" + SELECT je.name, jea.name, FROM `tabJournal Entry`je - JOIN + JOIN `tabJournal Entry Account`jea ON - je.name = jea.parent + je.name = jea.parent WHERE - je.docstatus != 2 AND jea.reference_type = 'Payroll Entry' AND jea.reference_name = '{payment_info.payroll_entry}' AND + je.docstatus != 2 AND jea.reference_type = 'Payroll Entry' AND jea.reference_name = '{payment_info.payroll_entry}' AND je.voucher_type = 'Bank Entry' AND jea.party_type = '{payment_info.party_type}' AND jea.party = '{payment_info.party}' LIMIT 1 - """, as_dict= 1, debug=1) - return ref_name \ No newline at end of file + """, + as_dict=1, + debug=1, + ) + return ref_name diff --git a/india_banking/india_banking/doc_events/payment_order_copy.py b/india_banking/india_banking/doc_events/payment_order_copy.py deleted file mode 100644 index 0cc082d..0000000 --- a/india_banking/india_banking/doc_events/payment_order_copy.py +++ /dev/null @@ -1,780 +0,0 @@ -from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions -import frappe -from frappe.utils import nowdate, getdate, now -import json -import frappe.utils -from frappe.utils.data import comma_and, cstr -import uuid, requests -import random - -from india_banking.india_banking.install import STD_BANK_LIST -from india_banking.india_banking.doctype.india_banking_request_log.india_banking_request_log import create_api_log - -from erpnext.accounts.doctype.payment_entry.payment_entry import get_split_invoice_rows - - -@frappe.whitelist() -def get_bank_balance(bank_name): - bank_doc = frappe.get_doc("Bank Account", bank_name) - - bank_connector_exists = frappe.db.exists("Bank Connector", {"company": bank_doc.company, "bank": bank_doc.bank}) - - if not bank_connector_exists: - frappe.throw("Bank Connector is not initialized") - - bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - - app_name = frappe._dict(get_bank_info(bank_doc.bank)).app_name - - if bank_connector.bank == "ICICI Bank" and not bank_connector.bulk_transaction: - app_name += "_composite" - - url = f"{bank_connector.url}/api/method/{app_name}.{app_name}.doctype.bank_request_log.bank_request_log.get_bank_balance" - - api_key = bank_connector.api_key - api_secret = bank_connector.get_password("api_secret") - headers = { - "Authorization": f"token {api_key}:{api_secret}", - "Content-Type": "application/json", - } - - payload = json.dumps({ - "bank_account_number": bank_doc.bank_account_no - }) - - response = requests.request("POST", url, headers=headers, data= payload) - - #create api request log - create_api_log(response, 'Get Bank Balance', "Bank Account", bank_doc.name) - - if response.status_code == 200: - response = json.loads(response.text) - response_data = frappe._dict((response.get('message') or {})) - if response_data.get('server_status') == "Success": - if response_data.balance or response_data.balance == 0: - frappe.db.set_value("Bank Account", bank_doc.name, "bank_balance", response_data.balance) - else: - frappe.msgprint(title= "API Failed", msg="Balance Fetch Failed", indicator='red') - -@frappe.whitelist() -def generate_payment_otp(docname): - payment_order_doc = frappe.get_doc("Payment Order", docname) - payment_order_doc.update_unique_and_file_reference_id(save=True) - - payment_order_doc.reload() - - # Fetch the connector information - bank_connector_exists = frappe.db.exists("Bank Connector", - { - "company": payment_order_doc.company, - "bank_account": payment_order_doc.company_bank_account - } - ) - - if not bank_connector_exists: - frappe.throw("Bank Connector is not initialized") - - bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - - payment_payload = {} - - #payload reference to this payment. - payment_payload['doc'] = payment_order_doc.as_dict(convert_dates_to_str=True) - - if not payment_order_doc.company_account_number: - frappe.throw("Source bank account number is missing") - - app_name = frappe._dict(get_bank_info(payment_order_doc.company_bank)).app_name - - url = f"{bank_connector.url}/api/method/{app_name}.{app_name}.doctype.bank_request_log.bank_request_log.generate_otp" - - api_key = bank_connector.api_key - api_secret = bank_connector.get_password("api_secret") - headers = { - "Authorization": f"token {api_key}:{api_secret}", - "Content-Type": "application/json" - } - response = requests.request("POST", url, headers=headers,data= json.dumps({'payload': payment_payload})) - - #create api response log - create_api_log(response, 'Generate Otp' , payment_order_doc.doctype, payment_order_doc.name) - - if response.ok: - response_details = response.json().get('message') - if response_details.get('server_status') == 'success': - frappe.msgprint(response_details.get('server_message'), alert=1, indicator='green') - else: - frappe.msgprint(response_details.get('server_message'), alert=1, indicator='red') - else: - frappe.throw('Invalid Request') - -@frappe.whitelist() -def make_bank_payment(docname, otp=None): - if not frappe.has_permission("Payment Order", "write"): - frappe.throw("Not permitted", frappe.PermissionError) - - payment_order_doc = frappe.get_doc("Payment Order", docname) - - # Fetch the connector information - bank_connector_exists = frappe.db.exists("Bank Connector", - { - "company": payment_order_doc.company, - "bank_account": payment_order_doc.company_bank_account - } - ) - - if not bank_connector_exists: - frappe.throw("Bank Connector is not initialized") - - bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - - if payment_order_doc.company_bank == 'ICICI Bank' and bank_connector.bulk_transaction and not otp: - frappe.throw(title='Invalid OTP', msg='Cannot Initiate Payment without OTP') - - if payment_order_doc.company_bank == 'ICICI Bank'and bank_connector.bulk_transaction: - payment_response = process_bulk_payment(payment_order_doc, otp) - - if payment_response.get('server_status') == 'success': - frappe.db.set_value("Payment Order", docname, "status", "Initiated") - frappe.db.set_value("Payment Order", docname, "file_sequence_number", payment_response.get('file_sequence_number')) - - for row in payment_order_doc.summary: - frappe.db.set_value("Payment Order Summary", row.name, "payment_initiated", 1) - frappe.db.set_value("Payment Order Summary", row.name, "payment_status", "Initiated") - frappe.db.set_value("Payment Order Summary", row.name, "payment_date", nowdate()) - - if payment_response.get('server_status') == 'failed': - return {"message": "Failed - "+ cstr(payment_response.get('server_message'))} - - return {"message": "Payment Initiated"} - - else: - count = 0 - for i in payment_order_doc.summary: - if not i.payment_initiated and i.payment_status == "Pending": - payment_response = process_payment( - i, payment_order_doc - ) - - if payment_response and "payment_status" in payment_response and payment_response["payment_status"] == "Initiated": - frappe.db.set_value("Payment Order Summary", i.name, "payment_initiated", 1) - frappe.db.set_value("Payment Order Summary", i.name, "payment_status", "Initiated") - frappe.db.set_value("Payment Order Summary", i.name, "payment_date", nowdate()) - count += 1 - elif payment_response and "payment_status" in payment_response and payment_response["payment_status"] == "": - if "message" in payment_response: - frappe.db.set_value("Payment Order Summary", i.name, "message", payment_response["message"]) - else: - frappe.db.set_value("Payment Order Summary", i.name, "payment_status", "Failed") - payment_entry_doc = frappe.get_doc("Payment Entry", i.payment_entry) - if payment_entry_doc.docstatus == 1: - payment_entry_doc.cancel() - - process_bank_payment_requests(i.name) - - if payment_response and "message" in payment_response: - frappe.db.set_value("Payment Order Summary", i.name, "message", payment_response["message"]) - - payment_order_doc.reload() - processed_count = 0 - for i in payment_order_doc.summary: - if i.payment_initiated: - processed_count += 1 - - if processed_count == len(payment_order_doc.summary): - frappe.db.set_value("Payment Order", docname, "status", "Initiated") - - return {"message": f"{count} payments initiated"} - -def process_bulk_payment(payment_order_doc, otp): - # Fetch the connector information - bank_connector_exists = frappe.db.exists("Bank Connector", - { - "company": payment_order_doc.company, - "bank_account": payment_order_doc.company_bank_account - } - ) - - if not bank_connector_exists: - frappe.throw("Bank Connector is not initialized") - - bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - - payment_payload = {} - - #payment payload. - payment_payload['doc'] = payment_order_doc.as_dict(convert_dates_to_str=True) - payment_payload['otp'] = otp - - payment_account_list = [] - - #Lei number validation - for ref in payment_order_doc.summary: - if ref.mode_of_transfer == "RTGS" and ref.amount >= 500000000: - lei_number = frappe.db.get_value(ref.party_type, ref.party, "lei_number") - payment_account_list.append(ref.account_name + '-' + lei_number) - if not lei_number: - frappe.throw("LEI Number required for payment > 50 Cr") - else: - payment_account_list.append(ref.account_name + '-' + ref.bank_account_no) - - payment_payload['desc'] = f"Payment to {comma_and(payment_account_list)} via {payment_order_doc.name}" - - if not payment_order_doc.company_account_number: - frappe.throw("Source bank account number is missing") - - app_name = frappe._dict(get_bank_info(payment_order_doc.company_bank)).app_name - - if bank_connector.bank == "ICICI Bank" and not bank_connector.bulk_transaction: - app_name += "_composite" - - url = f"{bank_connector.url}/api/method/{app_name}.{app_name}.doctype.bank_request_log.bank_request_log.make_payment" - - api_key = bank_connector.api_key - api_secret = bank_connector.get_password("api_secret") - headers = { - "Authorization": f"token {api_key}:{api_secret}", - "Content-Type": "application/json", - } - - response = requests.request("POST", url, headers=headers, data=json.dumps({"payload": payment_payload})) - - #create api request log - create_api_log(response, 'Make Payment', payment_order_doc.doctype, payment_order_doc.name) - - if response.ok: - payment_details =response.json() - return payment_details.get('message') - - frappe.throw('Invalid payment request') - -@frappe.whitelist() -def get_bulk_payment_status(payment_order_doc): - bank_connector_exists = frappe.db.exists("Bank Connector", - { - "company": payment_order_doc.company, - "bank_account": payment_order_doc.company_bank_account - } - ) - - if not bank_connector_exists: - frappe.throw("Bank Connector is not initialized") - - bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - - payment_payload = {} - - #payload reference to get payment status - payment_payload['doc'] = payment_order_doc.as_dict(convert_dates_to_str=True) - - app_name = frappe._dict(get_bank_info(payment_order_doc.company_bank)).app_name - - if bank_connector.bank == "ICICI Bank" and not bank_connector.bulk_transaction: - app_name += "_composite" - - url = f"{bank_connector.url}/api/method/{app_name}.{app_name}.doctype.bank_request_log.bank_request_log.get_payment_status" - - api_key = bank_connector.api_key - api_secret = bank_connector.get_password("api_secret") - headers = { - "Authorization": f"token {api_key}:{api_secret}", - "Content-Type": "application/json", - } - - response = requests.request("POST", url, headers=headers, data=json.dumps({'payload': payment_payload})) - - #create api request log - create_api_log(response, 'Get Payment Status', payment_order_doc.doctype, payment_order_doc.name) - - if response.ok: - response_details = frappe._dict(response.json().get('message', {})) - payment_details = response_details.payment_status - if response_details.get('server_status') == 'success': - frappe.msgprint(response_details.get('server_message'), response_details.get('file_status')) - fs = response_details.get('file_status') - if response_details.get('file_status') in ['FAL', 'REJ', 'REC']: - for row in payment_order_doc.summary: - frappe.db.set_value("Payment Order Summary", row.name, - "payment_status", 'Failed' if fs == 'FAL' else 'Rejected' - ) - payment_entry_doc = frappe.get_doc("Payment Entry", row.payment_entry) - if payment_entry_doc.docstatus == 1: - payment_entry_doc.cancel() - process_bank_payment_requests(row.name) - - if payment_details: - for row in payment_order_doc.summary: - row_payment_status = frappe._dict(payment_details.get(row.name, {})) - if row.payment_status == "Initiated" and row_payment_status: - if row_payment_status.transaction_status == 'SUC': - frappe.db.set_value("Payment Order Summary", row.name, - { - "reference_number": row_payment_status.host_reference_number, - "payment_status": 'Processed', - "message": row_payment_status.host_response_message - } - ) - frappe.db.set_value("Payment Entry", row.payment_entry, - "reference_no", row_payment_status.host_reference_number - ) - elif row_payment_status.transaction_status == 'FAL': - frappe.db.set_value("Payment Order Summary", row.name, - "payment_status", 'Failed' - ) - payment_entry_doc = frappe.get_doc("Payment Entry", row.payment_entry) - if payment_entry_doc.docstatus == 1: - payment_entry_doc.cancel() - process_bank_payment_requests(row.name) - - elif row_payment_status.transaction_status in ['RVS', 'REJ']: - frappe.db.set_value("Payment Order Summary", row.name, - "payment_status", 'Rejected' - ) - payment_entry_doc = frappe.get_doc("Payment Entry", row.payment_entry) - if payment_entry_doc.docstatus == 1: - payment_entry_doc.cancel() - process_bank_payment_requests(row.name) - - update_payment_status(payment_order_doc) - else: - frappe.throw(msg=response_details.server_message, title='Failed') - else: - frappe.throw('Invalid Request') - -def update_payment_status(payment_order_doc): - try: - success_count = 0 - faild_count = 0 - rejected_count = 0 - for ref in payment_order_doc.summary: - status = frappe.db.get_value( - "Payment Order Summary", ref.name, - "payment_status" - ) - if status == 'Processed': - success_count += 1 - if status == 'Failed': - faild_count += 1 - if status == 'Rejected': - rejected_count += 1 - - if success_count == len(payment_order_doc.summary): - frappe.db.set_value("Payment Order", - payment_order_doc.name, - "status", 'Approved' - ) - - elif faild_count == len(payment_order_doc.summary): - frappe.db.set_value("Payment Order", - payment_order_doc.name, - "status", 'Failed' - ) - elif rejected_count == len(payment_order_doc.summary): - frappe.db.set_value("Payment Order", - payment_order_doc.name, - "status", 'Rejected' - ) - elif success_count > 1 and success_count + faild_count + rejected_count == len(payment_order_doc.summary): - frappe.db.set_value("Payment Order", - payment_order_doc.name, - "status", 'Partially Approved' - ) - except Exception as e: - frappe.log_error(title='Payment Order Status Update Error', message=frappe.get_traceback()) - -@frappe.whitelist() -def get_payment_status(docname): - payment_order_doc = frappe.get_doc("Payment Order", docname) - - # Fetch the connector information - bank_connector_exists = frappe.db.exists("Bank Connector", - { - "company": payment_order_doc.company, - "bank_account": payment_order_doc.company_bank_account - } - ) - - if not bank_connector_exists: - frappe.throw("Bank Connector is not initialized") - - bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - - if payment_order_doc.company_bank == 'ICICI Bank' and bank_connector.bulk_transaction : - get_bulk_payment_status(payment_order_doc) - - else: - for i in payment_order_doc.summary: - if i.payment_status == "Initiated": - payment_response = get_response(i, payment_order_doc.company_bank_account, payment_order_doc.company) - - payment_order_doc.reload() - update_payment_status(payment_order_doc) - -@frappe.whitelist() -def make_payment_entries(docname): - payment_order_doc = frappe.get_doc("Payment Order", docname) - """create entry""" - frappe.flags.ignore_account_permission = True - - for row in payment_order_doc.summary: - pe = frappe.new_doc("Payment Entry") - pe.payment_type = "Pay" - pe.payment_entry_type = "Pay" - pe.company = payment_order_doc.company - pe.cost_center = row.cost_center - pe.project = row.project - pe.posting_date = nowdate() - pe.mode_of_payment = "Wire Transfer" - pe.party_type = row.party_type - pe.party = row.party - pe.bank_account = payment_order_doc.company_bank_account - pe.party_bank_account = row.bank_account - if pe.party_type == "Supplier": - pe.ensure_supplier_is_not_blocked() - pe.payment_order = payment_order_doc.name - - pe.paid_from = payment_order_doc.account - if row.account: - pe.paid_to = row.account - pe.paid_from_account_currency = "INR" - pe.paid_to_account_currency = "INR" - pe.paid_amount = row.amount - pe.received_amount = row.amount - pe.letter_head = frappe.db.get_value("Letter Head", {"is_default": 1}, "name") - pe.source_doctype = payment_order_doc.payment_order_type - - for dimension in get_accounting_dimensions(): - pe.update({dimension: payment_order_doc.get(dimension, '')}) - - if row.tax_withholding_category: - net_total = 0 - - for reference in payment_order_doc.references: - if reference.party_type == row.party_type and \ - reference.party == row.party and \ - reference.cost_center == row.cost_center and \ - reference.project == row.project and \ - reference.bank_account == row.bank_account and \ - reference.account == row.account and \ - reference.tax_withholding_category == row.tax_withholding_category and \ - reference.reference_doctype == row.reference_doctype: - net_total += frappe.db.get_value("Bank Payment Request", reference.bank_payment_request, "net_total") - pe.paid_amount = net_total - pe.received_amount = net_total - pe.apply_tax_withholding_amount = 1 - pe.tax_withholding_category = row.tax_withholding_category - for reference in payment_order_doc.references: - if not reference.is_adhoc: - filter_condition = ( reference.party_type == row.party_type and reference.party == row.party and reference.cost_center == row.cost_center - and reference.project == row.project and reference.bank_account == row.bank_account and reference.account == row.account - and reference.tax_withholding_category == row.tax_withholding_category and reference.reference_doctype == row.reference_doctype ) - if not payment_order_doc.is_party_wise: - filter_condition = filter_condition and (reference.reference_doctype == row.reference_doctype and reference.reference_name == row.reference_name) - - if filter_condition: - reference_amount = frappe.db.get_value("Bank Payment Request", reference.bank_payment_request, "net_total") - payment_term = "" - try: - payment_term = frappe.db.get_value("Bank Payment Request", reference.bank_payment_request, "payment_term") - - if not payment_term: - if template := frappe.db.get_value(reference.reference_doctype, reference.reference_name, "payment_terms_template"): - splited_invoice_rows = get_split_invoice_rows(frappe._dict( - { - "voucher_no": reference.reference_name - }), - template, - exc_rates= - { - reference.reference_name: frappe.get_doc("Purchase Invoice", reference.reference_name) - } - ) - - is_term_applied = frappe.db.get_value("Payment Terms Template", template, "allocate_payment_based_on_payment_terms") - - if splited_invoice_rows and is_term_applied: - term_row = 0 - while reference_amount>0: - term_paid = frappe.get_value("Payment Entry Reference", { - "reference_doctype": reference.reference_doctype, - "reference_name": reference.reference_name, - "payment_term": splited_invoice_rows[term_row].get('payment_term'), - "docstatus": 1 - - }, 'sum(allocated_amount)' - ) or 0 - - per = frappe.db.get_value("Payment Term", splited_invoice_rows[term_row].get('payment_term'), "invoice_portion") / 100 - invoice_amount = frappe.db.get_value(reference.reference_doctype, reference.reference_name, "grand_total") - to_be_pay = per * invoice_amount - - if (reference_amount + term_paid) <= to_be_pay: - paid_amount = reference_amount - reference_amount -= paid_amount - else: - paid_amount = (to_be_pay - term_paid) - reference_amount -= paid_amount - - if paid_amount: - pe.append( - "references", - { - "reference_doctype": reference.reference_doctype, - "reference_name": reference.reference_name, - "total_amount": invoice_amount, - "allocated_amount": paid_amount, - "payment_term": splited_invoice_rows[term_row].get('payment_term') - }, - ) - term_row +=1 - else: - pe.append( - "references", - { - "reference_doctype": reference.reference_doctype, - "reference_name": reference.reference_name, - "total_amount": reference_amount, - "allocated_amount": reference_amount - }, - ) - else: - pe.append( - "references", - { - "reference_doctype": reference.reference_doctype, - "reference_name": reference.reference_name, - "total_amount": reference_amount, - "allocated_amount": reference_amount - }, - ) - else: - pe.append( - "references", - { - "reference_doctype": reference.reference_doctype, - "reference_name": reference.reference_name, - "total_amount": reference_amount, - "allocated_amount": reference_amount, - "payment_term": payment_term - }, - ) - except: - frappe.log_error("Error in Payment Terms Template", frappe.get_traceback()) - - pe.update( - { - "reference_no": payment_order_doc.name, - "reference_date": nowdate(), - "remarks": "Bank Payment Entry from Payment Order - {0}".format( - payment_order_doc.name - ), - } - ) - pe.setup_party_account_field() - pe.set_missing_values() - pe.validate() - group_by_invoices(pe) - pe.insert(ignore_permissions=True, ignore_mandatory=True) - pe.submit() - frappe.db.set_value("Payment Order Summary", row.name, "payment_entry", pe.name) - -def group_by_invoices(self): - grouped_references = {} - if self.references: - for ref in self.references: - key = (ref.reference_name, ref.reference_doctype, ref.payment_term) - if key not in grouped_references: - grouped_references[key] = ref - else: - grouped_references[key].allocated_amount += ref.allocated_amount - - self.references = list(grouped_references.values()) - -def process_payment(payment_info, payment_order_doc): - # Fetch the connector information - bank_connector_exists = frappe.db.exists("Bank Connector", { - "company": payment_order_doc.company, - "bank_account": payment_order_doc.company_bank_account - } - ) - - if not bank_connector_exists: - frappe.throw("Bank Connector is not initialized") - - bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - - payment_payload = frappe._dict(payment_info.as_dict(convert_dates_to_str=True)) - - party_field_name = "supplier_name" if payment_info.party_type == "Supplier" else "employee_name" - - party_name = frappe.db.get_value(payment_info.party_type, payment_info.party, party_field_name) - - payment_payload.party_name = party_name - payment_payload.desc = f"Payment to {payment_info.party} via {payment_info.parent}" - - payment_payload.doc = payment_order_doc.as_dict(convert_dates_to_str=True) - - if not payment_order_doc.company_account_number: - frappe.throw("Source bank account number is missing") - - #app_name = frappe._dict(get_bank_info(payment_order_doc.company_bank)).app_name - - #if bank_connector.bank == "ICICI Bank" and not bank_connector.bulk_transaction: - # app_name += "_composite" - - url = f"{bank_connector.url}/api/method/india_banking_connector.api.connect" - - api_key = bank_connector.api_key - api_secret = bank_connector.get_password("api_secret") - headers = { - "Authorization": f"token {api_key}:{api_secret}", - "Content-Type": "application/json", - } - payment_payload.method = 'intiate_payment' - - - response = requests.request("POST", url, headers=headers, data=json.dumps(payment_payload)) - - #create api request log - create_api_log(response, 'Make Payment', payment_info.parenttype, payment_info.parent) - - if response.status_code == 200: - response = json.loads(response.text) - response_data = frappe._dict((response.get('message') or {})) - - if not response_data.status: - return {"payment_status": "", "message": str(response)} - - elif response_data.status == "ACCEPTED": - return {"payment_status": "Initiated", "message": response_data.message} - - elif response_data.status == "Request Failure": - return {"payment_status": "", "message": "Request Failure"} - - else: - return {"payment_status": "", "message": response_data.message} - else: - return {"payment_status": "", "message": ""} - -def get_bank_info(bank_name): - for bank in STD_BANK_LIST: - if bank['bank_name'] == bank_name: - return bank - return {} - -def notify_party(payment_info, response_data): - frappe.log_error("Payment email triggred") - if payment_info.payment_entry: - default_email_format= frappe.get_single("India Banking Settings").default_email_format or "Payment Advice" - if default_email_format: - try: - payment_entry = frappe.get_doc("Payment Entry", payment_info.payment_entry) - frappe.sendmail( - recipients=[payment_info.email or frappe.db.get_value('Bank Account', payment_info.bank_account, "email")], - subject="Payment Notification", - message="Payment for {0} is completed. Please check the attachment for details".format(payment_info.party), - attachments=[{"fname": "payment_details.pdf", "fcontent": frappe.get_print("Payment Entry", payment_entry.name, default_email_format, as_pdf=True)}] - ) - except Exception as e: - frappe.log_error("Payment Email Notification Failed", frappe.get_traceback()) - -def get_response(payment_info, company_bank_account, company): - payment_order_doc = frappe.get_doc("Payment Order", payment_info.parent) - - bank_connector_exists = frappe.db.exists("Bank Connector", {"company": company, "bank_account": company_bank_account}) - - if not bank_connector_exists: - frappe.throw("Bank Connector is not initialized") - - bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - - #app_name = frappe._dict(get_bank_info(payment_order_doc.company_bank)).app_name - - #if bank_connector.bank == "ICICI Bank" and not bank_connector.bulk_transaction: - # app_name += "_composite" - - #url = f"{bank_connector.url}/api/method/{app_name}.{app_name}.doctype.bank_request_log.bank_request_log.get_payment_status" - url = f"{bank_connector.url}/api/method/india_banking_connector.api.connect" - - api_key = bank_connector.api_key - api_secret = bank_connector.get_password("api_secret") - headers = { - "Authorization": f"token {api_key}:{api_secret}", - "Content-Type": "application/json", - } - - payment_info_payload = frappe._dict(payment_info.as_dict(convert_dates_to_str=True)) - - payment_info_payload.doc = payment_order_doc.as_dict(convert_dates_to_str=True) - payment_info_payload.method = 'get_payment_status' - - response = requests.request("POST", url, headers=headers, data=json.dumps(payment_info_payload)) - - #create api request log - create_api_log(response, 'Get Payment Status', payment_info.parenttype, payment_info.parent) - - if response.status_code == 200: - response = json.loads(response.text) - response_data = frappe._dict((response.get('message') or {})) - - if response_data: - if response_data.status == "Processed": - if response_data.reference_number: - frappe.db.set_value("Payment Order Summary", payment_info.name, "reference_number", response_data.reference_number) - frappe.db.set_value("Payment Entry", payment_info.payment_entry, "reference_no", response_data.reference_number) - - notify_party(payment_info, response_data) - - frappe.db.set_value("Payment Order Summary", payment_info.name, "payment_status", "Processed") - - elif response_data.status == "Pending": - frappe.db.set_value("Payment Order Summary", payment_info.name, "message", "Payment is pending") - - elif response_data.status == "Failed": - frappe.db.set_value("Payment Order Summary", payment_info.name, "payment_status", response_data.status) - payment_entry_doc = frappe.get_doc("Payment Entry", payment_info.payment_entry) - if payment_entry_doc.docstatus == 1: - payment_entry_doc.cancel() - process_bank_payment_requests(payment_info.name) - - elif response_data.status == "Rejected": - frappe.db.set_value("Payment Order Summary", payment_info.name, "payment_status", response_data.status) - payment_entry_doc = frappe.get_doc("Payment Entry", payment_info.payment_entry) - if payment_entry_doc.docstatus == 1: - payment_entry_doc.cancel() - - process_bank_payment_requests(payment_info.name) - -def process_bank_payment_requests(payment_order_summary): - pos = frappe.get_doc("Payment Order Summary", payment_order_summary) - payment_order_doc = frappe.get_doc("Payment Order", pos.parent) - - key = ( - pos.party_type, pos.party, pos.bank_account, pos.account, - pos.cost_center, pos.project, pos.tax_withholding_category, - pos.reference_doctype - ) - - failed_prs = [] - for ref in payment_order_doc.references: - ref_key = ( - ref.party_type, ref.party, ref.bank_account, ref.account, - ref.cost_center, ref.project, ref.tax_withholding_category, - ref.reference_doctype - ) - if key == ref_key: - failed_prs.append(ref.bank_payment_request) - - for pr in failed_prs: - pr_doc = frappe.get_doc("Bank Payment Request", pr) - if pr_doc.docstatus == 1: - pr_doc.check_if_payment_entry_exists() - pr_doc.set_as_cancelled() - pr_doc.db_set("docstatus", 2) - - - - - - - diff --git a/india_banking/patches.txt b/india_banking/patches.txt index 069db8d..5165f0d 100644 --- a/india_banking/patches.txt +++ b/india_banking/patches.txt @@ -2,7 +2,6 @@ # Patches added in this section will be executed before doctypes are migrated # Read docs to understand patches: https://frappeframework.com/docs/v14/user/en/database-migrations -india_banking.india_banking.install #2 [post_model_sync] # Patches added in this section will be executed after doctypes are migrated \ No newline at end of file diff --git a/india_banking/patches/migrate/after_migrate.py b/india_banking/patches/migrate/after_migrate.py index 5ed0044..1187787 100644 --- a/india_banking/patches/migrate/after_migrate.py +++ b/india_banking/patches/migrate/after_migrate.py @@ -1,5 +1,5 @@ -import frappe -from india_banking.india_banking.install import create_default_bank +from india_banking.install import create_default_bank + def execute(): - create_default_bank() \ No newline at end of file + create_default_bank() From 18b2562d35aa1dfeabd9a969aefe6edab9ac54d2 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 17 Dec 2024 14:10:49 +0530 Subject: [PATCH 12/83] refactor: take out the bank event and add it to the bank overrite class. --- india_banking/hooks.py | 5 ---- .../india_banking/doc_events/bank_account.py | 7 ------ india_banking/india_banking/override/bank.py | 23 +++++++++++++++---- 3 files changed, 18 insertions(+), 17 deletions(-) delete mode 100644 india_banking/india_banking/doc_events/bank_account.py diff --git a/india_banking/hooks.py b/india_banking/hooks.py index 93b344a..61740b9 100644 --- a/india_banking/hooks.py +++ b/india_banking/hooks.py @@ -163,11 +163,6 @@ # } # } -doc_events = { - "Bank Account": { - "validate": "india_banking.india_banking.doc_events.bank_account.validate_ifsc_code", - } -} accounting_dimension_doctypes = [ "Bank Payment Request", diff --git a/india_banking/india_banking/doc_events/bank_account.py b/india_banking/india_banking/doc_events/bank_account.py deleted file mode 100644 index d0ff132..0000000 --- a/india_banking/india_banking/doc_events/bank_account.py +++ /dev/null @@ -1,7 +0,0 @@ -import frappe, re -from frappe import _ , cstr - -def validate_ifsc_code(self, method): - pattern = re.compile("^[A-Z]{4}0[A-Z0-9]{6}$") - if not pattern.match(cstr(self.branch_code)): - frappe.throw(_("IFSC/Branch Code is not valid")) \ No newline at end of file diff --git a/india_banking/india_banking/override/bank.py b/india_banking/india_banking/override/bank.py index 0c98d01..cb6c592 100644 --- a/india_banking/india_banking/override/bank.py +++ b/india_banking/india_banking/override/bank.py @@ -1,9 +1,22 @@ -import frappe +import re +import frappe from erpnext.accounts.doctype.bank.bank import Bank -from frappe import _ +from frappe import _, cstr + class CustomBank(Bank): - def on_trash(self): - if self.is_standard: - frappe.throw(_("Standard Bank cannot be deleted"), title=_("Action Not Permitted")) \ No newline at end of file + def on_trash(self): + if self.is_standard: + frappe.throw( + _("Standard Bank cannot be deleted"), title=_("Action Not Permitted") + ) + + def validate(self): + super().validate() + self.validate_ifsc_code() + + def validate_ifsc_code(self): + pattern = re.compile("^[A-Z]{4}0[A-Z0-9]{6}$") + if not pattern.match(cstr(self.branch_code)): + frappe.throw(_("IFSC/Branch Code is not valid")) From ced05551502d8bf4596a75db162ff08787b03ad5 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Wed, 18 Dec 2024 16:31:08 +0530 Subject: [PATCH 13/83] fix: remove overrides bank and use docevent hooks --- india_banking/hooks.py | 11 ++++++++-- .../india_banking/doc_events/bank.py | 16 ++++++++++++++ india_banking/india_banking/override/bank.py | 22 ------------------- 3 files changed, 25 insertions(+), 24 deletions(-) create mode 100644 india_banking/india_banking/doc_events/bank.py delete mode 100644 india_banking/india_banking/override/bank.py diff --git a/india_banking/hooks.py b/india_banking/hooks.py index 61740b9..f9112c5 100644 --- a/india_banking/hooks.py +++ b/india_banking/hooks.py @@ -146,7 +146,6 @@ override_doctype_class = { "Payment Order": "india_banking.india_banking.override.payment_order.CustomPaymentOrder", "Payment Entry": "india_banking.india_banking.override.payment_entry.CustomPaymentEntry", - "Bank": "india_banking.india_banking.override.bank.CustomBank", "Payment Request": "india_banking.overrides.payment_request.BankPaymentRequest", } @@ -163,9 +162,17 @@ # } # } +doc_events = { + "Bank": { + "on_trash": "india_banking.india_banking.doc_events.bank.bank_on_trash" + }, + "Bank Account": { + "validate": "india_banking.india_banking.doc_events.bank." + } +} + accounting_dimension_doctypes = [ - "Bank Payment Request", "Payment Order", "Payment Order Reference", "Payment Order Summary", diff --git a/india_banking/india_banking/doc_events/bank.py b/india_banking/india_banking/doc_events/bank.py new file mode 100644 index 0000000..ee731af --- /dev/null +++ b/india_banking/india_banking/doc_events/bank.py @@ -0,0 +1,16 @@ +import frappe +import re +from frappe.utils import cstr + + +def bank_on_trash(doc, method=None): + if hasattr(doc, "is_standard") and doc.is_standard: + frappe.throw( + _("Standard Bank cannot be deleted"), title=_("Action Not Permitted") + ) + + +def validate_ifsc_code(doc, method=None): + pattern = re.compile("^[A-Z]{4}0[A-Z0-9]{6}$") + if not pattern.match(cstr(doc.branch_code)): + frappe.throw(_("IFSC/Branch Code is not valid")) \ No newline at end of file diff --git a/india_banking/india_banking/override/bank.py b/india_banking/india_banking/override/bank.py deleted file mode 100644 index cb6c592..0000000 --- a/india_banking/india_banking/override/bank.py +++ /dev/null @@ -1,22 +0,0 @@ -import re - -import frappe -from erpnext.accounts.doctype.bank.bank import Bank -from frappe import _, cstr - - -class CustomBank(Bank): - def on_trash(self): - if self.is_standard: - frappe.throw( - _("Standard Bank cannot be deleted"), title=_("Action Not Permitted") - ) - - def validate(self): - super().validate() - self.validate_ifsc_code() - - def validate_ifsc_code(self): - pattern = re.compile("^[A-Z]{4}0[A-Z0-9]{6}$") - if not pattern.match(cstr(self.branch_code)): - frappe.throw(_("IFSC/Branch Code is not valid")) From acc11adc764a17bc9c9ad486e49a59859d28039c Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Thu, 19 Dec 2024 11:16:58 +0530 Subject: [PATCH 14/83] refactor: payments button label and api path --- india_banking/hooks.py | 15 +- .../india_banking/doc_events/bank.py | 6 +- .../india_banking/doc_events/payment_order.py | 24 +- .../india_banking/override/payment_entry.py | 8 - india_banking/install.py | 4 + india_banking/overrides/journal_entry.py | 139 ++++ india_banking/overrides/payment_entry.py | 89 +++ .../override => overrides}/payment_order.py | 289 ++++++-- india_banking/overrides/payment_request.py | 57 ++ india_banking/public/js/payment_order.js | 688 +++++++++--------- india_banking/public/js/payment_request.js | 152 ++-- 11 files changed, 938 insertions(+), 533 deletions(-) delete mode 100644 india_banking/india_banking/override/payment_entry.py create mode 100644 india_banking/overrides/journal_entry.py create mode 100644 india_banking/overrides/payment_entry.py rename india_banking/{india_banking/override => overrides}/payment_order.py (50%) diff --git a/india_banking/hooks.py b/india_banking/hooks.py index f9112c5..5733e68 100644 --- a/india_banking/hooks.py +++ b/india_banking/hooks.py @@ -93,8 +93,6 @@ before_uninstall = "india_banking.uninstall.before_uninstall" -accounting_dimension_doctypes = ["Payment Order Reference", "Payment Order Summary"] - # Uninstallation # ------------ @@ -144,8 +142,8 @@ # } override_doctype_class = { - "Payment Order": "india_banking.india_banking.override.payment_order.CustomPaymentOrder", - "Payment Entry": "india_banking.india_banking.override.payment_entry.CustomPaymentEntry", + "Payment Order": "india_banking.overrides.payment_order.CustomPaymentOrder", + "Payment Entry": "india_banking.overrides.payment_entry.CustomPaymentEntry", "Payment Request": "india_banking.overrides.payment_request.BankPaymentRequest", } @@ -163,12 +161,10 @@ # } doc_events = { - "Bank": { - "on_trash": "india_banking.india_banking.doc_events.bank.bank_on_trash" + "Bank": {"on_trash": "india_banking.india_banking.doc_events.bank.bank_on_trash"}, + "Bank Account": { + "validate": "india_banking.india_banking.doc_events.bank.validate_ifsc_code" }, - "Bank Account": { - "validate": "india_banking.india_banking.doc_events.bank." - } } @@ -178,7 +174,6 @@ "Payment Order Summary", ] - # Scheduled Tasks # --------------- diff --git a/india_banking/india_banking/doc_events/bank.py b/india_banking/india_banking/doc_events/bank.py index ee731af..43914e3 100644 --- a/india_banking/india_banking/doc_events/bank.py +++ b/india_banking/india_banking/doc_events/bank.py @@ -1,5 +1,7 @@ -import frappe import re + +import frappe +from frappe import _ from frappe.utils import cstr @@ -13,4 +15,4 @@ def bank_on_trash(doc, method=None): def validate_ifsc_code(doc, method=None): pattern = re.compile("^[A-Z]{4}0[A-Z0-9]{6}$") if not pattern.match(cstr(doc.branch_code)): - frappe.throw(_("IFSC/Branch Code is not valid")) \ No newline at end of file + frappe.throw(_("IFSC/Branch Code is not valid")) diff --git a/india_banking/india_banking/doc_events/payment_order.py b/india_banking/india_banking/doc_events/payment_order.py index c504c0a..d076176 100644 --- a/india_banking/india_banking/doc_events/payment_order.py +++ b/india_banking/india_banking/doc_events/payment_order.py @@ -1,6 +1,4 @@ import json -import random -import uuid import frappe import frappe.utils @@ -9,7 +7,7 @@ get_accounting_dimensions, ) from erpnext.accounts.doctype.payment_entry.payment_entry import get_split_invoice_rows -from frappe.utils import getdate, now, nowdate +from frappe.utils import nowdate from frappe.utils.data import comma_and, cstr from india_banking.india_banking.doctype.india_banking_request_log.india_banking_request_log import ( @@ -472,7 +470,7 @@ def update_payment_status(payment_order_doc): frappe.db.set_value( "Payment Order", payment_order_doc.name, "status", "Partially Approved" ) - except Exception as e: + except: frappe.log_error( title="Payment Order Status Update Error", message=frappe.get_traceback() ) @@ -565,8 +563,8 @@ def make_payment_entries(docname): and reference.reference_doctype == row.reference_doctype ): net_total += frappe.db.get_value( - "Bank Payment Request", - reference.bank_payment_request, + "Payment Request", + reference.payment_request, "net_total", ) pe.paid_amount = net_total @@ -594,15 +592,15 @@ def make_payment_entries(docname): if filter_condition: reference_amount = frappe.db.get_value( - "Bank Payment Request", - reference.bank_payment_request, + "Payment Request", + reference.payment_request, "net_total", ) payment_term = "" try: payment_term = frappe.db.get_value( - "Bank Payment Request", - reference.bank_payment_request, + "Payment Request", + reference.payment_request, "payment_term", ) @@ -727,7 +725,7 @@ def make_payment_entries(docname): { "reference_no": payment_order_doc.name, "reference_date": nowdate(), - "remarks": "Bank Payment Entry from Payment Order - {0}".format( + "remarks": "Payment Entry from Payment Order - {0}".format( payment_order_doc.name ), } @@ -1049,10 +1047,10 @@ def process_bank_payment_requests(payment_order_summary): ref.reference_doctype, ) if key == ref_key: - failed_prs.append(ref.bank_payment_request) + failed_prs.append(ref.payment_request) for pr in failed_prs: - pr_doc = frappe.get_doc("Bank Payment Request", pr) + pr_doc = frappe.get_doc("Payment Request", pr) if pr_doc.docstatus == 1: pr_doc.check_if_payment_entry_exists() pr_doc.set_as_cancelled() diff --git a/india_banking/india_banking/override/payment_entry.py b/india_banking/india_banking/override/payment_entry.py deleted file mode 100644 index 95c381e..0000000 --- a/india_banking/india_banking/override/payment_entry.py +++ /dev/null @@ -1,8 +0,0 @@ -from erpnext.accounts.doctype.payment_entry.payment_entry import PaymentEntry - - -class CustomPaymentEntry(PaymentEntry): - def validate_duplicate_entry(self): - reference_names = [] - for d in self.get("references"): - reference_names.append((d.reference_doctype, d.reference_name, d.payment_term)) \ No newline at end of file diff --git a/india_banking/install.py b/india_banking/install.py index 74b8e6c..567ac76 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -237,6 +237,8 @@ def create_payment_custom_fields_in_payment_order(): "fieldname": "party_type", "fieldtype": "Link", "options": "DocType", + "in_list_view": 1, + "reqd": 1, "insert_after": "column_break_4", }, { @@ -244,6 +246,8 @@ def create_payment_custom_fields_in_payment_order(): "fieldname": "party", "fieldtype": "Dynamic Link", "options": "party_type", + "in_list_view": 1, + "reqd": 1, "insert_after": "party_type", }, { diff --git a/india_banking/overrides/journal_entry.py b/india_banking/overrides/journal_entry.py new file mode 100644 index 0000000..d28d913 --- /dev/null +++ b/india_banking/overrides/journal_entry.py @@ -0,0 +1,139 @@ +import frappe + + +@frappe.whitelist() +def make_payment_order(source_name, target_doc=None, args=None): + from frappe.model.mapper import get_mapped_doc + + def update_bank_entry(source, target): + target.payment_order_type = "Journal Entry" + target.docstaus = 0 + target.status = "Pending" + + journal_accounts = frappe.db.sql( + """ + SELECT + name, account, against_account, cost_center, debit as amount, exchange_rate, parent, + party as employee, party_type, parent as journal + FROM + `tabJournal Entry Account` + WHERE + parent = %s AND party_type = 'Employee' AND payment_status NOT IN ('Paid', 'Ordered')""", + source.name, + as_dict=1, + ) + + if employee_payemnt_details := get_employee_payemnt_details(journal_accounts): + for ref in employee_payemnt_details: + target.append("references", ref) + + doclist = get_mapped_doc( + "Journal Entry", + source_name, + { + "Journal Entry": { + "doctype": "Payment Order", + } + }, + target_doc, + update_bank_entry, + ) + + return doclist + + +def get_employee_payemnt_details(journal_accounts): + employee_bank_account_details = frappe.db.get_all( + "Bank Account", + {"party_type": "Employee", "disabled": 0, "is_default": 1}, + [ + "name", + "party", + "party_type", + "bank", + "branch_code", + "bank_account_no", + "mobile_number", + "email", + "workflow_state", + ], + ) + + employee_bank_account_details = { + detail.get("party"): detail for detail in employee_bank_account_details + } + + payment_details = [] + + for journal_account in journal_accounts: + employee_bank_details = employee_bank_account_details.get( + journal_account.get("employee", "") + ) + if not employee_bank_details: + frappe.throw( + "Default Bank Account not found for Employee {0}".format( + journal_account.get("employee") + ) + ) + else: + if employee_bank_details.get("workflow_state") != "Approved": + link = frappe.utils.get_link_to_form( + "Bank Account", employee_bank_details.get("name") + ) + frappe.throw( + "Bank Account({1}) for Employee {0} is not approved".format( + journal_account.get("employee"), link + ) + ) + + journal_account = frappe._dict(journal_account) + details = { + "reference_doctype": "Journal Entry", + "reference_name": journal_account.journal, + "journal_entry_account": journal_account.name, + "amount": journal_account.amount, + "party_type": "Employee", + "party": journal_account.employee, + "mode_of_payment": "", + "bank_account": employee_bank_details.name, + "account": journal_account.account, + } + payment_details.append(details) + + return payment_details + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_bank_entry(doctype, txt, searchfield, start, page_len, filters, as_dict): + search_condition = "" + if filters and filters.get("docs"): + filters.get("docs").append("") + exist_account = str(tuple(filters.get("docs"))) + search_condition += f" AND je.name NOT IN {exist_account} " + + if filters and filters.get("company"): + search_condition += f"AND je.company = '{filters.get('company')}'" + if txt: + search_condition += f"AND je.name LIKE '%{txt}%'" + + bank_entries = frappe.db.sql( + f""" + SELECT + DISTINCT je.name, je.company, sum(jea.debit) as total, je.voucher_type + FROM + `tabJournal Entry`je + JOIN + `tabJournal Entry Account`jea + ON + je.name = jea.parent + WHERE + je.docstatus = 1 AND jea.payment_status NOT IN ('Paid', 'Ordered') AND + je.voucher_type = 'Bank Entry' AND jea.party_type= "Employee" {search_condition} + GROUP BY + je.name, je.company, je.voucher_type + """, + as_dict=1, + ) + + return bank_entries diff --git a/india_banking/overrides/payment_entry.py b/india_banking/overrides/payment_entry.py new file mode 100644 index 0000000..392690e --- /dev/null +++ b/india_banking/overrides/payment_entry.py @@ -0,0 +1,89 @@ +import frappe +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_accounting_dimensions, +) +from erpnext.accounts.doctype.payment_entry.payment_entry import PaymentEntry +from erpnext.accounts.party import get_party_bank_account + + +class CustomPaymentEntry(PaymentEntry): + def validate_duplicate_entry(self): + reference_names = [] + for d in self.get("references"): + reference_names.append( + (d.reference_doctype, d.reference_name, d.payment_term) + ) + + +@frappe.whitelist() +def make_payment_order(source_name, target_doc=None, args=None): + from frappe.model.mapper import get_mapped_doc + + def set_missing_values(source, target): + target.payment_order_type = "Payment Entry" + target.company_bank_account = source.bank_account + target.party = "" + + account = "" + if source.paid_to: + account = source.paid_to + + for dimension in get_accounting_dimensions(): + target.update({dimension: source.get(dimension, "")}) + + if source.references: + target.append( + "references", + { + "reference_doctype": source.references[0].reference_doctype, + "reference_name": source.references[0].reference_name, + "amount": source.references[0].total_amount, + "party_type": source.party_type, + "party": source.party, + "mode_of_payment": source.mode_of_payment, + "bank_account": get_party_bank_account( + source.get("party_type"), source.get("party") + ) + if source.get("party_type") + else "", + "account": account, + "cost_center": source.cost_center, + "project": source.project, + "payment_entry": source.name, + }, + ) + else: + target.append( + "references", + { + "reference_doctype": "Payment Entry", + "reference_name": source.name, + "amount": source.paid_amount, + "party_type": source.party_type, + "party": source.party, + "mode_of_payment": "Wire Transfer", + "bank_account": source.party_bank_account + or get_party_bank_account( + source.get("party_type"), source.get("party") + ), + "account": source.paid_to, + "cost_center": source.cost_center, + "project": source.project, + "payment_entry": source.name, + }, + ) + target.status = "Pending" + + doclist = get_mapped_doc( + "Payment Entry", + source_name, + { + "Payment Entry": { + "doctype": "Payment Order", + } + }, + target_doc, + set_missing_values, + ) + + return doclist diff --git a/india_banking/india_banking/override/payment_order.py b/india_banking/overrides/payment_order.py similarity index 50% rename from india_banking/india_banking/override/payment_order.py rename to india_banking/overrides/payment_order.py index 57fcd95..a3560ac 100644 --- a/india_banking/india_banking/override/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -1,43 +1,68 @@ -import frappe, json +import json +import re + +import frappe from erpnext.accounts.doctype.payment_order.payment_order import PaymentOrder +from frappe.utils import get_datetime, get_link_to_form, getdate + from india_banking.india_banking.doc_events.payment_order import make_payment_entries +from india_banking.india_banking.doctype.bank_payment_request.bank_payment_request import ( + get_existing_bank_entry, +) + -from frappe.utils import get_datetime, get_link_to_form, getdate -import re -from india_banking.india_banking.doctype.bank_payment_request.bank_payment_request import get_existing_bank_entry class CustomPaymentOrder(PaymentOrder): def before_submit(self): self.update_unique_and_file_reference_id() self.validate_bank_payment_request() - + def validate_bank_payment_request(self): if self.references: for ref in self.references: if ref.bank_payment_request: - bank_payment_request = frappe.get_doc("Bank Payment Request", ref.bank_payment_request) + bank_payment_request = frappe.get_doc( + "Bank Payment Request", ref.bank_payment_request + ) if bank_payment_request.grand_total != ref.amount: - link = get_link_to_form("Bank Payment Request", ref.bank_payment_request) + link = get_link_to_form( + "Bank Payment Request", ref.bank_payment_request + ) message = f"The amount in #Row{ref.idx} does not match the amount of the bank payment request -{link}. The Difference is {ref.amount - bank_payment_request.grand_total}" frappe.throw(title="Invalid Amount", msg=message) @frappe.whitelist() def update_unique_and_file_reference_id(self, save=False): - unique_id = ''.join(re.findall(r'[0-9a-zA-Z]', self.name)) + unique_id = "".join(re.findall(r"[0-9a-zA-Z]", self.name)) unique_id = unique_id[-10:] - frappe.db.set_value("Payment Order", self.name, {"unique_id": unique_id, "file_reference_id": unique_id}) + frappe.db.set_value( + "Payment Order", + self.name, + {"unique_id": unique_id, "file_reference_id": unique_id}, + ) if save: frappe.db.commit() - def validate(self): self.validate_summary() for payment_info in self.summary: - if payment_info.mode_of_transfer == "RTGS" and payment_info.amount >= 500000000: - lei_number = frappe.db.get_value(payment_info.party_type, payment_info.party, "lei_number") + if ( + payment_info.mode_of_transfer == "RTGS" + and payment_info.amount >= 500000000 + ): + lei_number = frappe.db.get_value( + payment_info.party_type, payment_info.party, "lei_number" + ) if not lei_number: - frappe.throw(f"LEI Number required for payment > 50 Cr. For {payment_info.party_type} - {payment_info.party} - {payment_info.amount}") - if "A2A" in payment_info.mode_of_transfer and payment_info.bank != self.company_bank: - frappe.throw(f"Invalid mode of transfer for {payment_info.party_type} - {payment_info.party} at row #{payment_info.idx}") + frappe.throw( + f"LEI Number required for payment > 50 Cr. For {payment_info.party_type} - {payment_info.party} - {payment_info.amount}" + ) + if ( + "A2A" in payment_info.mode_of_transfer + and payment_info.bank != self.company_bank + ): + frappe.throw( + f"Invalid mode of transfer for {payment_info.party_type} - {payment_info.party} at row #{payment_info.idx}" + ) def validate_summary(self): if len(self.summary) <= 0: @@ -45,26 +70,37 @@ def validate_summary(self): default_mode_of_transfer = None if self.default_mode_of_transfer: - default_mode_of_transfer = frappe.get_doc("Mode of Transfer", self.default_mode_of_transfer) + default_mode_of_transfer = frappe.get_doc( + "Mode of Transfer", self.default_mode_of_transfer + ) for payment in self.summary: if payment.mode_of_transfer: - mode_of_transfer = frappe.get_doc("Mode of Transfer", payment.mode_of_transfer) + mode_of_transfer = frappe.get_doc( + "Mode of Transfer", payment.mode_of_transfer + ) else: if not default_mode_of_transfer: frappe.throw("Define a specific mode of transfer or a default one") mode_of_transfer = default_mode_of_transfer payment.mode_of_transfer = default_mode_of_transfer.mode - if payment.amount < mode_of_transfer.minimum_limit or payment.amount > mode_of_transfer.maximum_limit: - frappe.throw(f"Mode of Transfer not suitable for {payment.party} for {payment.amount}. {mode_of_transfer.mode}: {mode_of_transfer.minimum_limit}-{mode_of_transfer.maximum_limit}") + if ( + payment.amount < mode_of_transfer.minimum_limit + or payment.amount > mode_of_transfer.maximum_limit + ): + frappe.throw( + f"Mode of Transfer not suitable for {payment.party} for {payment.amount}. {mode_of_transfer.mode}: {mode_of_transfer.minimum_limit}-{mode_of_transfer.maximum_limit}" + ) summary_total = 0 references_total = 0 for ref in self.references: party_name_field = self.get_party_field_name(ref) - #update party name - ref.party_name = frappe.get_value(ref.party_type, ref.party, party_name_field) + # update party name + ref.party_name = frappe.get_value( + ref.party_type, ref.party, party_name_field + ) references_total += ref.amount @@ -75,21 +111,26 @@ def validate_summary(self): frappe.throw("Summary isn't matching the references") def get_party_field_name(self, party): - if party.party_type == 'Supplier': - return 'supplier_name' - elif party.party_type == 'Employee': - return 'employee_name' + if party.party_type == "Supplier": + return "supplier_name" + elif party.party_type == "Employee": + return "employee_name" else: frappe.throw(f"Unsupported party type {party.party_type}") def on_submit(self): - if self.payment_order_type not in ["Payment Entry", "Payroll Entry", "Journal Entry"]: + if self.payment_order_type == "Payment Request": make_payment_entries(self.name) frappe.db.set_value("Payment Order", self.name, "status", "Pending") for ref in self.references: - if hasattr(ref, "bank_payment_request"): - frappe.db.set_value("Bank Payment Request", ref.bank_payment_request, "status", "Payment Ordered") + if hasattr(ref, "payment_request"): + frappe.db.set_value( + "Payment Request", + ref.payment_request, + "status", + "Payment Ordered", + ) if self.payment_order_type == "Journal Entry": self.update_payemnt_status("submit") @@ -97,34 +138,53 @@ def on_submit(self): def update_payemnt_status(self, action=None): order_status = "" if action == "submit": - order_status = 'Ordered' + order_status = "Ordered" elif action == "cancel": - order_status = '' + order_status = "" elif action == "Paid": - order_status = 'Paid' + order_status = "Paid" elif action == "Failed": - order_status = 'Failed' + order_status = "Failed" for jea in self.summary: - frappe.db.set_value("Journal Entry Account", jea.journal_entry_account, "payment_status", order_status) - + frappe.db.set_value( + "Journal Entry Account", + jea.journal_entry_account, + "payment_status", + order_status, + ) + def make_payroll_bank_entry(self, submit=False): self.docstatus = 0 - payroll_entry = set([ref.payroll_entry for ref in self.references if ref.payroll_entry]) if self.references else [] + payroll_entry = ( + set([ref.payroll_entry for ref in self.references if ref.payroll_entry]) + if self.references + else [] + ) if payroll_entry: for pe in payroll_entry: payroll_entry = frappe.get_doc("Payroll Entry", pe) if not payroll_entry.payment_account: link = frappe.utils.get_link_to_form("Payroll Entry", pe) - frappe.throw(f"Payment Account is mandatory for Payroll Entry {link}") + frappe.throw( + f"Payment Account is mandatory for Payroll Entry {link}" + ) - journal_entry= get_bank_entry_for_payroll({'refrence_name': pe}) + journal_entry = get_bank_entry_for_payroll({"refrence_name": pe}) if not journal_entry: journal = payroll_entry.make_bank_entry(for_withheld_salaries=False) else: - journal = frappe.get_doc('Journal Entry', journal_entry) - - frappe.db.set_value("Journal Entry", journal.name, {"payment_order": self.name, "cheque_no": self.name, 'cheque_date': getdate()}) + journal = frappe.get_doc("Journal Entry", journal_entry) + + frappe.db.set_value( + "Journal Entry", + journal.name, + { + "payment_order": self.name, + "cheque_no": self.name, + "cheque_date": getdate(), + }, + ) journal.reload() if submit and not journal.docstatus: journal.submit() @@ -133,15 +193,19 @@ def on_update_after_submit(self): frappe.throw("You cannot modify a payment order") return - def before_cancel(self): - self.update_payemnt_status('cancel') + self.update_payemnt_status("cancel") for summary_item in self.summary: if summary_item.payment_status in ["Processed", "Initiated"]: - frappe.throw("You cannot cancel a payment order with Initiated/Processed payments") + frappe.throw( + "You cannot cancel a payment order with Initiated/Processed payments" + ) return for account in self.summary: - if account.payment_status == "Processed" or account.payment_status == "Initiated": + if ( + account.payment_status == "Processed" + or account.payment_status == "Initiated" + ): frappe.throw("Cannot cancel a {} Order".format(account.payment_status)) def on_trash(self): @@ -160,9 +224,15 @@ def update_payment_status(self, cancel=False): else: ref_field = "payment_order_status" ref_doc_field = "reference_name" - if self.payment_order_type not in ["Payment Entry", "Journal Entry", "Payroll Entry"]: + if self.payment_order_type not in [ + "Payment Entry", + "Journal Entry", + "Payroll Entry", + ]: for d in self.references: - frappe.db.set_value(self.payment_order_type, d.get(ref_doc_field), ref_field, status) + frappe.db.set_value( + self.payment_order_type, d.get(ref_doc_field), ref_field, status + ) @frappe.whitelist() @@ -174,27 +244,71 @@ def get_party_summary(references, company_bank_account): # Considering the following dimensions to group payments # (party_type, party, bank_account, account, cost_center, project) def _get_unique_key(ref=None, summarise_field=False): - summarise_payment_based_on = frappe.get_single("India Banking Settings").summarise_payment_based_on + summarise_payment_based_on = frappe.get_single( + "India Banking Settings" + ).summarise_payment_based_on if summarise_payment_based_on == "Party": if summarise_field: - return ("party_type", "party", "bank_account", "account", "cost_center", "project", - "tax_withholding_category", "reference_doctype", "payment_entry", "journal_entry", - "journal_entry_account") - - return (ref.party_type, ref.party, ref.bank_account, ref.account, ref.cost_center, ref.project, - ref.tax_withholding_category, ref.reference_doctype, ref.payment_entry, ref.journal_entry, - ref.journal_entry_account) + return ( + "party_type", + "party", + "bank_account", + "account", + "cost_center", + "project", + "tax_withholding_category", + "reference_doctype", + "payment_entry", + "journal_entry", + "journal_entry_account", + ) + + return ( + ref.party_type, + ref.party, + ref.bank_account, + ref.account, + ref.cost_center, + ref.project, + ref.tax_withholding_category, + ref.reference_doctype, + ref.payment_entry, + ref.journal_entry, + ref.journal_entry_account, + ) elif summarise_payment_based_on == "Voucher": if summarise_field: - return ('party_type', 'party', 'reference_doctype', 'reference_name', 'bank_account', - 'account', 'cost_center', 'project', 'tax_withholding_category', 'payment_entry', 'journal_entry', - 'journal_entry_account') - - return (ref.party_type, ref.party, ref.reference_doctype, ref.reference_name, ref.bank_account, - ref.account, ref.cost_center, ref.project, ref.tax_withholding_category, ref.payment_entry, - ref.journal_entry, ref.journal_entry_account) + return ( + "party_type", + "party", + "reference_doctype", + "reference_name", + "bank_account", + "account", + "cost_center", + "project", + "tax_withholding_category", + "payment_entry", + "journal_entry", + "journal_entry_account", + ) + + return ( + ref.party_type, + ref.party, + ref.reference_doctype, + ref.reference_name, + ref.bank_account, + ref.account, + ref.cost_center, + ref.project, + ref.tax_withholding_category, + ref.payment_entry, + ref.journal_entry, + ref.journal_entry_account, + ) summary = {} for ref in references: @@ -208,9 +322,13 @@ def _get_unique_key(ref=None, summarise_field=False): result = [] for key, val in summary.items(): - summary_line_item = {k: v for k, v in zip(_get_unique_key(summarise_field=True), key) } + summary_line_item = { + k: v for k, v in zip(_get_unique_key(summarise_field=True), key) + } summary_line_item["amount"] = val - summarise_payment_based_on = frappe.get_single("India Banking Settings").summarise_payment_based_on + summarise_payment_based_on = frappe.get_single( + "India Banking Settings" + ).summarise_payment_based_on if summarise_payment_based_on == "Party": summary_line_item["is_party_wise"] = 1 else: @@ -223,37 +341,48 @@ def _get_unique_key(ref=None, summarise_field=False): company_bank = frappe.db.get_value("Bank Account", company_bank_account, "bank") row["mode_of_transfer"] = None if party_bank == company_bank: - mode_of_transfer = frappe.db.get_value("Mode of Transfer", {"is_bank_specific": 1, "bank": party_bank}) + mode_of_transfer = frappe.db.get_value( + "Mode of Transfer", {"is_bank_specific": 1, "bank": party_bank} + ) if mode_of_transfer: row["mode_of_transfer"] = mode_of_transfer else: - mot = frappe.db.get_value("Mode of Transfer", { - "minimum_limit": ["<=", row["amount"]], - "maximum_limit": [">", row["amount"]], - "is_bank_specific": 0 + mot = frappe.db.get_value( + "Mode of Transfer", + { + "minimum_limit": ["<=", row["amount"]], + "maximum_limit": [">", row["amount"]], + "is_bank_specific": 0, }, - order_by = "priority asc") + order_by="priority asc", + ) if mot: row["mode_of_transfer"] = mot return result -def get_bank_entry_for_payroll(filters= None): - if filters and filters.get('refrence_name'): - condition = "AND jea.reference_name = '{0}'".format(filters.get('refrence_name')) - journal_entry= frappe.db.sql(f""" - SELECT +def get_bank_entry_for_payroll(filters=None): + if filters and filters.get("refrence_name"): + condition = "AND jea.reference_name = '{0}'".format( + filters.get("refrence_name") + ) + + journal_entry = frappe.db.sql( + f""" + SELECT je.name - FROM + FROM `tabJournal Entry`je - JOIN + JOIN `tabJournal Entry Account`jea ON - je.name = jea.parent - WHERE + je.name = jea.parent + WHERE je.docstatus != 2 AND jea.reference_type = 'Payroll Entry' AND je.voucher_type = 'Bank Entry' {condition} LIMIT 1 - """, as_dict= 1 ) + """, + as_dict=1, + ) - return journal_entry if journal_entry else '' \ No newline at end of file + return journal_entry if journal_entry else "" diff --git a/india_banking/overrides/payment_request.py b/india_banking/overrides/payment_request.py index 348eae6..dd64f0d 100644 --- a/india_banking/overrides/payment_request.py +++ b/india_banking/overrides/payment_request.py @@ -1,4 +1,7 @@ import frappe +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_accounting_dimensions, +) from erpnext.accounts.doctype.payment_request.payment_request import PaymentRequest from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( get_party_tax_withholding_details, @@ -104,3 +107,57 @@ def valdidate_bank_for_wire_transfer(self): frappe.throw("Cannot proceed with un-approved bank account") except Exception: frappe.throw("Workflow Not Found for Bank Account") + + +@frappe.whitelist() +def make_payment_order(source_name, target_doc=None, args=None): + from frappe.model.mapper import get_mapped_doc + + def set_missing_values(source, target): + target.payment_order_type = "Payment Request" + account = "" + if source.payment_type: + account = frappe.db.get_value( + "Payment Type", source.payment_type, "account" + ) + if source.reference_doctype == "Purchase Invoice": + account = frappe.db.get_value( + source.reference_doctype, source.reference_name, "credit_to" + ) + + for dimension in get_accounting_dimensions(): + target.update({dimension: source.get(dimension, "")}) + + target.append( + "references", + { + "reference_doctype": source.reference_doctype, + "reference_name": source.reference_name, + "amount": source.grand_total, + "party_type": source.party_type, + "party": source.party, + "payment_request": source_name, + "mode_of_payment": source.mode_of_payment, + "bank_account": source.bank_account, + "account": account, + "is_adhoc": source.is_adhoc, + "cost_center": source.cost_center, + "project": source.project, + "tax_withholding_category": source.tax_withholding_category, + }, + ) + target.status = "Pending" + + doclist = get_mapped_doc( + "Payment Request", + source_name, + { + "Payment Request": { + "doctype": "Payment Order", + } + }, + target_doc, + set_missing_values, + ) + + return doclist diff --git a/india_banking/public/js/payment_order.js b/india_banking/public/js/payment_order.js index 12ec942..fbb441b 100644 --- a/india_banking/public/js/payment_order.js +++ b/india_banking/public/js/payment_order.js @@ -1,363 +1,359 @@ -frappe.ui.form.on('Payment Order', { - onload(frm) { - frm.set_df_property("payment_order_type", "options", [""].concat(["Bank Payment Request", "Payment Entry", "Purchase Invoice", "Payroll Entry"])); - frm.refresh_field("payment_order_type"); - if(frm.is_new()){ - cur_frm.clear_table('references') - } +frappe.ui.form.on("Payment Order", { + onload(frm) { + if (frm.is_new()) { + cur_frm.clear_table("references"); + } - frm.set_query("company_bank_account", function (doc) { - return { - filters: { - company: doc.company, - is_company_account: 1, - workflow_state: "Approved" - }, - }; - }); - }, - refresh(frm) { - frm.set_df_property('summary', 'cannot_delete_rows', true); - frm.set_df_property('summary', 'cannot_add_rows', true); + frm.set_query("company_bank_account", function (doc) { + return { + filters: { + company: doc.company, + is_company_account: 1, + workflow_state: "Approved", + }, + }; + }); + }, - frm.remove_custom_button("Payment Entry", "Get Payments from"); - frm.remove_custom_button("Payment Request", "Get Payments from"); + get_payments_from_payment_request(frm) { + frm.trigger("remove_row_if_empty"); + let docs = frm.doc.references?.map((doc) => { + return doc.payment_request; + }); - frm.set_df_property("payment_order_type", "options", [""].concat(["Bank Payment Request", "Payment Entry", "Purchase Invoice"])); - frm.refresh_field("payment_order_type"); + erpnext.utils.map_current_doc({ + method: "india_banking.overrides.payment_request.make_payment_order", + source_doctype: "Payment Request", + target: frm, + setters: { + party_type: "", + party: "", + grand_total: "", + }, + get_query_filters: { + docstatus: 1, + status: ["=", "Initiated"], + bank: frm.doc.bank, + name: ["not in", docs], + company: frm.doc.company, + }, + }); + }, - if (frm.doc.docstatus == 0) { - frm.add_custom_button(__('Bank Payment Request'), function() { - frm.trigger("remove_row_if_empty"); - let docs = frm.doc.references?.map((doc)=>{return doc.bank_payment_request}) + get_payments_from_payment_entry(frm) { + frm.trigger("remove_row_if_empty"); + let docs = frm.doc.references?.map((doc) => { + return doc.payment_entry; + }); - erpnext.utils.map_current_doc({ - method: "india_banking.india_banking.doctype.bank_payment_request.bank_payment_request.make_payment_order", - source_doctype: "Bank Payment Request", - target: frm, - args: {"ref_doctype": "Bank Payment Request"}, - setters: { - party: frm.doc.supplier || "", - grand_total: "", - }, - get_query_filters: { - docstatus: 1, - status: ["in", ["Initiated"]], - name: ["not in", docs], - mode_of_payment: "Wire Transfer", - transaction_date : ["<=", frm.doc.posting_date], - company: frm.doc.company - } - }); - }, __("Get from")); + erpnext.utils.map_current_doc({ + method: "india_banking.overrides.payment_entry.make_payment_order", + source_doctype: "Payment Entry", + target: frm, + setters: { + party: "", + paid_amount: "", + }, + get_query_filters: { + docstatus: 1, + name: ["not in", docs], + source_doctype: ["!=", "Payment Request"], + }, + }); + }, - frm.add_custom_button(__('Payment Entry'), function() { - frm.trigger("remove_row_if_empty"); - let docs = frm.doc.references?.map((doc)=>{return doc.payment_entry}) + get_payments_from_journal_entry(frm) { + console.log("get_payments_from_journal_entry"); + erpnext.utils.map_current_doc({ + method: "india_banking.overrides.journal_entry.make_payment_order", + source_doctype: "Journal Entry", + target: frm, + setters: [ + { + fieldtype: "Link", + label: "Company", + fieldname: "company", + options: "Company", + default: frappe.defaults.get_user_default("company"), + }, + { + fieldtype: "Select", + label: "Entry Type", + fieldname: "voucher_type", + options: "Bank Entry", + hidden: 1, + }, + { + fieldtype: "Currency", + label: "Amount", + fieldname: "total", + hidden: 1, + }, + ], + get_query: function () { + let docs = frm.doc.references?.map((doc) => { + return doc.reference_name; + }); + let unique_accounts = [...new Set(docs)]; + return { + query: + "india_banking.india_banking.doctype.bank_payment_request.bank_payment_request.get_bank_entry", + filters: { + docs: unique_accounts, + }, + }; + }, + }); + }, - erpnext.utils.map_current_doc({ - method: "india_banking.india_banking.doctype.bank_payment_request.bank_payment_request.make_payment_order", - source_doctype: "Payment Entry", - target: frm, - args: {"ref_doctype": "Payment Entry"}, - setters: { - party: frm.doc.supplier || "", - paid_amount : "" - }, - get_query_filters: { - docstatus: 1, - name: ["not in", docs], - source_doctype: ["!=", "Bank Payment Request"] - } - }); - }, __("Get from")); - frm.add_custom_button(__('Bank Entry (JV)'), function() { - erpnext.utils.map_current_doc({ - method: "india_banking.india_banking.doctype.bank_payment_request.bank_payment_request.make_payment_order", - source_doctype: "Journal Entry", - target: frm, - args: {"ref_doctype": "Journal Entry"}, - setters: [ - { - fieldtype: "Link", - label: "Company", - fieldname: "company", - options: "Company", - default: frappe.defaults.get_user_default("company") - }, - { - fieldtype: "Select", - label: "Entry Type", - fieldname: "voucher_type", - options: "Bank Entry", - hidden: 1 - }, - { - fieldtype: "Currency", - label: "Amount", - fieldname: "total", - hidden: 1 - } - ], - get_query: function () { - let docs = frm.doc.references?.map((doc)=>{return doc.reference_name}) - let unique_accounts = [...new Set(docs)] - return { - query: "india_banking.india_banking.doctype.bank_payment_request.bank_payment_request.get_bank_entry", - filters: { - docs: unique_accounts - }, - }; - }, - }); - }, __("Get from")); - } - if (frm.doc.docstatus===1 && frm.doc.payment_order_type==='Bank Payment Request') { - frm.remove_custom_button(__('Create Payment Entries')); - } - let is_pending = false - if (frm.doc.status == "Pending" && frm.doc.docstatus == 1) { - if (frm.has_perm('write') && 'summary' in frm.doc) { - var uninitiated_payments = 0; - for(var i = 0; i < frm.doc.summary.length; i++) { - if (!frm.doc.summary[i].payment_initiated) { - uninitiated_payments += 1 - } - if(frm.doc.summary[i].payment_status == "Pending"){ - is_pending = true - } - } - if (uninitiated_payments > 0 && is_pending) { - frappe.db.get_value( - "Bank Connector", - { bank: frm.doc.company_bank}, - "bulk_transaction" - ,(r)=>{ - if(r.bulk_transaction){ - frm.add_custom_button(__('Initiate Payment'), function() { - frappe.call({ - method: "india_banking.india_banking.doc_events.payment_order.generate_payment_otp", - freeze: true, - freeze_message: "Initiating Payment...", - args: { - docname: frm.doc.name - }, - callback: (res)=>{// - if(!res.exc){ - frappe.prompt( - { - label: 'Enter OTP', - place_holder: 'Enter', - fieldname: 'otp', - fieldtype: 'Data' - }, (values) => { - frappe.call({ - method: "india_banking.india_banking.doc_events.payment_order.make_bank_payment", - freeze: 1, - args: { - docname: frm.doc.name, - otp: values.otp, - }, - callback: function(r) { - if(r.message) { - frappe.msgprint(r.message) - } - frm.reload_doc(); - } - }); - }, - "Sent an OTP to the registered account number", - "Proceed") - }// - } - }) - }); - }else{ - frm.add_custom_button(__('Initiate Payment'), function() { - frappe.call({ - method: "india_banking.india_banking.doc_events.payment_order.make_bank_payment", - freeze: 1, - freeze_message: "Initiating Payment...", - args: { - docname: frm.doc.name - }, - callback: function(r) { - if(r.message && !r.exc) { - frappe.msgprint(r.message) - } - frm.reload_doc(); - } - }); - }); - } - }) - } - } - } + refresh(frm) { + frm.set_df_property("summary", "cannot_delete_rows", true); + frm.set_df_property("summary", "cannot_add_rows", true); - if ((frm.doc.status == "Pending" || frm.doc.status == "Initiated") && frm.doc.docstatus == 1) { - if (frm.has_perm('write') && 'summary' in frm.doc) { - var pending_status_check = 0 - for (var j = 0; j < frm.doc.summary.length; j++) { - if(frm.doc.summary[j].payment_status == "Initiated") { - pending_status_check += 1 - } - } + frm.remove_custom_button("Payment Entry", "Get Payments from"); + frm.remove_custom_button("Payment Request", "Get Payments from"); - if (pending_status_check > 0) { - frm.add_custom_button(__('Get Status'), function() { - frappe.call({ - method: "india_banking.india_banking.doc_events.payment_order.get_payment_status", - freeze: 1, - freeze_message: "Fetching payment status....", - args: { - docname: frm.doc.name, - }, - callback: function(r) { - if(r.message && !r.exc) { - frappe.msgprint(r.message) - } - frm.reload_doc(); - } - }); - }); - } - } - } - frm.set_query("party_type", "references" , function() { - return { - filters: { - "name": ["in", ["Supplier", "Employee"]] - } - }; - }); - frm.set_query("mode_of_transfer", "summary" , function() { - return { - filters: { - "disabled": 0 - } - }; - }); - }, + if (frm.doc.docstatus == 0) { + frm.add_custom_button( + __("Payment Request"), + function () { + frm.trigger("get_payments_from_payment_request"); + }, + __("Get Payments from") + ); - remove_button: function(frm) { - // remove custom button of order type that is not imported - let label = ["Payment Request", "Purchase Invoice"]; + frm.add_custom_button( + __("Payment Entry"), + function () { + frm.trigger("get_payments_from_payment_entry"); + }, + __("Get Payments from") + ); - if (frm.doc.references.length > 0 && frm.doc.payment_order_type) { - label = label.reduce(x => { - x!= frm.doc.payment_order_type; - return x; - }); - frm.remove_custom_button(label, "Get from"); - } - }, - get_summary: function(frm) { - if (frm.doc.docstatus > 0) { - frappe.msgprint("Not allowed to change post submission"); - return - } - if (!frm.doc.company_bank_account > 0) { - frappe.msgprint("Please Select Company Bank Account"); - return - } - frappe.call({ - method: "india_banking.india_banking.override.payment_order.get_party_summary", - args: { - references: frm.doc.references, - company_bank_account: frm.doc.company_bank_account - }, - freeze: true, - callback: function(r) { - let is_party_wise = 0; - if(r.message && !r.exc) { - let summary_data = r.message - frm.clear_table("summary"); - var doc_total = 0 - for (var i = 0; i < summary_data.length; i++) { - if (summary_data[i].is_party_wise && !is_party_wise) { - is_party_wise = 1; - } - doc_total += summary_data[i].amount - let row = frm.add_child("summary"); - row.party_type = summary_data[i].party_type; - row.party = summary_data[i].party; - row.amount = summary_data[i].amount; - row.bank_account = summary_data[i].bank_account; - row.account = summary_data[i].account; - row.mode_of_transfer = summary_data[i].mode_of_transfer; - row.cost_center = summary_data[i].cost_center; - row.project = summary_data[i].project; - row.tax_withholding_category = summary_data[i].tax_withholding_category; - row.reference_doctype = summary_data[i].reference_doctype; - row.reference_name = summary_data[i].reference_name; - row.payment_entry = summary_data[i].payment_entry; - row.journal_entry = summary_data[i].journal_entry; - row.journal_entry_account = summary_data[i].journal_entry_account; + frm.add_custom_button( + __("Bank Entry(JV)"), + function () { + frm.trigger("get_payments_from_journal_entry"); + }, + __("Get Payments from") + ); + } + frm.trigger("remove_button"); - } - if (is_party_wise) { - frm.set_value("is_party_wise", 1); - } else { - frm.set_value("is_party_wise", 0); - } - frm.refresh_field("summary"); - frm.doc.total = doc_total; - frm.refresh_fields(); - } - } - }); - }, - update_status: function(frm) { - if (frm.doc.docstatus != 1) { - frappe.msgprint("Updating status is not allowed without submission"); - return - } + let is_pending = false; + if (frm.doc.status == "Pending" && frm.doc.docstatus == 1) { + if (frm.has_perm("write") && "summary" in frm.doc) { + var uninitiated_payments = 0; + for (var i = 0; i < frm.doc.summary.length; i++) { + if (!frm.doc.summary[i].payment_initiated) { + uninitiated_payments += 1; + } + if (frm.doc.summary[i].payment_status == "Pending") { + is_pending = true; + } + } + if (uninitiated_payments > 0 && is_pending) { + frappe.db.get_value( + "Bank Connector", + { bank: frm.doc.company_bank }, + "bulk_transaction", + (r) => { + if (r.bulk_transaction) { + frm.add_custom_button(__("Initiate Payment"), function () { + frappe.call({ + method: + "india_banking.india_banking.doc_events.payment_order.generate_payment_otp", + freeze: true, + freeze_message: "Initiating Payment...", + args: { + docname: frm.doc.name, + }, + callback: (res) => { + if (!res.exc) { + frappe.prompt( + { + label: "Enter OTP", + place_holder: "Enter", + fieldname: "otp", + fieldtype: "Data", + }, + (values) => { + frappe.call({ + method: + "india_banking.india_banking.doc_events.payment_order.make_bank_payment", + freeze: 1, + args: { + docname: frm.doc.name, + otp: values.otp, + }, + callback: function (r) { + if (r.message) { + frappe.msgprint(r.message); + } + frm.reload_doc(); + }, + }); + }, + "Sent an OTP to the registered account number", + "Proceed" + ); + } + }, + }); + }); + } else { + frm.add_custom_button(__("Initiate Payment"), function () { + frappe.call({ + method: + "india_banking.india_banking.doc_events.payment_order.make_bank_payment", + freeze: 1, + freeze_message: "Initiating Payment...", + args: { + docname: frm.doc.name, + }, + callback: function (r) { + if (r.message && !r.exc) { + frappe.msgprint(r.message); + } + frm.reload_doc(); + }, + }); + }); + } + } + ); + } + } + } - if (!frm.doc.approval_status) { - frappe.msgprint("Updating status is not allowed without value"); - return - } + if ( + (frm.doc.status == "Pending" || frm.doc.status == "Initiated") && + frm.doc.docstatus == 1 + ) { + if (frm.has_perm("write") && "summary" in frm.doc) { + var pending_status_check = 0; + for (var j = 0; j < frm.doc.summary.length; j++) { + if (frm.doc.summary[j].payment_status == "Initiated") { + pending_status_check += 1; + } + } - var selected_rows = frm.get_selected() - if (!Object.keys(selected_rows).length || !"summary" in selected_rows){ - frappe.msgprint("No rows are selected"); - return - } + if (pending_status_check > 0) { + frm.add_custom_button(__("Get Status"), function () { + frappe.call({ + method: + "india_banking.india_banking.doc_events.payment_order.get_payment_status", + freeze: 1, + freeze_message: "Fetching payment status....", + args: { + docname: frm.doc.name, + }, + callback: function (r) { + if (r.message && !r.exc) { + frappe.msgprint(r.message); + } + frm.reload_doc(); + }, + }); + }); + } + } + } - frappe.call({ - method: "india_banking.india_banking.doc_events.payment_order.modify_approval_status", - args: { - items: selected_rows.summary, - approval_status: frm.doc.approval_status, - }, - callback: function(r) { - if(r.message && !r.exc) { - var updated_count = 0 - for (var line_item in r.message) { - if (r.message[line_item].status) { - frappe.model.set_value("Payment Order Summary", line_item, "approval_status", r.message[line_item].message); - updated_count += 1 - } else { - frappe.msgprint(r.message[line_item].message) - } - } - frappe.msgprint(updated_count + " record(s) updated.") - } - frm.dirty(); - frm.refresh_fields(); - } - }); - } + frm.set_query("mode_of_transfer", "summary", function () { + return { + filters: { + disabled: 0, + }, + }; + }); + }, -}); + remove_button: function (frm) { + // remove custom button of order type that is not imported + frm.remove_custom_button("Create Journal Entries"); + if ( + (frm.doc.references.length > 0 && frm.doc.payment_order_type) || + frm.doc.docstatus != 0 + ) { + if ( + frm.doc.payment_order_type == "Payment Request" || + frm.doc.docstatus != 0 + ) { + frm.remove_custom_button("Bank Entry(JV)", "Get Payments from"); + frm.remove_custom_button("Payment Entry", "Get Payments from"); + } + if ( + frm.doc.payment_order_type == "Payment Entry" || + frm.doc.docstatus != 0 + ) { + frm.remove_custom_button("Bank Entry(JV)", "Get Payments from"); + frm.remove_custom_button("Payment Request", "Get Payments from"); + } + if ( + frm.doc.payment_order_type == "Payment Entry" || + frm.doc.docstatus != 0 + ) { + frm.remove_custom_button("Payment Request", "Get Payments from"); + frm.remove_custom_button("Bank Entry(JV)", "Get Payments from"); + } + } + }, -frappe.ui.form.on('Payment Order Summary', { - setup: function(frm) { - frm.set_query("party_type", function() { - return { - query: "erpnext.setup.doctype.party_type.party_type.get_party_type", - }; - }); - } -}) \ No newline at end of file + get_summary: function (frm) { + if (frm.doc.docstatus > 0) { + frappe.msgprint("Not allowed to change post submission"); + return; + } + if (!frm.doc.company_bank_account > 0) { + frappe.msgprint("Please Select Company Bank Account"); + return; + } + frappe.call({ + method: "india_banking.overrides.payment_order.get_party_summary", + args: { + references: frm.doc.references, + company_bank_account: frm.doc.company_bank_account, + }, + freeze: true, + callback: function (r) { + let is_party_wise = 0; + if (r.message && !r.exc) { + let summary_data = r.message; + frm.clear_table("summary"); + var doc_total = 0; + for (var i = 0; i < summary_data.length; i++) { + if (summary_data[i].is_party_wise && !is_party_wise) { + is_party_wise = 1; + } + doc_total += summary_data[i].amount; + let row = frm.add_child("summary"); + row.party_type = summary_data[i].party_type; + row.party = summary_data[i].party; + row.amount = summary_data[i].amount; + row.bank_account = summary_data[i].bank_account; + row.account = summary_data[i].account; + row.mode_of_transfer = summary_data[i].mode_of_transfer; + row.cost_center = summary_data[i].cost_center; + row.project = summary_data[i].project; + row.tax_withholding_category = + summary_data[i].tax_withholding_category; + row.reference_doctype = summary_data[i].reference_doctype; + row.reference_name = summary_data[i].reference_name; + row.payment_entry = summary_data[i].payment_entry; + row.journal_entry = summary_data[i].journal_entry; + row.journal_entry_account = summary_data[i].journal_entry_account; + } + if (is_party_wise) { + frm.set_value("is_party_wise", 1); + } else { + frm.set_value("is_party_wise", 0); + } + frm.refresh_field("summary"); + frm.doc.total = doc_total; + frm.refresh_fields(); + } + }, + }); + }, +}); diff --git a/india_banking/public/js/payment_request.js b/india_banking/public/js/payment_request.js index ae54826..43c147c 100644 --- a/india_banking/public/js/payment_request.js +++ b/india_banking/public/js/payment_request.js @@ -1,76 +1,80 @@ -frappe.ui.form.on('Payment Request', { - refresh(frm) { - if(frm.doc.status == "Initiated") { - frm.remove_custom_button(__('Create Payment Entry')) - } - frm.set_query("payment_type", function() { - return { - filters: { - "company": frm.doc.company - } - }; - }); - if(frm.doc.docstatus == 1){ - cur_frm.add_custom_button('Goto Payment Order', function(){ - frappe.set_route('List', 'Payment Order') - }) - } - }, - company (frm) { - frm.set_query("payment_type", function() { - return { - filters: { - "company": frm.doc.company - } - }; - }); - }, - mode_of_payment (frm) { - var conditions = get_bank_query_conditions(frm); - if (frm.doc.mode_of_payment == "Wire Transfer") { - frm.set_query("bank_account", function() { - return { - filters: conditions - }; - }); - } - }, - party_type (frm) { - var conditions = get_bank_query_conditions(frm); - if (frm.doc.mode_of_payment == "Wire Transfer") { - frm.set_query("bank_account", function() { - return { - filters: conditions - }; - }); - } - }, - party (frm) { - var conditions = get_bank_query_conditions(frm); - if (frm.doc.mode_of_payment == "Wire Transfer") { - frm.set_query("bank_account", function() { - return { - filters: conditions - }; - }); - } - } +frappe.ui.form.on("Payment Request", { + refresh(frm) { + if ( + frm.doc.payment_request_type == "Outward" && + ["Initiated", "Partially Paid"].includes(frm.doc.status) + ) { + frm.remove_custom_button(__("Create Payment Entry")); + cur_frm + .add_custom_button("Goto Payment Order", function () { + frappe.set_route("List", "Payment Order"); + }) + .addClass("btn-primary"); + } + + frm.set_query("payment_type", function () { + return { + filters: { + company: frm.doc.company, + }, + }; + }); + }, + company(frm) { + frm.set_query("payment_type", function () { + return { + filters: { + company: frm.doc.company, + }, + }; + }); + }, + mode_of_payment(frm) { + var conditions = get_bank_query_conditions(frm); + if (frm.doc.mode_of_payment == "Wire Transfer") { + frm.set_query("bank_account", function () { + return { + filters: conditions, + }; + }); + } + }, + party_type(frm) { + var conditions = get_bank_query_conditions(frm); + if (frm.doc.mode_of_payment == "Wire Transfer") { + frm.set_query("bank_account", function () { + return { + filters: conditions, + }; + }); + } + }, + party(frm) { + var conditions = get_bank_query_conditions(frm); + if (frm.doc.mode_of_payment == "Wire Transfer") { + frm.set_query("bank_account", function () { + return { + filters: conditions, + }; + }); + } + }, }); -var get_bank_query_conditions = function(frm) { - var conditions = {} - if (frm.doc.party_type) { - conditions["party_type"] = frm.doc.party_type; - } - if (frm.doc.party) { - conditions["party"] = frm.doc.party; - } - if (frm.doc.mode_of_payment == "Wire Transfer") { - frm.set_query("bank_account", function() { - return { - filters: conditions - }; - }); - } - return conditions; -}; \ No newline at end of file +var get_bank_query_conditions = function (frm) { + var conditions = {}; + if (frm.doc.party_type) { + conditions["party_type"] = frm.doc.party_type; + } + if (frm.doc.party) { + conditions["party"] = frm.doc.party; + } + if (frm.doc.mode_of_payment == "Wire Transfer") { + frm.set_query("bank_account", function () { + return { + filters: conditions, + }; + }); + } + return conditions; +}; From 7769578e13aa6e672bdda5f500ea5d058a487595 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Thu, 19 Dec 2024 12:15:01 +0530 Subject: [PATCH 15/83] refactor: remove purchase order and purchase invoice js overrides --- india_banking/hooks.py | 1 - india_banking/overrides/payment_request.py | 11 ++++ india_banking/public/js/payment_order.js | 4 +- india_banking/public/js/purchase_invoice.js | 39 -------------- india_banking/public/js/purchase_order.js | 57 --------------------- 5 files changed, 12 insertions(+), 100 deletions(-) delete mode 100644 india_banking/public/js/purchase_invoice.js delete mode 100644 india_banking/public/js/purchase_order.js diff --git a/india_banking/hooks.py b/india_banking/hooks.py index 5733e68..088dd56 100644 --- a/india_banking/hooks.py +++ b/india_banking/hooks.py @@ -38,7 +38,6 @@ doctype_js = { "Payment Order": "public/js/payment_order.js", - "Purchase Order": "public/js/purchase_order.js", "Purchase Invoice": "public/js/purchase_invoice.js", "Payment Type": "public/js/payment_type.js", "Bank Account": "public/js/bank_account.js", diff --git a/india_banking/overrides/payment_request.py b/india_banking/overrides/payment_request.py index dd64f0d..9a646ff 100644 --- a/india_banking/overrides/payment_request.py +++ b/india_banking/overrides/payment_request.py @@ -6,6 +6,8 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( get_party_tax_withholding_details, ) +from erpnext.accounts.party import get_party_bank_account +from frappe import _ class BankPaymentRequest(PaymentRequest): @@ -49,6 +51,15 @@ def on_submit(self): if not self.grand_total: frappe.throw("Amount cannot be zero") + bank_account = get_party_bank_account(self.party_type, self.party) + + if not bank_account: + frappe.throw( + _("Default Bank Account is missing for {0} - {1}").format( + self.party_type, self.party + ) + ) + debit_account = None if self.payment_type: debit_account = frappe.db.get_value( diff --git a/india_banking/public/js/payment_order.js b/india_banking/public/js/payment_order.js index fbb441b..452548e 100644 --- a/india_banking/public/js/payment_order.js +++ b/india_banking/public/js/payment_order.js @@ -63,7 +63,6 @@ frappe.ui.form.on("Payment Order", { }, get_payments_from_journal_entry(frm) { - console.log("get_payments_from_journal_entry"); erpnext.utils.map_current_doc({ method: "india_banking.overrides.journal_entry.make_payment_order", source_doctype: "Journal Entry", @@ -96,8 +95,7 @@ frappe.ui.form.on("Payment Order", { }); let unique_accounts = [...new Set(docs)]; return { - query: - "india_banking.india_banking.doctype.bank_payment_request.bank_payment_request.get_bank_entry", + query: "india_banking.overrides.journal_entry.get_bank_entry", filters: { docs: unique_accounts, }, diff --git a/india_banking/public/js/purchase_invoice.js b/india_banking/public/js/purchase_invoice.js deleted file mode 100644 index 06ada3f..0000000 --- a/india_banking/public/js/purchase_invoice.js +++ /dev/null @@ -1,39 +0,0 @@ -frappe.ui.form.on('Purchase Invoice', { - refresh(frm) { - if (frm.doc.outstanding_amount > 0 && !cint(frm.doc.is_return) && !frm.doc.on_hold) { - cur_frm.add_custom_button( - __("Bank Payment Request"), - function () { - make_bank_payment_request(frm) - }, - __("Create") - ); - } - setTimeout(() => { - cur_frm.remove_custom_button("Payment", "Create") - }, 500); - } -}) - -const make_bank_payment_request = function(frm){ - const payment_request_type = (['Sales Order', 'Sales Invoice'].includes(frm.doc.doctype)) - ? "Inward" : "Outward"; - - frappe.call({ - method:"india_banking.india_banking.doctype.bank_payment_request.bank_payment_request.make_bank_payment_request", - args: { - dt: frm.doc.doctype, - dn: frm.doc.name, - recipient_id: frm.doc.contact_email, - payment_request_type: payment_request_type, - party_type: payment_request_type == 'Outward' ? "Supplier" : "Customer", - party: payment_request_type == 'Outward' ? frm.doc.supplier : frm.doc.customer - }, - callback: function(r) { - if(!r.exc){ - frappe.model.sync(r.message); - frappe.set_route("Form", r.message.doctype, r.message.name); - } - } - }) -} \ No newline at end of file diff --git a/india_banking/public/js/purchase_order.js b/india_banking/public/js/purchase_order.js deleted file mode 100644 index 4ab7756..0000000 --- a/india_banking/public/js/purchase_order.js +++ /dev/null @@ -1,57 +0,0 @@ -frappe.ui.form.on('Purchase Order', { - refresh(frm) { - frm.call({ - method: "india_banking.india_banking.doctype.bank_payment_request.bank_payment_request.validate_payment_request_status", - args: { - "ref_doctype": frm.doc.doctype, - "ref_name": frm.doc.name, - "grand_total" : frm.doc.rounded_total || frm.doc.grand_total - }, - callback: (r) => { - if (frm.doc.docstatus == 1 && r.message != "Completed") { - if (frm.doc.status != "Closed") { - if (frm.doc.status != "On Hold") { - if (flt(frm.doc.per_billed, 2) < 100) { - frm.add_custom_button(__('Bank Payment Request'), function () { - make_bank_payment_request(frm) - }, "Create"); - } - } - } - } - }, - }); - - setTimeout(() => { - frm.trigger('toggle_custom_button') - }, 1000); - - }, - - toggle_custom_button(frm) { - cur_frm.remove_custom_button("Payment", "Create") - }, -}) - -const make_bank_payment_request = function (frm) { - const payment_request_type = (['Sales Order', 'Sales Invoice'].includes(frm.doc.doctype)) - ? "Inward" : "Outward"; - - frappe.call({ - method: "india_banking.india_banking.doctype.bank_payment_request.bank_payment_request.make_bank_payment_request", - args: { - dt: frm.doc.doctype, - dn: frm.doc.name, - recipient_id: frm.doc.contact_email, - payment_request_type: payment_request_type, - party_type: payment_request_type == 'Outward' ? "Supplier" : "Customer", - party: payment_request_type == 'Outward' ? frm.doc.supplier : frm.doc.customer - }, - callback: function (r) { - if (!r.exc) { - frappe.model.sync(r.message); - frappe.set_route("Form", r.message.doctype, r.message.name); - } - } - }) -} \ No newline at end of file From d4238a0b1e664da19775d3a46aab634efd7ffd72 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Thu, 19 Dec 2024 12:45:44 +0530 Subject: [PATCH 16/83] fix: create default mode of transfers --- india_banking/install.py | 2 +- india_banking/patches/migrate/after_migrate.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/india_banking/install.py b/india_banking/install.py index 567ac76..91140c6 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -327,7 +327,7 @@ def create_default_bank(): for bank in STD_BANK_LIST: if not frappe.db.exists("Bank", bank): bank_doc = frappe.new_doc("Bank") - bank_doc.bank = bank + bank_doc.bank_name = bank bank_doc.is_standard = 1 bank_doc.save() diff --git a/india_banking/patches/migrate/after_migrate.py b/india_banking/patches/migrate/after_migrate.py index 1187787..de8e875 100644 --- a/india_banking/patches/migrate/after_migrate.py +++ b/india_banking/patches/migrate/after_migrate.py @@ -1,5 +1,6 @@ -from india_banking.install import create_default_bank +from india_banking.install import create_default_bank, create_default_mode_of_transfers def execute(): create_default_bank() + create_default_mode_of_transfers() From 8f6eadfbc4d6b274fda616462ab7d1dd4b476957 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Thu, 19 Dec 2024 12:51:56 +0530 Subject: [PATCH 17/83] fix: remove patch file --- india_banking/patches.txt | 5 +++-- india_banking/patches/migrate/__init__.py | 0 india_banking/patches/migrate/after_migrate.py | 6 ------ 3 files changed, 3 insertions(+), 8 deletions(-) delete mode 100644 india_banking/patches/migrate/__init__.py delete mode 100644 india_banking/patches/migrate/after_migrate.py diff --git a/india_banking/patches.txt b/india_banking/patches.txt index 5165f0d..7e09a64 100644 --- a/india_banking/patches.txt +++ b/india_banking/patches.txt @@ -2,6 +2,7 @@ # Patches added in this section will be executed before doctypes are migrated # Read docs to understand patches: https://frappeframework.com/docs/v14/user/en/database-migrations - [post_model_sync] -# Patches added in this section will be executed after doctypes are migrated \ No newline at end of file +# Patches added in this section will be executed after doctypes are migrated + +india_banking.patches.v1_0.create_default \ No newline at end of file diff --git a/india_banking/patches/migrate/__init__.py b/india_banking/patches/migrate/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/india_banking/patches/migrate/after_migrate.py b/india_banking/patches/migrate/after_migrate.py deleted file mode 100644 index de8e875..0000000 --- a/india_banking/patches/migrate/after_migrate.py +++ /dev/null @@ -1,6 +0,0 @@ -from india_banking.install import create_default_bank, create_default_mode_of_transfers - - -def execute(): - create_default_bank() - create_default_mode_of_transfers() From f9d53d5cad624f40d48b43e42e2ca409854c678c Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Thu, 19 Dec 2024 13:04:37 +0530 Subject: [PATCH 18/83] refactor: remove unused function and imports from utils --- india_banking/utils.py | 87 +++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 56 deletions(-) diff --git a/india_banking/utils.py b/india_banking/utils.py index 7955249..1d8252c 100644 --- a/india_banking/utils.py +++ b/india_banking/utils.py @@ -1,65 +1,40 @@ -import frappe, json -from datetime import datetime +import frappe from frappe import _ -import requests -from frappe.utils import get_link_to_form - - -def create_response_log(log_details): - log = frappe.get_doc({ - "doctype": "India Banking Request Log", - "status": log_details.status, - "payload": log_details.get("payload") or "", - "voucher_type": log_details.get("voucher_type"), - "voucher_name": log_details.get("voucher_name"), - "response": json.dumps(log_details.get("response"), indent=4), - - }).insert(ignore_permissions=True) - frappe.db.commit() - return log.name - - -def send_request(args): - response = requests.request(args.method, args.url, headers=args.headers, data=args.payload) - data = frappe._dict(json.loads(response.text)) - log_name = create_response_log(frappe._dict({ - "status": "Success" if response.ok else "Failure", - "payload": args.payload, - "voucher_type": args.get("voucher_type") or "", - "voucher_name": args.get("voucher_name") or "", - "response": json.loads(response.text), - - })) - - if response.ok: - return response.text - - else: - return response.text - def get_bank_address_details(bank_account): - address = frappe.db.get_value("Dynamic Link", {"link_doctype": "Bank Account", "link_name": bank_account}, 'parent') - if not address: + address = frappe.db.get_value( + "Dynamic Link", + {"link_doctype": "Bank Account", "link_name": bank_account}, + "parent", + ) + if not address: return {} - party_address_ = frappe.get_doc('Address', address) - address_line = party_address_.get('address_line1', '').split(',') - street_name = party_address_.get('city', '') - building_number = address_line[0] if address_line else '' + party_address_ = frappe.get_doc("Address", address) + address_line = party_address_.get("address_line1", "").split(",") + street_name = party_address_.get("city", "") + building_number = address_line[0] if address_line else "" if len(building_number) > 10: building_number = building_number[:10] - post_code = party_address_.get('pincode', '') - town_name = party_address_.get('state', '')[:3].upper() if party_address_.get('state', '') else '' - country_sub_division = [party_address_.get('country', '')[:2]] if party_address_.get('country', '') else [] - country = party_address_.get('country', '')[:2] + post_code = party_address_.get("pincode", "") + town_name = ( + party_address_.get("state", "")[:3].upper() + if party_address_.get("state", "") + else "" + ) + country_sub_division = ( + [party_address_.get("country", "")[:2]] + if party_address_.get("country", "") + else [] + ) + country = party_address_.get("country", "")[:2] return { - "AddressLine": address_line, - "StreetName": street_name, - "BuildingNumber": building_number, - "PostCode": post_code, - "TownName": town_name, - "CountySubDivision": country_sub_division, - "Country": country - } + "AddressLine": address_line, + "StreetName": street_name, + "BuildingNumber": building_number, + "PostCode": post_code, + "TownName": town_name, + "CountySubDivision": country_sub_division, + "Country": country, + } From a130b7ef9acb049939b1914fba7650bf5fa60d08 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Thu, 19 Dec 2024 13:08:59 +0530 Subject: [PATCH 19/83] refactor: remove unused api file --- india_banking/api.py | 68 -------------------------------------------- 1 file changed, 68 deletions(-) delete mode 100644 india_banking/api.py diff --git a/india_banking/api.py b/india_banking/api.py deleted file mode 100644 index 6b6ab1d..0000000 --- a/india_banking/api.py +++ /dev/null @@ -1,68 +0,0 @@ -import frappe -import json -import requests -from frappe.utils import getdate - - -@frappe.whitelist() -def get_bank_statement(bank_name, from_date=None, to_date= None, is_paginated=False, last_tran_id=None, update=False): - bank_doc = frappe.get_doc("Bank Account", bank_name) - - bank_connector_exists = frappe.db.exists("Bank Connector", {"company": bank_doc.company, "bank": bank_doc.bank}) - - if not bank_connector_exists: - frappe.throw("Bank Connector is not initialized") - - bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - - app_name = frappe._dict(get_bank_info(bank_doc.bank)).app_name - - if bank_connector.bank == "ICICI Bank" and not bank_connector.bulk_transaction: - app_name += "_composite" - - url = f"{bank_connector.url}/api/method/{app_name}.{app_name}.doctype.bank_request_log.bank_request_log.get_bank_statement" - - paginated_args = {} - if is_paginated: - url = f"{bank_connector.url}/api/method/{app_name}.{app_name}.doctype.bank_request_log.bank_request_log.get_bank_statements_paginated" - paginated_args['conflg'] = 'Y' if not last_tran_id else 'N' - paginated_args['last_tran_id'] = last_tran_id or "" - - api_key = bank_connector.api_key - api_secret = bank_connector.get_password("api_secret") - headers = { - "Authorization": f"token {api_key}:{api_secret}", - "Content-Type": "application/json", - } - - payload = json.dumps({ - "bank_account_number": bank_doc.bank_account_no, - "from_date": from_date or getdate().strftime("%d-%m-%Y"), - "to_date": to_date or getdate().strftime("%d-%m-%Y"), - }) - - response = requests.request("POST", url, headers=headers, data= payload) - - #create api request log - action = "Get Bank Statement" if not is_paginated else "Get Bank Statements Paginated" - create_api_log(response, action , "Bank Account", bank_doc.name) - - if response.status_code == 200: - response = json.loads(response.text) - response_data = frappe._dict((response.get('message') or {})) - if response_data.get('server_status') == "Success": - if response_data.bank_statements and update: - update_bank_transactions(bank_doc, response_data.bank_statements) - if update: - return response_data.bank_statements - else: - frappe.msgprint(title= "API Failed", msg=action + " Failed", indicator='red') - - - -def update_bank_transactions(bank_doc, statements): - for statement in statements: - if not frappe.db.exists("Bank Transaction", {"bank_account": bank_doc.name, "reference_number": statement.transaction_id}): - bank_transaction_doc = frappe.new_doc("Bank Transaction") - bank_transaction_doc.update(statement) - bank_transaction_doc.save() From f0b8ec8450add6f8846953a0059ea784a03ba6b4 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Thu, 19 Dec 2024 13:15:26 +0530 Subject: [PATCH 20/83] refactor: format indentation --- india_banking/tasks.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/india_banking/tasks.py b/india_banking/tasks.py index 87b61f4..48b805f 100644 --- a/india_banking/tasks.py +++ b/india_banking/tasks.py @@ -1,17 +1,21 @@ import frappe + from india_banking.india_banking.doc_events.payment_order import get_payment_status + + def daily(): - orders = frappe.get_all("Payment Order Summary", - { - 'docstatus': 1, - 'payment_status': 'Initiated' - }, - pluck= 'parent', distinct='parent') + orders = frappe.get_all( + "Payment Order Summary", + {"docstatus": 1, "payment_status": "Initiated"}, + pluck="parent", + distinct="parent", + ) for order in orders: try: - frappe.enqueue( - get_payment_status, docname= order, queue="short" - ) + frappe.enqueue(get_payment_status, docname=order, queue="short") except: - frappe.log_error(title="Error in Payment Order Status Cron", message=frappe.get_traceback()) \ No newline at end of file + frappe.log_error( + title="Error in Payment Order Status Cron", + message=frappe.get_traceback(), + ) From 8d247b6184d56c28a8f679da266336e39ef622b0 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Thu, 19 Dec 2024 16:27:40 +0530 Subject: [PATCH 21/83] refactor: remove unused file from doc_events --- .../doc_events/purchase_invoice.py | 56 ------------------- 1 file changed, 56 deletions(-) delete mode 100644 india_banking/india_banking/doc_events/purchase_invoice.py diff --git a/india_banking/india_banking/doc_events/purchase_invoice.py b/india_banking/india_banking/doc_events/purchase_invoice.py deleted file mode 100644 index fbe1df6..0000000 --- a/india_banking/india_banking/doc_events/purchase_invoice.py +++ /dev/null @@ -1,56 +0,0 @@ -import frappe -from frappe.utils import today - - -def hold_invoice_for_payment(self, method): - self.block_invoice("Hold on Payments") - -def on_update_after_submit(self, method): - unblock_bulk_release(self, method) - -def unblock_bulk_release(self, method): - if self.on_hold == 1 and self.hold_comment == "Hold on Payments": - if self.release_using_data_import == 1: - self.db_set("on_hold", 0) - self.db_set("release_date", None) - self.db_set("hold_comment", "Released using data import") - -@frappe.whitelist() -def make_payment_order(source_name, target_doc=None): - from frappe.model.mapper import get_mapped_doc - - def set_missing_values(source, target): - target.payment_order_type = "Purchase Invoice" - bank_account = source.bank_account - if not bank_account: - bank_account = frappe.db.get_value("Bank Account", {"party_type": "Supplier", "party": source.supplier, "is_default": 1, "workflow_state": "Approved"}, "name") - - if not bank_account: - frappe.throw(f"{source.supplier} does not have an default & approved bank account") - - target.posting_date = today() - target.append( - "references", - { - "reference_doctype": source.doctype, - "reference_name": source.name, - "amount": source.outstanding_amount, - "supplier": source.supplier, - "mode_of_payment": "Wire Transfer", - "bank_account": bank_account - }, - ) - target.status = "Pending" - - doclist = get_mapped_doc( - "Purchase Invoice", - source_name, - { - "Purchase Invoice": { - "doctype": "Payment Order", - } - }, - target_doc, - set_missing_values, - ) - return doclist \ No newline at end of file From bcea1603bdfeddac5da510bf38efca99358a4112 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 20 Dec 2024 10:01:37 +0530 Subject: [PATCH 22/83] refactor: create custom fields --- india_banking/install.py | 158 ++++++++++++++++++++--- india_banking/overrides/payment_order.py | 14 +- india_banking/patches.txt | 4 +- india_banking/uninstall.py | 22 ++++ 4 files changed, 171 insertions(+), 27 deletions(-) diff --git a/india_banking/install.py b/india_banking/install.py index 91140c6..c244e4f 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -19,7 +19,10 @@ def after_install(): def make_custom_fields(): create_payment_request_custom_fields() create_payment_custom_fields_in_payment_order() + create_payment_entry_custom_fields() create_bank_doc_custom_fields() + create_bank_account_custom_fields() + create_journal_entry_custom_fields() def create_property_setter(): @@ -36,7 +39,7 @@ def toggle_payment_request_creation(allow=True): def create_bank_doc_custom_fields(): - click.secho("* Installing Bank DOC Custom Fields...") + click.secho("* Installing Custom Fields in a Bank...") fields = { "Bank": [ { @@ -45,7 +48,45 @@ def create_bank_doc_custom_fields(): "fieldtype": "Check", "read_only": 1, "insert_after": "bank", - } + }, + ] + } + + create_custom_fields(fields) + + +def create_journal_entry_custom_fields(): + click.secho("* Installing Custom Fields in a Journal Entry...") + fields = { + "Journal Entry Account": [ + { + "label": "Payment Details", + "fieldname": "payment_details", + "fieldtype": "Section Break", + "insert_after": "against_account", + }, + { + "label": "Payment Status", + "fieldname": "payment_status", + "fieldtype": "Select", + "options": "\nOrdered\nPaid\nFailed", + "no_copy": 1, + "read_only": 1, + "insert_after": "payment_details", + }, + { + "fieldname": "payment_details_column_break", + "fieldtype": "Column Break", + "insert_after": "payment_status", + }, + { + "label": "Reference Number", + "fieldname": "reference_number", + "fieldtype": "Data", + "no_copy": 1, + "read_only": 1, + "insert_after": "payment_details_column_break", + }, ] } @@ -170,24 +211,42 @@ def create_payment_request_property_setter(): make_property_setter(_property) -def delete_payment_request_property_setter(): - data = [ - ( - _property.get("doctype", ""), - _property.get("property", ""), - _property.get("fieldname", ""), - ) - for _property in properties - ] - for doctype, property, fieldname in data: - click.echo(f"* Updating {doctype} Property") - delete_property_setter(doctype, property, fieldname) - - def create_payment_custom_fields_in_payment_order(): click.secho("* Installing Payment Fields in Payment Order") fields = { "Payment Order": [ + { + "label": "ICICI Bank Api Info", + "fieldname": "icici_bank_api_info", + "fieldtype": "Section Break", + "insert_after": "account", + }, + { + "label": "Unique ID", + "fieldname": "unique_id", + "fieldtype": "Data", + "hidden": 1, + "insert_after": "icici_bank_api_info", + }, + { + "fieldtype": "Column Break", + "fieldname": "bank_api_info_column_break", + "insert_after": "unique_id", + }, + { + "label": "File Sequence Number", + "fieldname": "file_sequence_number", + "fieldtype": "Data", + "hidden": 1, + "insert_after": "bank_api_info_column_break", + }, + { + "label": "File Reference Id", + "fieldname": "file_reference_id", + "hidden": 1, + "fieldtype": "Data", + "insert_after": "file_sequence_number", + }, { "label": "Get Summary", "fieldname": "get_summary", @@ -297,6 +356,73 @@ def create_payment_custom_fields_in_payment_order(): create_custom_fields(fields) +def create_payment_entry_custom_fields(): + click.secho("* Installing Custom Payment Fields in a Payment Entry...") + fields = { + "Payment Entry": [ + { + "fieldname": "source_section", + "fieldtype": "Section Break", + "insert_after": "title", + }, + { + "label": "Source Doctype", + "fieldname": "source_doctype", + "fieldtype": "Link", + "options": "DocType", + "read_only": 1, + "insert_after": "source_section", + }, + { + "fieldtype": "Column Break", + "fieldname": "source_column", + "insert_after": "source_doctype", + }, + { + "label": "Source Name", + "fieldname": "source_name", + "fieldtype": "Dynamic Link", + "options": "source_doctype", + "read_only": 1, + "insert_after": "source_column", + }, + ] + } + + create_custom_fields(fields) + + +def create_bank_account_custom_fields(): + click.secho("* Installing Custom Payment Fields in a Bank Account...") + fields = { + "Bank Account": [ + { + "label": "Mobile Number", + "fieldname": "mobile_number", + "insert_after": "iban", + "fieldtype": "Data", + }, + { + "label": "Email", + "fieldname": "email", + "fieldtype": "Data", + "options": "Email", + "insert_after": "mobile_number", + "reqd": 1, + }, + { + "label": "Bank Balance", + "fieldname": "bank_balance", + "fieldtype": "Currency", + "insert_after": "bank_account_no", + "read_only": 1, + }, + ] + } + + create_custom_fields(fields) + + def toggle_reqd_for_reference_in_payment_order(reqd=False): frappe.db.set_value( "DocField", diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index a3560ac..84bfe59 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -19,15 +19,13 @@ def before_submit(self): def validate_bank_payment_request(self): if self.references: for ref in self.references: - if ref.bank_payment_request: - bank_payment_request = frappe.get_doc( - "Bank Payment Request", ref.bank_payment_request + if ref.payment_request: + payment_request = frappe.get_doc( + "Payment Request", ref.payment_request ) - if bank_payment_request.grand_total != ref.amount: - link = get_link_to_form( - "Bank Payment Request", ref.bank_payment_request - ) - message = f"The amount in #Row{ref.idx} does not match the amount of the bank payment request -{link}. The Difference is {ref.amount - bank_payment_request.grand_total}" + if payment_request.grand_total != ref.amount: + link = get_link_to_form("Payment Request", ref.payment_request) + message = f"The amount in #Row{ref.idx} does not match the amount of the Payment Request -{link}. The Difference is {ref.amount - payment_request.grand_total}" frappe.throw(title="Invalid Amount", msg=message) @frappe.whitelist() diff --git a/india_banking/patches.txt b/india_banking/patches.txt index 7e09a64..f15c3a9 100644 --- a/india_banking/patches.txt +++ b/india_banking/patches.txt @@ -3,6 +3,4 @@ # Read docs to understand patches: https://frappeframework.com/docs/v14/user/en/database-migrations [post_model_sync] -# Patches added in this section will be executed after doctypes are migrated - -india_banking.patches.v1_0.create_default \ No newline at end of file +# Patches added in this section will be executed after doctypes are migrated \ No newline at end of file diff --git a/india_banking/uninstall.py b/india_banking/uninstall.py index 84365b0..58b78cb 100644 --- a/india_banking/uninstall.py +++ b/india_banking/uninstall.py @@ -18,6 +18,23 @@ def before_uninstall(): def delete_custom_fields(): fieldnames = { + "Journal Entry Account": [ + "payment_details", + "payment_status", + "payment_details_column_break", + "reference_number", + ], + "Bank Account": [ + "mobile_number", + "email", + "bank_balance", + ], + "Payment Entry": [ + "source_section", + "source_doctype", + "source_column", + "source_name", + ], "Payment Request": [ "payment_type", "is_adhoc", @@ -28,6 +45,11 @@ def delete_custom_fields(): "payment_term", ], "Payment Order": [ + "icici_bank_api_info", + "unique_id", + "bank_api_info_column_break", + "file_sequence_number", + "file_reference_id", "get_summary", "payment_summary", "is_party_wise", From ca336874ba24374e6d5a9e9d7647ce6babc6375f Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 20 Dec 2024 11:38:38 +0530 Subject: [PATCH 23/83] refactor: remove customisation exports --- .../india_banking/custom/bank_account.json | 219 --- .../custom/journal_entry_account.json | 281 ---- .../india_banking/custom/payment_entry.json | 302 ---- .../india_banking/custom/payment_order.json | 1140 ------------- .../custom/payment_order_reference.json | 1404 ----------------- .../custom/payment_order_summary.json | 366 ----- india_banking/install.py | 78 +- india_banking/uninstall.py | 34 +- 8 files changed, 66 insertions(+), 3758 deletions(-) delete mode 100644 india_banking/india_banking/custom/bank_account.json delete mode 100644 india_banking/india_banking/custom/journal_entry_account.json delete mode 100644 india_banking/india_banking/custom/payment_entry.json delete mode 100644 india_banking/india_banking/custom/payment_order.json delete mode 100644 india_banking/india_banking/custom/payment_order_reference.json delete mode 100644 india_banking/india_banking/custom/payment_order_summary.json diff --git a/india_banking/india_banking/custom/bank_account.json b/india_banking/india_banking/custom/bank_account.json deleted file mode 100644 index d2ffff9..0000000 --- a/india_banking/india_banking/custom/bank_account.json +++ /dev/null @@ -1,219 +0,0 @@ -{ - "custom_fields": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-07-26 10:00:15.774662", - "default": null, - "depends_on": "eval: doc.is_company_account", - "description": null, - "docstatus": 0, - "dt": "Bank Account", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "bank_balance", - "fieldtype": "Currency", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 22, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "bank_account_no", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Bank Balance", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-07-26 10:00:41.931210", - "modified_by": "Administrator", - "module": null, - "name": "Bank Account-custom_bank_balance", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-06-12 13:07:09.673507", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Bank Account", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "email", - "fieldtype": "Data", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 18, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "mobile_number", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Email", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-06-12 13:07:39.588654", - "modified_by": "Administrator", - "module": null, - "name": "Bank Account-custom_email", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 1, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-06-12 12:08:47.521599", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Bank Account", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "mobile_number", - "fieldtype": "Data", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 17, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "iban", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Mobile Number", - "length": 0, - "mandatory_depends_on": "is_company_account", - "modified": "2024-06-12 12:13:44.684011", - "modified_by": "Administrator", - "module": null, - "name": "Bank Account-custom_mobile_number", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 1, - "unique": 0, - "width": null - } - ], - "custom_perms": [], - "doctype": "Bank Account", - "links": [], - "property_setters": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-07-26 10:00:15.641705", - "default_value": null, - "doc_type": "Bank Account", - "docstatus": 0, - "doctype_or_field": "DocType", - "field_name": null, - "idx": 0, - "is_system_generated": 0, - "modified": "2024-07-26 10:00:15.641705", - "modified_by": "Administrator", - "module": null, - "name": "Bank Account-main-field_order", - "owner": "Administrator", - "property": "field_order", - "property_type": "Data", - "row_name": null, - "value": "[\"account_name\", \"account\", \"bank\", \"account_type\", \"account_subtype\", \"column_break_7\", \"disabled\", \"is_default\", \"is_company_account\", \"company\", \"section_break_11\", \"party_type\", \"column_break_14\", \"party\", \"account_details_section\", \"iban\", \"mobile_number\", \"email\", \"column_break_12\", \"branch_code\", \"bank_account_no\", \"bank_balance\", \"address_and_contact\", \"address_html\", \"column_break_13\", \"contact_html\", \"integration_details_section\", \"integration_id\", \"last_integration_date\", \"column_break_27\", \"mask\"]" - } - ], - "sync_on_migrate": 1 - } \ No newline at end of file diff --git a/india_banking/india_banking/custom/journal_entry_account.json b/india_banking/india_banking/custom/journal_entry_account.json deleted file mode 100644 index fff8631..0000000 --- a/india_banking/india_banking/custom/journal_entry_account.json +++ /dev/null @@ -1,281 +0,0 @@ -{ - "custom_fields": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-09-26 11:23:32.947014", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Journal Entry Account", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "payment_status", - "fieldtype": "Select", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 31, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "custom_payment_details", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Payment Status", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-09-26 11:40:54.591874", - "modified_by": "Administrator", - "module": null, - "name": "Journal Entry Account-custom_payment_status", - "no_copy": 1, - "non_negative": 0, - "options": "\nOrdered\nPaid\nFailed", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-09-26 11:23:33.024275", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Journal Entry Account", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "custom_column_break_twbua", - "fieldtype": "Column Break", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 32, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "payment_status", - "is_system_generated": 0, - "is_virtual": 0, - "label": "", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-09-26 11:23:46.765171", - "modified_by": "Administrator", - "module": null, - "name": "Journal Entry Account-custom_column_break_twbua", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-09-26 11:23:32.863238", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Journal Entry Account", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "custom_payment_details", - "fieldtype": "Section Break", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 30, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "against_account", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Payment Details", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-09-26 11:23:32.863238", - "modified_by": "Administrator", - "module": null, - "name": "Journal Entry Account-custom_payment_details", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-09-25 17:00:35.704486", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Journal Entry Account", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "reference_number", - "fieldtype": "Data", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 33, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "custom_column_break_twbua", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Reference Number", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-09-25 17:00:51.140895", - "modified_by": "Administrator", - "module": null, - "name": "Journal Entry Account-custom_reference_number", - "no_copy": 1, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - } - ], - "custom_perms": [], - "doctype": "Journal Entry Account", - "links": [], - "property_setters": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-09-26 11:52:03.046866", - "default_value": null, - "doc_type": "Journal Entry Account", - "docstatus": 0, - "doctype_or_field": "DocType", - "field_name": null, - "idx": 0, - "is_system_generated": 0, - "modified": "2024-09-26 15:18:51.136835", - "modified_by": "Administrator", - "module": null, - "name": "Journal Entry Account-main-field_order", - "owner": "Administrator", - "property": "field_order", - "property_type": "Data", - "row_name": null, - "value": "[\"account\", \"account_type\", \"col_break1\", \"bank_account\", \"party_type\", \"party\", \"accounting_dimensions_section\", \"cost_center\", \"dimension_col_break\", \"project\", \"currency_section\", \"account_currency\", \"column_break_10\", \"exchange_rate\", \"sec_break1\", \"debit_in_account_currency\", \"debit\", \"col_break2\", \"credit_in_account_currency\", \"credit\", \"reference\", \"reference_type\", \"reference_name\", \"reference_due_date\", \"reference_detail_no\", \"col_break3\", \"is_advance\", \"user_remark\", \"against_account\", \"custom_payment_details\", \"payment_status\", \"custom_column_break_twbua\", \"reference_number\"]" - } - ], - "sync_on_migrate": 1 -} \ No newline at end of file diff --git a/india_banking/india_banking/custom/payment_entry.json b/india_banking/india_banking/custom/payment_entry.json deleted file mode 100644 index 3b58535..0000000 --- a/india_banking/india_banking/custom/payment_entry.json +++ /dev/null @@ -1,302 +0,0 @@ -{ - "custom_fields": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-06 10:08:47.377833", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Entry", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "source_doctype", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 94, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "section_break_kbrcw", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Source Doctype", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-06 10:24:12.549922", - "modified_by": "Administrator", - "module": null, - "name": "Payment Entry-custom_source_doctype", - "no_copy": 0, - "non_negative": 0, - "options": "DocType", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-06 10:08:47.254557", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Entry", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "section_break_kbrcw", - "fieldtype": "Section Break", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 93, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "title", - "is_system_generated": 0, - "is_virtual": 0, - "label": "", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-06 10:24:12.543828", - "modified_by": "Administrator", - "module": null, - "name": "Payment Entry-custom_section_break_kbrcw", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-06 10:15:03.157600", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Entry", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "source_name", - "fieldtype": "Dynamic Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 96, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "column_break_us2ws", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Source Name", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-06 10:15:42.668883", - "modified_by": "Administrator", - "module": null, - "name": "Payment Entry-custom_source_name", - "no_copy": 0, - "non_negative": 0, - "options": "source_doctype", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-06 10:15:03.039193", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Entry", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "column_break_us2ws", - "fieldtype": "Column Break", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 95, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "source_doctype", - "is_system_generated": 0, - "is_virtual": 0, - "label": "", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-06 10:15:42.639934", - "modified_by": "Administrator", - "module": null, - "name": "Payment Entry-custom_column_break_us2ws", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - } - ], - "custom_perms": [], - "doctype": "Payment Entry", - "links": [ - { - "creation": "2016-06-01 14:38:51.012597", - "custom": 0, - "docstatus": 0, - "group": null, - "hidden": 0, - "idx": 1, - "is_child_table": 1, - "link_doctype": "Bank Transaction Payments", - "link_fieldname": "payment_entry", - "modified": "2024-05-02 18:40:38.356912", - "modified_by": "Administrator", - "name": "599444a2bb", - "owner": "Administrator", - "parent": "Payment Entry", - "parent_doctype": "Bank Transaction", - "parentfield": "links", - "parenttype": "DocType", - "table_fieldname": "payment_entries" - } - ], - "property_setters": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-05-06 10:59:41.145834", - "default_value": null, - "doc_type": "Payment Entry", - "docstatus": 0, - "doctype_or_field": "DocType", - "field_name": null, - "idx": 0, - "is_system_generated": 0, - "modified": "2024-05-06 10:59:41.145834", - "modified_by": "Administrator", - "module": null, - "name": "Payment Entry-main-field_order", - "owner": "Administrator", - "property": "field_order", - "property_type": "Data", - "row_name": null, - "value": "[\"type_of_payment\", \"naming_series\", \"payment_type\", \"payment_order_status\", \"column_break_5\", \"posting_date\", \"company\", \"mode_of_payment\", \"party_section\", \"party_type\", \"party\", \"party_name\", \"book_advance_payments_in_separate_party_account\", \"column_break_11\", \"bank_account\", \"party_bank_account\", \"contact_person\", \"contact_email\", \"payment_accounts_section\", \"party_balance\", \"paid_from\", \"paid_from_account_type\", \"paid_from_account_currency\", \"paid_from_account_balance\", \"column_break_18\", \"paid_to\", \"paid_to_account_type\", \"paid_to_account_currency\", \"paid_to_account_balance\", \"payment_amounts_section\", \"paid_amount\", \"paid_amount_after_tax\", \"source_exchange_rate\", \"base_paid_amount\", \"base_paid_amount_after_tax\", \"column_break_21\", \"received_amount\", \"received_amount_after_tax\", \"target_exchange_rate\", \"base_received_amount\", \"base_received_amount_after_tax\", \"section_break_14\", \"get_outstanding_invoices\", \"get_outstanding_orders\", \"references\", \"section_break_34\", \"total_allocated_amount\", \"base_total_allocated_amount\", \"set_exchange_gain_loss\", \"column_break_36\", \"unallocated_amount\", \"difference_amount\", \"write_off_difference_amount\", \"taxes_and_charges_section\", \"purchase_taxes_and_charges_template\", \"sales_taxes_and_charges_template\", \"column_break_55\", \"apply_tax_withholding_amount\", \"tax_withholding_category\", \"section_break_56\", \"taxes\", \"section_break_60\", \"base_total_taxes_and_charges\", \"column_break_61\", \"total_taxes_and_charges\", \"deductions_or_loss_section\", \"deductions\", \"transaction_references\", \"reference_no\", \"column_break_23\", \"reference_date\", \"clearance_date\", \"accounting_dimensions_section\", \"project\", \"dimension_col_break\", \"cost_center\", \"section_break_12\", \"status\", \"custom_remarks\", \"remarks\", \"base_in_words\", \"column_break_16\", \"letter_head\", \"print_heading\", \"bank\", \"bank_account_no\", \"payment_order\", \"in_words\", \"subscription_section\", \"auto_repeat\", \"amended_from\", \"title\", \"section_break_kbrcw\", \"source_doctype\", \"column_break_us2ws\", \"source_name\"]" - } - ], - "sync_on_migrate": 1 -} \ No newline at end of file diff --git a/india_banking/india_banking/custom/payment_order.json b/india_banking/india_banking/custom/payment_order.json deleted file mode 100644 index 990aecc..0000000 --- a/india_banking/india_banking/custom/payment_order.json +++ /dev/null @@ -1,1140 +0,0 @@ -{ - "custom_fields": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-06-13 12:59:36.145405", - "default": "1", - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "is_party_wise", - "fieldtype": "Check", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 25, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "default_mode_of_transfer", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Is Party Wise", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-06-13 13:03:40.916902", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order-custom_is_party_wise", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-08-03 15:10:33.301170", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "summary", - "fieldtype": "Table", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 26, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "is_party_wise", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Summary", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-06-13 12:59:50.876019", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order-summary", - "no_copy": 0, - "non_negative": 0, - "options": "Payment Order Summary", - "owner": "vignesh@aerle.in", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-06-12 10:53:44.062876", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order", - "fetch_from": "company_bank_account.mobile_number", - "fetch_if_empty": 1, - "fieldname": "mobile_number", - "fieldtype": "Data", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 10, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "company_bank_account_name", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Mobile Number", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-06-12 10:53:55.708751", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order-custom_mobile_number", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-06-12 10:35:05.940875", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order", - "fetch_from": "company_bank_account.account_name", - "fetch_if_empty": 1, - "fieldname": "company_bank_account_name", - "fieldtype": "Data", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 9, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "company_bank_account", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Company Bank Account Name", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-06-12 10:37:22.786361", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order-custom_company_bank_account_name", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-16 18:13:22.108841", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "custom_column_break_wkolr", - "fieldtype": "Column Break", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 17, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "unique_id", - "is_system_generated": 0, - "is_virtual": 0, - "label": "", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-18 14:39:05.981411", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order-custom_column_break_wkolr", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-18 14:38:33.293739", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "file_reference_id", - "fieldtype": "Data", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 20, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "section_break_5", - "is_system_generated": 0, - "is_virtual": 0, - "label": "File Reference Id", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-18 14:39:05.971861", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order-custom_file_reference_id", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-17 12:39:42.524744", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "file_sequence_number", - "fieldtype": "Data", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 18, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "custom_column_break_wkolr", - "is_system_generated": 0, - "is_virtual": 0, - "label": "File Sequence Number", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-17 12:39:59.591910", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order-custom_file_sequence_number", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-16 18:13:22.004590", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "custom_icici_bank_api_info", - "fieldtype": "Section Break", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 15, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "company_ifsc", - "is_system_generated": 0, - "is_virtual": 0, - "label": "ICICI Bank Api Info", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-16 18:13:22.004590", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order-custom_icici_bank_api_info", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-16 18:00:02.696339", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "unique_id", - "fieldtype": "Data", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 16, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "custom_icici_bank_api_info", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Unique ID", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-16 18:00:28.600150", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order-custom_unique_id", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-16 16:57:04.682134", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order", - "fetch_from": "company_bank_account.branch_code", - "fetch_if_empty": 1, - "fieldname": "company_ifsc", - "fieldtype": "Data", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 14, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "company_account_number", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Company IFSC", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-16 17:00:44.514956", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order-custom_company_ifsc", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-16 16:57:04.585149", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order", - "fetch_from": "company_bank_account.bank_account_no", - "fetch_if_empty": 1, - "fieldname": "company_account_number", - "fieldtype": "Data", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 13, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "account", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Company Account Number", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-16 17:00:27.498109", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order-custom_company_account_number", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-08-03 15:10:33.628889", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "total", - "fieldtype": "Currency", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 27, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "summary", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Total", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-08-03 15:10:33.628889", - "modified_by": "vignesh@aerle.in", - "module": null, - "name": "Payment Order-total", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "vignesh@aerle.in", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-08-03 15:10:33.037995", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "default_mode_of_transfer", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 24, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "payment_summary", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Default Mode of Transfer", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-08-03 15:10:33.037995", - "modified_by": "vignesh@aerle.in", - "module": null, - "name": "Payment Order-default_mode_of_transfer", - "no_copy": 0, - "non_negative": 0, - "options": "Mode of Transfer", - "owner": "vignesh@aerle.in", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-08-03 15:10:32.778633", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "payment_summary", - "fieldtype": "Section Break", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 23, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "get_summary", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Payment Summary", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-08-03 15:10:32.778633", - "modified_by": "vignesh@aerle.in", - "module": null, - "name": "Payment Order-payment_summary", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "vignesh@aerle.in", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-08-03 15:10:32.514610", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "get_summary", - "fieldtype": "Button", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 22, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "references", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Get Summary", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-08-03 15:10:32.514610", - "modified_by": "vignesh@aerle.in", - "module": null, - "name": "Payment Order-get_summary", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "vignesh@aerle.in", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-08-03 15:10:31.961274", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 7, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "posting_date", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Status", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-08-03 15:10:31.961274", - "modified_by": "vignesh@aerle.in", - "module": null, - "name": "Payment Order-status", - "no_copy": 0, - "non_negative": 0, - "options": "Pending\nPending Approval\nPartially Approved\nApproved\nPartially Initiated\nInitiated\nRejected\nFailed", - "owner": "vignesh@aerle.in", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - } - ], - "custom_perms": [], - "doctype": "Payment Order", - "links": [], - "property_setters": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-05-27 18:52:21.352940", - "default_value": null, - "doc_type": "Payment Order", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "account", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-09-24 16:04:28.062627", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order-account-fetch_if_empty", - "owner": "Administrator", - "property": "fetch_if_empty", - "property_type": "Check", - "row_name": null, - "value": "1" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-05-27 18:52:21.313964", - "default_value": null, - "doc_type": "Payment Order", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "company_bank", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-09-24 16:04:28.053366", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order-company_bank-fetch_if_empty", - "owner": "Administrator", - "property": "fetch_if_empty", - "property_type": "Check", - "row_name": null, - "value": "1" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-08-07 01:01:06.386719", - "default_value": null, - "doc_type": "Payment Order", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "party", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-09-24 16:04:28.044181", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order-party-in_list_view", - "owner": "Administrator", - "property": "in_list_view", - "property_type": "Check", - "row_name": null, - "value": "0" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-05-28 18:35:17.959511", - "default_value": null, - "doc_type": "Payment Order", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "party", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-09-24 16:04:28.034918", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order-party-depends_on", - "owner": "Administrator", - "property": "depends_on", - "property_type": "Data", - "row_name": null, - "value": "eval: doc.payment_order_type=='Bank Payment Request';" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-08-03 14:39:59.895470", - "default_value": null, - "doc_type": "Payment Order", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "party", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-09-24 16:04:28.025467", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order-party-hidden", - "owner": "Administrator", - "property": "hidden", - "property_type": "Check", - "row_name": null, - "value": "1" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-06-13 12:59:36.039608", - "default_value": null, - "doc_type": "Payment Order", - "docstatus": 0, - "doctype_or_field": "DocType", - "field_name": null, - "idx": 0, - "is_system_generated": 0, - "modified": "2024-09-24 16:04:28.016210", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order-main-field_order", - "owner": "Administrator", - "property": "field_order", - "property_type": "Data", - "row_name": null, - "value": "[\"naming_series\", \"company\", \"payment_order_type\", \"party\", \"column_break_2\", \"posting_date\", \"status\", \"company_bank_account\", \"company_bank_account_name\", \"mobile_number\", \"company_bank\", \"account\", \"company_account_number\", \"company_ifsc\", \"custom_icici_bank_api_info\", \"unique_id\", \"custom_column_break_wkolr\", \"file_sequence_number\", \"section_break_5\", \"file_reference_id\", \"references\", \"get_summary\", \"payment_summary\", \"default_mode_of_transfer\", \"is_party_wise\", \"summary\", \"total\", \"amended_from\"]" - } - ], - "sync_on_migrate": 1 -} \ No newline at end of file diff --git a/india_banking/india_banking/custom/payment_order_reference.json b/india_banking/india_banking/custom/payment_order_reference.json deleted file mode 100644 index eafb92c..0000000 --- a/india_banking/india_banking/custom/payment_order_reference.json +++ /dev/null @@ -1,1404 +0,0 @@ -{ - "custom_fields": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-07-29 18:09:02.824275", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": "", - "fetch_if_empty": 0, - "fieldname": "payment_term", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 8, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "journal_entry_account", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Payment Term", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-09-26 12:54:16.790168", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-custom_payment_term", - "no_copy": 0, - "non_negative": 0, - "options": "Payment Term", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-09-26 12:52:04.567808", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "journal_entry_account", - "fieldtype": "Data", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 7, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "payroll_entry", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Journal Entry Account", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-09-26 12:54:16.782947", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-custom_journal_entry_account", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-09-24 19:28:57.586383", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "payroll_entry", - "fieldtype": "Data", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 6, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "bank_payment_request", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Payroll Entry", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-09-24 19:31:40.577081", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-custom_payroll_entry", - "no_copy": 0, - "non_negative": 0, - "options": "Payroll Entry", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-07-29 18:24:11.962532", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "remarks", - "fieldtype": "Text", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 13, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "overdue_from_credit_period", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Remarks", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-07-29 18:26:03.088665", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-custom_remarks", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-07-29 18:21:38.903103", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "overdue_from_credit_period", - "fieldtype": "Float", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 12, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "credit_period", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Overdue From Credit Period", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-07-29 18:26:03.060300", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-custom_overdue_from_credit_period", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-07-29 18:21:38.822021", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "credit_period", - "fieldtype": "Float", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 11, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "payment_due_date", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Credit Period", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-07-29 18:25:52.975058", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-custom_credit_period", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-07-29 18:21:38.726551", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "payment_due_date", - "fieldtype": "Date", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 10, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "expense_head", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Payment Due Date", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-07-29 18:25:43.020277", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-custom_payment_due_date", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-07-29 18:23:09.265942", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "expense_head", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 9, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "payment_term", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Expense Head", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-07-29 18:25:25.389883", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-custom_expense_head", - "no_copy": 0, - "non_negative": 0, - "options": "Account", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-17 11:12:32.749779", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": "bank_account.bank_account_no", - "fetch_if_empty": 0, - "fieldname": "bank_account_no", - "fieldtype": "Data", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 28, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "bank", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Bank Account No", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-17 14:40:21.812077", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-custom_bank_account_no", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-17 11:11:20.648981", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": "bank_account.bank", - "fetch_if_empty": 0, - "fieldname": "bank", - "fieldtype": "Link", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 27, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "bank_account", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Bank", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-17 14:40:21.784101", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-custom_bank", - "no_copy": 0, - "non_negative": 0, - "options": "Bank", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-17 14:36:41.682699", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "payment_entry", - "fieldtype": "Link", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 4, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "amount", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Payment Entry", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-17 14:37:00.361062", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-custom_payment_entry", - "no_copy": 0, - "non_negative": 0, - "options": "Payment Entry", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-17 11:19:12.943124", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": "bank_account.branch_code", - "fetch_if_empty": 0, - "fieldname": "branch_code", - "fieldtype": "Data", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 30, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "account_name", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Branch Code", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-17 11:19:36.873686", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-custom_branch_code", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-17 11:16:16.393444", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": "bank_account.account_name", - "fetch_if_empty": 0, - "fieldname": "account_name", - "fieldtype": "Data", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 29, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "bank_account_no", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Account Name", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-17 11:16:51.513308", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-custom_account_name", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-16 17:32:46.771539", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "party_name", - "fieldtype": "Data", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 17, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "party", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Party Name", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-16 17:33:04.682680", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-custom_party_name", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-08-03 14:35:14.051124", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "is_adhoc", - "fieldtype": "Check", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 21, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "payment_request", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Is AdHoc", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-06 11:34:50.974415", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-is_adhoc", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-04-30 20:37:48.407204", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "bank_payment_request", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 5, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "payment_entry", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Bank Payment Request", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-06 11:34:50.966919", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-custom_bank_payment_request", - "no_copy": 0, - "non_negative": 0, - "options": "Bank Payment Request", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-08-07 02:32:24.476855", - "default": null, - "depends_on": "eval:doc.party_type == \"Supplier\"", - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "tax_withholding_category", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 19, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "supplier", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Tax Withholding Category", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-08-07 02:32:24.476855", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-tax_withholding_category", - "no_copy": 0, - "non_negative": 0, - "options": "Tax Withholding Category", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-08-07 01:22:18.430470", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "project", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 24, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "cost_center", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Project", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-08-07 01:22:18.430470", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-project", - "no_copy": 0, - "non_negative": 0, - "options": "Project", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-08-07 01:22:18.108011", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "cost_center", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 23, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "mode_of_payment", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Cost Center", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-08-07 01:22:18.108011", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-cost_center", - "no_copy": 0, - "non_negative": 0, - "options": "Cost Center", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-08-07 01:03:35.294570", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "party", - "fieldtype": "Dynamic Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 16, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "party_type", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Party", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-08-07 01:03:35.294570", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-party", - "no_copy": 0, - "non_negative": 0, - "options": "party_type", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-08-07 01:03:34.994784", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Reference", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "party_type", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 15, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "column_break_4", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Party Type", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-08-07 01:03:34.994784", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-party_type", - "no_copy": 0, - "non_negative": 0, - "options": "DocType", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - } - ], - "custom_perms": [], - "doctype": "Payment Order Reference", - "links": [], - "property_setters": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-09-26 12:52:15.355303", - "default_value": null, - "doc_type": "Payment Order Reference", - "docstatus": 0, - "doctype_or_field": "DocType", - "field_name": null, - "idx": 0, - "is_system_generated": 0, - "modified": "2024-09-26 12:52:15.355303", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-main-field_order", - "owner": "Administrator", - "property": "field_order", - "property_type": "Data", - "row_name": null, - "value": "[\"reference_doctype\", \"reference_name\", \"amount\", \"payment_entry\", \"bank_payment_request\", \"payroll_entry\", \"custom_journal_entry_account\", \"payment_term\", \"expense_head\", \"payment_due_date\", \"credit_period\", \"overdue_from_credit_period\", \"remarks\", \"column_break_4\", \"party_type\", \"party\", \"party_name\", \"supplier\", \"tax_withholding_category\", \"payment_request\", \"is_adhoc\", \"mode_of_payment\", \"cost_center\", \"project\", \"bank_account_details\", \"bank_account\", \"bank\", \"bank_account_no\", \"account_name\", \"branch_code\", \"column_break_10\", \"account\", \"payment_reference\"]" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-08-07 01:06:14.471670", - "default_value": null, - "doc_type": "Payment Order Reference", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "supplier", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-09-26 12:20:54.525889", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-supplier-in_standard_filter", - "owner": "Administrator", - "property": "in_standard_filter", - "property_type": "Check", - "row_name": null, - "value": "0" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-08-07 01:06:14.613460", - "default_value": null, - "doc_type": "Payment Order Reference", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "supplier", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-09-26 12:20:54.516355", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-supplier-hidden", - "owner": "Administrator", - "property": "hidden", - "property_type": "Check", - "row_name": null, - "value": "1" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-05-13 18:39:43.258049", - "default_value": null, - "doc_type": "Payment Order Reference", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "bank_account", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-09-26 12:20:54.506593", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Reference-bank_account-mandatory_depends_on", - "owner": "Administrator", - "property": "mandatory_depends_on", - "property_type": "Data", - "row_name": null, - "value": "" - } - ], - "sync_on_migrate": 1 -} \ No newline at end of file diff --git a/india_banking/india_banking/custom/payment_order_summary.json b/india_banking/india_banking/custom/payment_order_summary.json deleted file mode 100644 index 092a536..0000000 --- a/india_banking/india_banking/custom/payment_order_summary.json +++ /dev/null @@ -1,366 +0,0 @@ -{ - "custom_fields": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-06-12 13:09:09.810532", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Summary", - "fetch_from": "bank_account.email", - "fetch_if_empty": 1, - "fieldname": "email", - "fieldtype": "Data", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 14, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "branch_code", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Email", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-06-12 13:11:59.369273", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Summary-custom_email", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-17 14:49:17.055975", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Summary", - "fetch_from": "bank_account.branch_code", - "fetch_if_empty": 0, - "fieldname": "branch_code", - "fieldtype": "Data", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 13, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "account_name", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Branch Code", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-17 14:54:11.602016", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Summary-custom_branch_code", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-17 14:49:16.984514", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Summary", - "fetch_from": "bank_account.account_name", - "fetch_if_empty": 0, - "fieldname": "account_name", - "fieldtype": "Data", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 12, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "bank_account_no", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Account Name", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-17 14:53:53.554332", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Summary-custom_account_name", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-17 14:49:16.913703", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Summary", - "fetch_from": "bank_account.bank_account_no", - "fetch_if_empty": 0, - "fieldname": "bank_account_no", - "fieldtype": "Data", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 11, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "bank", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Bank Account No", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-17 14:53:19.240682", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Summary-custom_bank_account_no", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-05-17 14:49:16.831802", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Payment Order Summary", - "fetch_from": "bank_account.bank", - "fetch_if_empty": 0, - "fieldname": "bank", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 10, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "reference_number", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Bank", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-05-17 14:52:30.145088", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Summary-custom_bank", - "no_copy": 0, - "non_negative": 0, - "options": "Bank", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - } - ], - "custom_perms": [], - "doctype": "Payment Order Summary", - "links": [], - "property_setters": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-06-12 13:11:00.016227", - "default_value": null, - "doc_type": "Payment Order Summary", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "reference_name", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-09-24 16:04:27.906223", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Summary-reference_name-options", - "owner": "Administrator", - "property": "options", - "property_type": "Text", - "row_name": null, - "value": "" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-06-12 13:12:29.473546", - "default_value": null, - "doc_type": "Payment Order Summary", - "docstatus": 0, - "doctype_or_field": "DocType", - "field_name": null, - "idx": 0, - "is_system_generated": 0, - "modified": "2024-09-24 16:04:27.896499", - "modified_by": "Administrator", - "module": null, - "name": "Payment Order Summary-main-field_order", - "owner": "Administrator", - "property": "field_order", - "property_type": "Data", - "row_name": null, - "value": "[\"party_type\", \"party\", \"amount\", \"banking_section\", \"mode_of_transfer\", \"payment_status\", \"bank_account\", \"message\", \"reference_number\", \"bank\", \"bank_account_no\", \"account_name\", \"branch_code\", \"email\", \"accounting_section\", \"account\", \"tax_withholding_category\", \"payment_entry\", \"reference_doctype\", \"reference_name\", \"accounting_dimensions_section\", \"cost_center\", \"project\", \"payment_initiated\", \"payment_date\"]" - } - ], - "sync_on_migrate": 1 -} \ No newline at end of file diff --git a/india_banking/install.py b/india_banking/install.py index c244e4f..8ed8423 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -8,6 +8,7 @@ def after_install(): + click.secho("* Updating India Banking Customisations") toggle_payment_request_creation(True) make_custom_fields() create_property_setter() @@ -17,11 +18,11 @@ def after_install(): def make_custom_fields(): - create_payment_request_custom_fields() - create_payment_custom_fields_in_payment_order() - create_payment_entry_custom_fields() create_bank_doc_custom_fields() create_bank_account_custom_fields() + create_payment_request_custom_fields() + create_payment_order_custom_fields() + create_payment_entry_custom_fields() create_journal_entry_custom_fields() @@ -31,7 +32,9 @@ def create_property_setter(): def toggle_payment_request_creation(allow=True): click.secho( - "* {} Payment Request Creation...".format("Enabling" if allow else "Disabling") + " -> {} Payment Request Creation...".format( + "Enabling" if allow else "Disabling" + ) ) frappe.db.set_value( "DocType", "Payment Request", {"in_create": not allow, "track_changes": allow} @@ -39,7 +42,7 @@ def toggle_payment_request_creation(allow=True): def create_bank_doc_custom_fields(): - click.secho("* Installing Custom Fields in a Bank...") + click.secho(" -> Installing Custom Fields in a Bank...") fields = { "Bank": [ { @@ -56,7 +59,7 @@ def create_bank_doc_custom_fields(): def create_journal_entry_custom_fields(): - click.secho("* Installing Custom Fields in a Journal Entry...") + click.secho(" -> Installing Custom Fields in a Journal Entry...") fields = { "Journal Entry Account": [ { @@ -94,7 +97,7 @@ def create_journal_entry_custom_fields(): def create_supplier_custom_fields(): - click.secho("* Installing Supplier Custom Fields...") + click.secho(" -> Installing Custom Fields Supplier...") fields = { "Supplier": [ { @@ -111,7 +114,7 @@ def create_supplier_custom_fields(): def create_payment_request_custom_fields(): - click.secho("* Installing Payment and Tax Custom Fields in Payment Request") + click.secho(" -> Installing Custom Fields in a Payment Request") fields = { "Payment Request": [ { @@ -185,34 +188,37 @@ def create_payment_request_custom_fields(): create_custom_fields(fields) -properties = [ - { - "doctype_or_field": "DocField", - "doctype": "Payment Request", - "fieldname": "grand_total", - "property": "read_only", - "property_type": "Check", - "value": 1, - }, - { - "doctype_or_field": "DocField", - "doctype": "Payment Request", - "fieldname": "grand_total", - "property": "reqd", - "property_type": "Check", - "value": 0, - }, -] +properties = { + "Payment Request": [ + { + "doctype_or_field": "DocField", + "doctype": "Payment Request", + "fieldname": "grand_total", + "property": "read_only", + "property_type": "Check", + "value": 1, + }, + { + "doctype_or_field": "DocField", + "doctype": "Payment Request", + "fieldname": "grand_total", + "property": "reqd", + "property_type": "Check", + "value": 0, + }, + ] +} def create_payment_request_property_setter(): - for _property in properties: - click.echo(f'* Updating {_property.get("doctype", "")} Property') - make_property_setter(_property) + for doctype in properties.keys(): + click.echo(f" -> Updating {doctype} Field Properties") + for _property in properties.get(doctype): + make_property_setter(_property) -def create_payment_custom_fields_in_payment_order(): - click.secho("* Installing Payment Fields in Payment Order") +def create_payment_order_custom_fields(): + click.secho(" -> Installing Custom Fields in a Payment Order") fields = { "Payment Order": [ { @@ -253,6 +259,12 @@ def create_payment_custom_fields_in_payment_order(): "fieldtype": "Button", "insert_after": "references", }, + { + "label": "Default Mode of Transfer", + "fieldname": "default_mode_of_transfer", + "fieldtype": "Link", + "options": "Mode of Transfer", + }, { "label": "Payment Summary", "fieldname": "payment_summary", @@ -357,7 +369,7 @@ def create_payment_custom_fields_in_payment_order(): def create_payment_entry_custom_fields(): - click.secho("* Installing Custom Payment Fields in a Payment Entry...") + click.secho(" -> Installing Custom Fields in a Payment Entry...") fields = { "Payment Entry": [ { @@ -393,7 +405,7 @@ def create_payment_entry_custom_fields(): def create_bank_account_custom_fields(): - click.secho("* Installing Custom Payment Fields in a Bank Account...") + click.secho(" -> Installing Custom Fields in a Bank Account...") fields = { "Bank Account": [ { diff --git a/india_banking/uninstall.py b/india_banking/uninstall.py index 58b78cb..2b69dd3 100644 --- a/india_banking/uninstall.py +++ b/india_banking/uninstall.py @@ -17,6 +17,7 @@ def before_uninstall(): def delete_custom_fields(): + click.secho("* Removing India Banking Customizations") fieldnames = { "Journal Entry Account": [ "payment_details", @@ -43,6 +44,8 @@ def delete_custom_fields(): "apply_tax_withholding_amount", "tax_withholding_category", "payment_term", + "remarks", + "remark_section", ], "Payment Order": [ "icici_bank_api_info", @@ -52,6 +55,7 @@ def delete_custom_fields(): "file_reference_id", "get_summary", "payment_summary", + "default_mode_of_transfer", "is_party_wise", "summary", "total", @@ -64,15 +68,17 @@ def delete_custom_fields(): "is_adhoc", "payment_term", "remarks", + "cost_center", + "project", ], "Supplier": ["lei_number"], "Bank": ["is_standard"], } for doctype, fieldnames in fieldnames.items(): - click.secho(f"* Uninstalling Custom Fields from {doctype}") + click.secho(f" -> Uninstalling Custom Fields from {doctype}") for fieldname in fieldnames: - frappe.db.delete("Custom Field", {"name": f"{doctype}-" + fieldname}) + frappe.db.delete("Custom Field", {"dt": doctype, "fieldname": fieldname}) frappe.clear_cache(doctype=doctype) @@ -82,14 +88,16 @@ def delete_propert_setters(): def delete_payment_request_property_setter(): - data = [ - ( - _property.get("doctype", ""), - _property.get("property", ""), - _property.get("fieldname", ""), - ) - for _property in properties - ] - for doctype, property, fieldname in data: - click.echo(f"* Updating {doctype} Property") - delete_property_setter(doctype, property, fieldname) + for doctype in properties.keys(): + data = [ + ( + _property.get("doctype", ""), + _property.get("property", ""), + _property.get("fieldname", ""), + ) + for _property in properties.get(doctype) + ] + + click.echo(f" -> Removing {doctype} Properties") + for doctype, property, fieldname in data: + delete_property_setter(doctype, property, fieldname) From 5c8068ffe60bda33a0f40f0bb77de88213c643c8 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 20 Dec 2024 11:52:54 +0530 Subject: [PATCH 24/83] refactor: remove bank payment request doc --- .../doctype/bank_payment_request/__init__.py | 0 .../bank_payment_request.js | 107 ---- .../bank_payment_request.json | 465 -------------- .../bank_payment_request.py | 582 ------------------ .../bank_payment_request_list.js | 21 - .../test_bank_payment_request.py | 9 - india_banking/overrides/payment_order.py | 5 +- 7 files changed, 1 insertion(+), 1188 deletions(-) delete mode 100644 india_banking/india_banking/doctype/bank_payment_request/__init__.py delete mode 100644 india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js delete mode 100644 india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.json delete mode 100644 india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.py delete mode 100644 india_banking/india_banking/doctype/bank_payment_request/bank_payment_request_list.js delete mode 100644 india_banking/india_banking/doctype/bank_payment_request/test_bank_payment_request.py diff --git a/india_banking/india_banking/doctype/bank_payment_request/__init__.py b/india_banking/india_banking/doctype/bank_payment_request/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js deleted file mode 100644 index d3a77f8..0000000 --- a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) 2024, Aerele Technologies Private Limited and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Bank Payment Request', { - setup: function (frm) { - frm.set_query("party_type", function () { - return { - filters: { - "name": ["in", ["Supplier", "Employee"]] - } - }; - }); - }, - refresh(frm) { - frm.set_query("payment_type", function() { - return { - filters: { - "company": frm.doc.company - } - }; - }); - - frm.set_query("cost_center", function() { - return { - filters: { - "is_group": 0, - "disabled": 0 - } - }; - }); - - frm.set_query("mode_of_payment", function() { - return { - filters: { - "name": "Wire Transfer" - } - }; - }); - - setTimeout(() => { - frm.trigger('toggle_custom_button') - }, 500); - }, - toggle_custom_button(frm){ - if(frm.doc.status == "Initiated") { - frm.remove_custom_button(__('Create Payment Entry')) - } - }, - - company (frm) { - frm.set_query("payment_type", function() { - return { - filters: { - "company": frm.doc.company - } - }; - }); - }, - mode_of_payment (frm) { - var conditions = get_bank_query_conditions(frm); - if (frm.doc.mode_of_payment == "Wire Transfer") { - frm.set_query("bank_account", function() { - return { - filters: conditions - }; - }); - } - }, - party_type (frm) { - var conditions = get_bank_query_conditions(frm); - if (frm.doc.mode_of_payment == "Wire Transfer") { - frm.set_query("bank_account", function() { - return { - filters: conditions - }; - }); - } - }, - party (frm) { - var conditions = get_bank_query_conditions(frm); - if (frm.doc.mode_of_payment == "Wire Transfer") { - frm.set_query("bank_account", function() { - return { - filters: conditions - }; - }); - } - } -}); - -const get_bank_query_conditions = function(frm) { - var conditions = {} - if (frm.doc.party_type) { - conditions["party_type"] = frm.doc.party_type; - } - if (frm.doc.party) { - conditions["party"] = frm.doc.party; - } - if (frm.doc.mode_of_payment == "Wire Transfer") { - frm.set_query("bank_account", function() { - return { - filters: conditions - }; - }); - } - return conditions; -}; \ No newline at end of file diff --git a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.json b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.json deleted file mode 100644 index bf02909..0000000 --- a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.json +++ /dev/null @@ -1,465 +0,0 @@ -{ - "actions": [], - "autoname": "naming_series:", - "creation": "2024-04-30 10:42:52.086041", - "doctype": "DocType", - "engine": "InnoDB", - "field_order": [ - "payment_request_type", - "transaction_date", - "mode_of_payment", - "column_break_2", - "naming_series", - "company", - "payment_type", - "is_adhoc", - "party_details", - "party_type", - "reference_doctype", - "column_break_4", - "party", - "reference_name", - "transaction_details", - "net_total", - "taxes_deducted", - "grand_total", - "column_break_18", - "currency", - "apply_tax_withholding_amount", - "tax_withholding_category", - "party_account_currency", - "bank_account_details", - "bank_account", - "bank", - "bank_account_no", - "account", - "column_break_11", - "iban", - "branch_code", - "swift_number", - "accounting_dimensions_section", - "cost_center", - "dimension_col_break", - "project", - "recipient_and_message", - "print_format", - "email_to", - "subject", - "column_break_9", - "payment_gateway_account", - "status", - "make_sales_invoice", - "section_break_10", - "payment_url", - "section_break_7", - "payment_account", - "payment_order", - "amended_from", - "column_break_uumn", - "payment_gateway", - "payment_channel", - "payment_term" - ], - "fields": [ - { - "default": "Outward", - "fieldname": "payment_request_type", - "fieldtype": "Select", - "label": "Payment Request Type", - "options": "Outward", - "reqd": 1 - }, - { - "default": "Today", - "fieldname": "transaction_date", - "fieldtype": "Date", - "label": "Transaction Date", - "mandatory_depends_on": "eval:doc.mode_of_payment == 'Wire Transfer'" - }, - { - "fieldname": "column_break_2", - "fieldtype": "Column Break" - }, - { - "fieldname": "naming_series", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Series", - "no_copy": 1, - "options": "ACC-BPRQ-.YYYY.-", - "print_hide": 1, - "reqd": 1 - }, - { - "default": "Wire Transfer", - "fieldname": "mode_of_payment", - "fieldtype": "Link", - "label": "Mode of Payment", - "options": "Mode of Payment" - }, - { - "fieldname": "party_details", - "fieldtype": "Section Break", - "label": "Party Details" - }, - { - "fieldname": "party_type", - "fieldtype": "Link", - "label": "Party Type", - "options": "DocType" - }, - { - "fieldname": "party", - "fieldtype": "Dynamic Link", - "label": "Party", - "options": "party_type" - }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, - { - "fieldname": "reference_doctype", - "fieldtype": "Link", - "in_standard_filter": 1, - "label": "Reference Doctype", - "no_copy": 1, - "options": "DocType", - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "fieldname": "reference_name", - "fieldtype": "Dynamic Link", - "in_global_search": 1, - "in_standard_filter": 1, - "label": "Reference Name", - "no_copy": 1, - "options": "reference_doctype", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "transaction_details", - "fieldtype": "Section Break", - "label": "Transaction Details" - }, - { - "description": "Amount in customer's currency", - "fieldname": "grand_total", - "fieldtype": "Currency", - "label": "Amount", - "options": "currency", - "read_only": 1 - }, - { - "fieldname": "column_break_18", - "fieldtype": "Column Break" - }, - { - "fieldname": "currency", - "fieldtype": "Link", - "label": "Transaction Currency", - "options": "Currency", - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "bank_account_details", - "fieldtype": "Section Break", - "label": "Bank Account Details" - }, - { - "fieldname": "bank_account", - "fieldtype": "Link", - "label": "Bank Account", - "mandatory_depends_on": "eval:doc.mode_of_payment == 'Wire Transfer'", - "options": "Bank Account" - }, - { - "fetch_from": "bank_account.bank", - "fieldname": "bank", - "fieldtype": "Link", - "label": "Bank", - "options": "Bank", - "read_only": 1 - }, - { - "fetch_from": "bank_account.bank_account_no", - "fieldname": "bank_account_no", - "fieldtype": "Read Only", - "label": "Bank Account No" - }, - { - "fetch_from": "bank_account.account", - "fieldname": "account", - "fieldtype": "Read Only", - "label": "Account", - "read_only": 1 - }, - { - "fieldname": "column_break_11", - "fieldtype": "Column Break" - }, - { - "fetch_from": "bank_account.iban", - "fieldname": "iban", - "fieldtype": "Read Only", - "label": "IBAN" - }, - { - "fetch_from": "bank_account.branch_code", - "fetch_if_empty": 1, - "fieldname": "branch_code", - "fieldtype": "Read Only", - "label": "Branch Code" - }, - { - "fetch_from": "bank.swift_number", - "fieldname": "swift_number", - "fieldtype": "Read Only", - "label": "SWIFT Number" - }, - { - "collapsible": 1, - "fieldname": "accounting_dimensions_section", - "fieldtype": "Section Break", - "label": "Accounting Dimensions" - }, - { - "fieldname": "cost_center", - "fieldtype": "Link", - "label": "Cost Center", - "options": "Cost Center" - }, - { - "fieldname": "dimension_col_break", - "fieldtype": "Column Break" - }, - { - "fieldname": "project", - "fieldtype": "Link", - "label": "Project", - "options": "Project" - }, - { - "depends_on": "eval: doc.payment_request_type == 'Inward'", - "fieldname": "recipient_and_message", - "fieldtype": "Section Break", - "label": "Recipient Message And Payment Details" - }, - { - "depends_on": "eval: doc.payment_channel != 'Phone'", - "fieldname": "print_format", - "fieldtype": "Select", - "label": "Print Format" - }, - { - "fieldname": "email_to", - "fieldtype": "Data", - "in_global_search": 1, - "label": "To" - }, - { - "depends_on": "eval: doc.payment_channel != 'Phone'", - "fieldname": "subject", - "fieldtype": "Data", - "in_global_search": 1, - "label": "Subject" - }, - { - "fieldname": "column_break_9", - "fieldtype": "Column Break" - }, - { - "depends_on": "eval: doc.payment_request_type == 'Inward'", - "fieldname": "payment_gateway_account", - "fieldtype": "Link", - "label": "Payment Gateway Account", - "options": "Payment Gateway Account" - }, - { - "default": "Draft", - "fieldname": "status", - "fieldtype": "Select", - "hidden": 1, - "in_standard_filter": 1, - "label": "Status", - "options": "\nDraft\nRequested\nInitiated\nPartially Paid\nPayment Ordered\nPaid\nFailed\nCancelled", - "read_only": 1 - }, - { - "default": "0", - "depends_on": "eval:doc.reference_doctype== 'Sales Order'", - "fieldname": "make_sales_invoice", - "fieldtype": "Check", - "hidden": 1, - "label": "Make Sales Invoice", - "read_only": 1 - }, - { - "depends_on": "eval: doc.payment_request_type == 'Inward' || doc.payment_channel != 'Phone'", - "fieldname": "section_break_10", - "fieldtype": "Section Break" - }, - { - "fieldname": "payment_url", - "fieldtype": "Data", - "hidden": 1, - "length": 500, - "options": "URL", - "read_only": 1 - }, - { - "collapsible": 1, - "collapsible_depends_on": "eval: doc.payment_gateway_account", - "depends_on": "eval: doc.payment_request_type == 'Inward'", - "fieldname": "section_break_7", - "fieldtype": "Section Break", - "label": "Payment Gateway Details" - }, - { - "fetch_from": "payment_gateway_account.payment_gateway", - "fieldname": "payment_gateway", - "fieldtype": "Read Only", - "label": "Payment Gateway" - }, - { - "fetch_from": "payment_gateway_account.payment_account", - "fieldname": "payment_account", - "fieldtype": "Read Only", - "label": "Payment Account", - "read_only": 1 - }, - { - "fetch_from": "payment_gateway_account.payment_channel", - "fieldname": "payment_channel", - "fieldtype": "Select", - "label": "Payment Channel", - "options": "\nEmail\nPhone", - "read_only": 1 - }, - { - "fieldname": "payment_order", - "fieldtype": "Link", - "label": "Payment Order", - "options": "Payment Order", - "read_only": 1 - }, - { - "fieldname": "payment_type", - "fieldtype": "Link", - "label": "Payment Type", - "mandatory_depends_on": "eval:doc.mode_of_payment == 'Wire Transfer' && doc.reference_doctype != 'Purchase Invoice' && doc.payment_request_type == 'Outward'", - "options": "Payment Type" - }, - { - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "options": "Company" - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "label": "Amended From", - "no_copy": 1, - "options": "Bank Payment Request", - "print_hide": 1, - "read_only": 1, - "search_index": 1 - }, - { - "depends_on": "eval:doc.tax_withholding_category", - "fieldname": "taxes_deducted", - "fieldtype": "Currency", - "label": "Taxes Deducted" - }, - { - "default": "0", - "depends_on": "eval:doc.party_type == 'Supplier' && doc.reference_doctype != 'Purchase Invoice' && doc.payment_request_type == 'Outward'", - "fieldname": "apply_tax_withholding_amount", - "fieldtype": "Check", - "label": "Apply Tax Withholding Amount" - }, - { - "depends_on": "eval:doc.apply_tax_withholding_amount", - "fieldname": "tax_withholding_category", - "fieldtype": "Link", - "label": "Tax Withholding Category", - "options": "Tax Withholding Category" - }, - { - "fieldname": "net_total", - "fieldtype": "Currency", - "label": "Net Total" - }, - { - "default": "0", - "depends_on": "eval:doc.mode_of_payment == 'Wire Transfer' && doc.payment_request_type == 'Outward'", - "fieldname": "is_adhoc", - "fieldtype": "Check", - "label": "Is AdHoc" - }, - { - "fieldname": "column_break_uumn", - "fieldtype": "Column Break" - }, - { - "fieldname": "payment_term", - "fieldtype": "Link", - "label": "Payment Term", - "options": "Payment Term", - "read_only": 1 - }, - { - "fieldname": "party_account_currency", - "fieldtype": "Link", - "label": "Party Account Currency", - "options": "Currency", - "read_only": 1 - } - ], - "index_web_pages_for_search": 1, - "is_submittable": 1, - "links": [], - "modified": "2024-11-25 13:10:22.989664", - "modified_by": "Administrator", - "module": "India Banking", - "name": "Bank Payment Request", - "naming_rule": "By \"Naming Series\" field", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts User", - "share": 1, - "write": 1 - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "share": 1, - "submit": 1, - "write": 1 - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "states": [] -} \ No newline at end of file diff --git a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.py b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.py deleted file mode 100644 index 5275bf9..0000000 --- a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.py +++ /dev/null @@ -1,582 +0,0 @@ -# Copyright (c) 2024, Aerele Technologies Private Limited and contributors -# For license information, please see license.txt - -import frappe -from frappe.model.document import Document - -from erpnext.accounts.doctype.payment_request.payment_request import PaymentRequest, get_existing_payment_request_amount -from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details - - -from erpnext.accounts.doctype.payment_request import payment_request as PR - -from erpnext.accounts.party import get_party_bank_account -from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( - get_accounting_dimensions, -) -from frappe.utils.data import flt, today, cstr - - -class BankPaymentRequest(PaymentRequest): - def validate_subscription_details(self): - pass - - def validate(self): - frappe.log_error(title="India - banking", message=cstr(self.as_dict())) - if self.apply_tax_withholding_amount and self.tax_withholding_category and self.payment_request_type == "Outward": - if not self.net_total: - self.net_total = self.grand_total - tds_amount = self.calculate_pr_tds(self.net_total) - self.taxes_deducted = tds_amount - self.grand_total = self.net_total - self.taxes_deducted - else: - if self.net_total and not self.grand_total: - self.grand_total = self.net_total - if self.grand_total and self.net_total != self.grand_total and not self.apply_tax_withholding_amount: - self.grand_total = self.net_total - - if not self.is_adhoc: - super().validate() - else: - if self.get("__islocal"): - self.status = "Draft" - if self.reference_doctype or self.reference_name: - frappe.throw("Payments with references cannot be marked as ad-hoc") - - self.valdidate_bank_for_wire_transfer() - - def validate_payment_request_amount(self): - existing_payment_request_amount = flt( - get_existing_payment_request_amount(self.reference_doctype, self.reference_name) - ) - - docname = None - if frappe.flags.update_amount or not self.is_new(): - docname = self.name - - existing_payment_request_amount_drafted = flt( - get_existing_payment_request_amount(self.reference_doctype, self.reference_name, submitted=False, update=docname) - ) - - total_existing_payment_request_amount = existing_payment_request_amount + existing_payment_request_amount_drafted - - - ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) - - if not hasattr(ref_doc, "order_type") or getattr(ref_doc, "order_type") != "Shopping Cart": - if self.reference_doctype in ["Purchase Order"]: - ref_amount = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total) - elif self.reference_doctype in ["Purchase Invoice"]: - if ref_doc.rounded_total: - ref_amount = flt(ref_doc.rounded_total) - else: - ref_amount = flt(ref_doc.grand_total) - else: - ref_amount = get_amount(ref_doc, self.payment_account) - - frappe.log_error("existing_payment_request_amount_drafted", existing_payment_request_amount_drafted ) - frappe.log_error("existing_payment_request_amount", existing_payment_request_amount) - frappe.log_error("ref_amount", ref_amount) - frappe.log_error("self.net_total", self.net_total) - - if total_existing_payment_request_amount + flt(self.net_total) > ref_amount: - frappe.throw( - frappe._("Total Bank Payment Request amount cannot be greater than {0} amount").format( - self.reference_doctype - ) - ) - - def on_submit(self): - debit_account = None - if self.payment_type: - debit_account = frappe.db.get_value("Payment Type", self.payment_type, "account") - elif self.reference_doctype == "Purchase Invoice": - debit_account = frappe.db.get_value(self.reference_doctype, self.reference_name, "credit_to") - - if not debit_account: - frappe.throw("Debit account for Payment Type {} cannot be determined".format(self.payment_type)) - if not self.is_adhoc: - super().on_submit() - else: - if self.payment_request_type == "Outward": - self.db_set("status", "Initiated") - return - - def create_payment_entry(self, submit=True): - payment_entry = super().create_payment_entry(submit=submit) - payment_entry.source_doctype = self.payment_order_type - if payment_entry.docstatus != 1 and self.payment_type: - payment_entry.paid_to = frappe.db.get_value("Payment Type", self.payment_type, "account") or "" - - return payment_entry - - def calculate_pr_tds(self, amount): - doc = self - doc.supplier = self.party - doc.company = self.company - doc.base_tax_withholding_net_total = amount - doc.tax_withholding_net_total = amount - doc.taxes = [] - taxes = get_party_tax_withholding_details(doc, self.tax_withholding_category) - if taxes: - return taxes["tax_amount"] - else: - return 0 - - def valdidate_bank_for_wire_transfer(self): - if self.mode_of_payment == "Wire Transfer" and not self.bank_account: - frappe.throw(frappe._("Bank Account is missing for Wire Transfer Payments")) - - try: - status = frappe.db.get_value("Bank Account", self.bank_account, "workflow_state") - - if self.mode_of_payment == "Wire Transfer" and status != "Approved": - frappe.throw("Cannot proceed with un-approved bank account") - except: - frappe.throw("Workflow Not Found for Bank Account") - - -@frappe.whitelist() -def validate_payment_request_status(**args): - total_bank_payment_request_amount = frappe.db.get_all( - "Bank Payment Request", { - "reference_doctype": args.get('ref_doctype'), - "reference_name": args.get('ref_name'), - "docstatus": 1 - }, - "sum(grand_total) as grand_total") - - if total_bank_payment_request_amount[0] and total_bank_payment_request_amount[0].get('grand_total'): - if flt(total_bank_payment_request_amount[0].get('grand_total')) >= flt(args.get('grand_total')): - return 'Completed' - - return "" - -def get_employee_payemnt_details(journal_accounts): - employee_bank_account_details = frappe.db.get_all('Bank Account', - { - 'party_type': 'Employee', - 'disabled': 0, - 'is_default': 1 - }, - ['name','party', 'party_type', 'bank', 'branch_code', 'bank_account_no', 'mobile_number', 'email', 'workflow_state'] - ) - - employee_bank_account_details = {detail.get('party'): detail for detail in employee_bank_account_details} - - payment_details = [] - - for journal_account in journal_accounts: - employee_bank_details = employee_bank_account_details.get(journal_account.get('employee', '')) - if not employee_bank_details: - frappe.throw('Default Bank Account not found for Employee {0}'.format(journal_account.get('employee'))) - else: - if employee_bank_details.get('workflow_state') != 'Approved': - link = frappe.utils.get_link_to_form("Bank Account", employee_bank_details.get('name')) - frappe.throw("Bank Account({1}) for Employee {0} is not approved".format( - journal_account.get('employee'), link) - ) - - journal_account = frappe._dict(journal_account) - details = { - "reference_doctype": "Journal Entry", - "reference_name": journal_account.journal, - "journal_entry_account": journal_account.name, - "amount": journal_account.amount, - "party_type": "Employee", - "party": journal_account.employee, - "mode_of_payment": "", - "bank_account": employee_bank_details.name, - "account": journal_account.account, - } - payment_details.append(details) - - return payment_details - -@frappe.whitelist(allow_guest=True) -def make_bank_payment_request(**args): - """Make Bank payment request""" - - args = frappe._dict(args) - - ref_doc = frappe.get_doc(args.dt, args.dn) - gateway_account = PR.get_gateway_details(args) or frappe._dict() - - grand_total = get_amount(ref_doc, gateway_account.get("payment_account")) - - bank_account = ( - get_party_bank_account(args.get("party_type"), args.get("party")) if args.get("party_type") else "" - ) - - if not bank_account: - frappe.throw(frappe._("Default Bank Account is missing for {0} - {1}").format(args.get("party_type"), args.get("party"))) - - draft_payment_request = frappe.db.get_value( - "Bank Payment Request", - {"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": 0}, - ) - - existing_payment_request_amount = get_existing_payment_request_amount(args.dt, args.dn) - - if existing_payment_request_amount: - grand_total -= existing_payment_request_amount - - party_account_currency = ref_doc.get("party_account_currency") - - if not party_account_currency: - party_account = get_party_account(party_type, ref_doc.get(party_type.lower()), ref_doc.company) - party_account_currency = get_account_currency(party_account) - - if draft_payment_request: - bpr = frappe.get_doc("Bank Payment Request", draft_payment_request) - bpr.db_set("net_total", grand_total, update_modified=False) - bpr.validate() - frappe.db.set_value( - "Bank Payment Request", draft_payment_request, { - "net_total": bpr.net_total, - "grand_total": bpr.grand_total, - "taxes_deducted": bpr.taxes_deducted - }, - update_modified=False - ) - - else: - bpr = frappe.new_doc("Bank Payment Request") - - if not args.get("payment_request_type"): - args["payment_request_type"] = ( - "Outward" if args.get("dt") in ["Purchase Order", "Purchase Invoice"] else "Inward" - ) - - bpr.payment_type = "Pay" - - bpr.update( - { - "payment_gateway_account": gateway_account.get("name"), - "payment_gateway": gateway_account.get("payment_gateway"), - "payment_account": gateway_account.get("payment_account"), - "payment_channel": gateway_account.get("payment_channel"), - "payment_request_type": args.get("payment_request_type"), - "currency": ref_doc.currency, - "company": ref_doc.company, - "party_account_currency": party_account_currency, - "grand_total": grand_total, - "mode_of_payment": "Wire Transfer", - "transaction_date": today(), - "email_to": args.recipient_id or ref_doc.owner, - "subject": frappe._("Bank Payment Request for {0}").format(args.dn), - "message": gateway_account.get("message") or PR.get_dummy_message(ref_doc), - "reference_doctype": args.dt, - "reference_name": args.dn, - "party_type": args.get("party_type") or "Customer", - "party": args.get("party") or ref_doc.get("customer"), - "bank_account": bank_account, - "net_total": grand_total - } - ) - - # Update dimensions - bpr.update( - { - "cost_center": ref_doc.get("cost_center") or - frappe.get_value(ref_doc.get("doctype") + " Item", - {'parent': ref_doc.get("name")}, 'cost_center' - ), - "project": ref_doc.get("project") or - frappe.get_value(ref_doc.get("doctype") + " Item", - {'parent': ref_doc.get("name")}, 'project' - ) - } - ) - - for dimension in get_accounting_dimensions(): - bpr.update({dimension: ref_doc.get(dimension)}) - - bpr.insert(ignore_permissions=True) - - if args.submit_doc: - bpr.submit() - - if args.return_doc: - return bpr - - return bpr.as_dict() - -def update_payroll_entry(source, target): - target.payment_order_type = "Payroll Entry" - target.docstaus = 0 - target.status = 'Pending' - - for dimension in get_accounting_dimensions(): - target.update({dimension: source.get(dimension, '')}) - - if get_employee_payemnt_details(source): - for ref in get_employee_payemnt_details(source): - target.append("references",ref) - -def update_bank_entry(source, target): - target.payment_order_type = "Journal Entry" - target.docstaus = 0 - target.status = 'Pending' - - journal_accounts = frappe.db.sql(""" - SELECT - name, account, against_account, cost_center, debit as amount, exchange_rate, parent, - party as employee, party_type, parent as journal - FROM - `tabJournal Entry Account` - WHERE - parent = %s AND party_type = 'Employee' AND payment_status NOT IN ('Paid', 'Ordered')""", source.name, as_dict=1) - - if get_employee_payemnt_details(journal_accounts): - for ref in get_employee_payemnt_details(journal_accounts): - target.append("references",ref) - - -@frappe.whitelist() -def make_payment_order(source_name, target_doc=None, args= None): - from frappe.model.mapper import get_mapped_doc - - def set_missing_values(source, target): - target.payment_order_type = "Bank Payment Request" - account = "" - if source.payment_type: - account = frappe.db.get_value("Payment Type", source.payment_type, "account") - if source.reference_doctype == "Purchase Invoice": - account = frappe.db.get_value(source.reference_doctype, source.reference_name, "credit_to") - - for dimension in get_accounting_dimensions(): - target.update({dimension: source.get(dimension, '')}) - - target.append( - "references", - { - "reference_doctype": source.reference_doctype, - "reference_name": source.reference_name, - "amount": source.grand_total, - "party_type": source.party_type, - "party": source.party, - "bank_payment_request": source_name, - "mode_of_payment": source.mode_of_payment, - "bank_account": source.bank_account, - "account": account, - "is_adhoc": source.is_adhoc, - "cost_center": source.cost_center, - "project": source.project, - "tax_withholding_category": source.tax_withholding_category - }, - ) - target.status = "Pending" - - def update_missing_values(source, target): - target.payment_order_type = "Payment Entry" - target.company_bank_account = source.bank_account - target.party = '' - - account = "" - if source.paid_to: - account = source.paid_to - - for dimension in get_accounting_dimensions(): - target.update({dimension: source.get(dimension, '')}) - - if source.references: - target.append( - "references", - { - "reference_doctype": source.references[0].reference_doctype, - "reference_name": source.references[0].reference_name, - "amount": source.references[0].total_amount, - "party_type": source.party_type, - "party": source.party, - "mode_of_payment": source.mode_of_payment, - "bank_account": get_party_bank_account(source.get("party_type"), source.get("party")) if source.get("party_type") else "", - "account": account, - "cost_center": source.cost_center, - "project": source.project, - "payment_entry": source.name - } - ) - else: - target.append( - "references", - { - "reference_doctype": "Payment Entry", - "reference_name": source.name, - "amount": source.paid_amount, - "party_type": source.party_type, - "party": source.party, - "mode_of_payment": "Wire Transfer", - "bank_account": source.party_bank_account or get_party_bank_account(source.get("party_type"), source.get("party")), - "account": source.paid_to, - "cost_center": source.cost_center, - "project": source.project, - "payment_entry": source.name - } - ) - target.status = "Pending" - - if args.get('ref_doctype') not in ["Payment Entry", "Payroll Entry", "Journal Entry"]: - doclist = get_mapped_doc( - "Bank Payment Request", - source_name, - { - "Bank Payment Request": { - "doctype": "Payment Order", - } - }, - target_doc, - set_missing_values, - ) - elif args.get('ref_doctype') == "Payment Entry": - doclist = get_mapped_doc( - "Payment Entry", - source_name, - { - "Payment Entry": { - "doctype": "Payment Order", - } - }, - target_doc, - update_missing_values, - ) - elif args.get('ref_doctype') == "Payroll Entry": - doclist = get_mapped_doc( - "Payroll Entry", - source_name, - { - "Payroll Entry": { - "doctype": "Payment Order", - } - }, - target_doc, - update_payroll_entry, - ) - elif args.get('ref_doctype') == "Journal Entry": - doclist = get_mapped_doc( - "Journal Entry", - source_name, - { - "Journal Entry": { - "doctype": "Payment Order", - } - }, - target_doc, - update_bank_entry, - ) - - - return doclist - -def get_existing_payment_request_amount(ref_dt, ref_dn, submitted= True, update=None, payment_term= None): - """ - Get the existing Bank payment request which are unpaid or partially paid for payment channel other than Phone - and get the summation of existing paid Bank payment request for Phone payment channel. - """ - - docstatus = 1 if submitted else 0 - - where_conditions = "AND payment_term = '{0}'".format(payment_term.replace("%", "%%")) if payment_term else "AND payment_term is null" - - existing_payment_request_amount = frappe.db.sql( - """ - select sum(net_total) - from `tabBank Payment Request` - where - name != %s - and reference_doctype = %s - and reference_name = %s - and docstatus = %s {0} - """.format(where_conditions), - (update or "", ref_dt, ref_dn, docstatus) - ) - return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0 - -def get_amount(ref_doc, payment_account=None): - """get amount based on doctype""" - dt = ref_doc.doctype - if dt in ["Sales Order", "Purchase Order"]: - grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total) - elif dt in ["Sales Invoice", "Purchase Invoice"]: - if not ref_doc.get("is_pos"): - if ref_doc.party_account_currency == ref_doc.currency: - if ref_doc.rounded_total: - grand_total = flt(ref_doc.rounded_total) - else: - grand_total = flt(ref_doc.grand_total) - else: - if ref_doc.base_rounded_total: - grand_total = flt(ref_doc.base_rounded_total) / ref_doc.conversion_rate - else: - grand_total = flt(ref_doc.base_grand_total) / ref_doc.conversion_rate - elif dt == "Sales Invoice": - for pay in ref_doc.payments: - if pay.type == "Phone" and pay.account == payment_account: - grand_total = pay.amount - break - elif dt == "POS Invoice": - for pay in ref_doc.payments: - if pay.type == "Phone" and pay.account == payment_account: - grand_total = pay.amount - break - elif dt == "Fees": - grand_total = ref_doc.outstanding_amount - - if grand_total > 0: - return grand_total - else: - frappe.throw(frappe._("Bank Payment Entry is already created")) - - -@frappe.whitelist() -def get_existing_bank_entry(filters= None): - condition = '' - if filters and filters.get('refrence_name'): - condition += "AND jea.reference_name = '{0}'".format(filters.get('refrence_name')) - - payroll_entries= frappe.db.sql(f""" - SELECT - DISTINCT jea.reference_name as payroll_entry - FROM - `tabJournal Entry`je - JOIN - `tabJournal Entry Account`jea - ON - je.name = jea.parent - WHERE - je.docstatus != 2 AND jea.reference_type = 'Payroll Entry' AND je.voucher_type = 'Bank Entry' - {condition} - """, as_dict= 1 ) - return [payroll_entry.payroll_entry for payroll_entry in payroll_entries] - -@frappe.whitelist() -@frappe.validate_and_sanitize_search_inputs -def get_bank_entry(doctype, txt, searchfield, start, page_len, filters, as_dict): - search_condition = '' - if filters and filters.get('docs'): - filters.get('docs').append('') - exist_account = str(tuple(filters.get('docs'))) - search_condition += f" AND je.name NOT IN {exist_account} " - - if filters and filters.get('company'): - search_condition += f"AND je.company = '{filters.get('company')}'" - if txt: - search_condition += f"AND je.name LIKE '%{txt}%'" - - bank_entries = frappe.db.sql(f""" - SELECT - DISTINCT je.name, je.company, sum(jea.debit) as total, je.voucher_type - FROM - `tabJournal Entry`je - JOIN - `tabJournal Entry Account`jea - ON - je.name = jea.parent - WHERE - je.docstatus = 1 AND jea.payment_status NOT IN ('Paid', 'Ordered') AND - je.voucher_type = 'Bank Entry' AND jea.party_type= "Employee" {search_condition} - GROUP BY - je.name, je.company, je.voucher_type - """, as_dict= 1) - - return bank_entries diff --git a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request_list.js b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request_list.js deleted file mode 100644 index 77a7933..0000000 --- a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request_list.js +++ /dev/null @@ -1,21 +0,0 @@ -frappe.listview_settings["Bank Payment Request"] = { - add_fields: ["status"], - get_indicator: function (doc) { - if (doc.status == "Draft") { - return [__("Draft"), "gray", "status,=,Draft"]; - } - if (doc.status == "Requested") { - return [__("Requested"), "green", "status,=,Requested"]; - } else if (doc.status == "Initiated") { - return [__("Initiated"), "green", "status,=,Initiated"]; - }else if (doc.status == 'Payment Ordered') { - return [__('Payment Ordered'), "green", "status,=,Payment Ordered"]; - }else if (doc.status == "Partially Paid") { - return [__("Partially Paid"), "orange", "status,=,Partially Paid"]; - } else if (doc.status == "Paid") { - return [__("Paid"), "blue", "status,=,Paid"]; - } else if (doc.status == "Cancelled") { - return [__("Cancelled"), "red", "status,=,Cancelled"]; - } - }, -}; diff --git a/india_banking/india_banking/doctype/bank_payment_request/test_bank_payment_request.py b/india_banking/india_banking/doctype/bank_payment_request/test_bank_payment_request.py deleted file mode 100644 index f02c7ba..0000000 --- a/india_banking/india_banking/doctype/bank_payment_request/test_bank_payment_request.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2024, Aerele Technologies Private Limited and Contributors -# See license.txt - -# import frappe -from frappe.tests.utils import FrappeTestCase - - -class TestBankPaymentRequest(FrappeTestCase): - pass diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index 84bfe59..4ede821 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -3,12 +3,9 @@ import frappe from erpnext.accounts.doctype.payment_order.payment_order import PaymentOrder -from frappe.utils import get_datetime, get_link_to_form, getdate +from frappe.utils import get_link_to_form, getdate from india_banking.india_banking.doc_events.payment_order import make_payment_entries -from india_banking.india_banking.doctype.bank_payment_request.bank_payment_request import ( - get_existing_bank_entry, -) class CustomPaymentOrder(PaymentOrder): From 98f65755c45d3850d6ede5809e4517919772d6a0 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 20 Dec 2024 18:13:32 +0530 Subject: [PATCH 25/83] safty commit --- .../doctype/bank_connector/bank_connector.js | 3 +- .../doctype/bank_connector/bank_connector.py | 75 ++++++++- india_banking/public/js/payment_order.js | 145 ++++++++---------- 3 files changed, 140 insertions(+), 83 deletions(-) diff --git a/india_banking/india_banking/doctype/bank_connector/bank_connector.js b/india_banking/india_banking/doctype/bank_connector/bank_connector.js index b75c182..f1237ea 100644 --- a/india_banking/india_banking/doctype/bank_connector/bank_connector.js +++ b/india_banking/india_banking/doctype/bank_connector/bank_connector.js @@ -8,7 +8,8 @@ frappe.ui.form.on("Bank Connector", { filters: { disabled: 0, is_default: 1, - is_company_account: 1 + is_company_account: 1, + company: doc.company, }, }; }); diff --git a/india_banking/india_banking/doctype/bank_connector/bank_connector.py b/india_banking/india_banking/doctype/bank_connector/bank_connector.py index 0554d91..097560f 100644 --- a/india_banking/india_banking/doctype/bank_connector/bank_connector.py +++ b/india_banking/india_banking/doctype/bank_connector/bank_connector.py @@ -1,9 +1,80 @@ # Copyright (c) 2024, Aerele Technologies Private Limited and contributors # For license information, please see license.txt -# import frappe +import frappe from frappe.model.document import Document +from frappe import _ +OTP_ENABLED_BANK = [ + ("ICICI Bank", 1) # ICICI Bank, Bulk Transaction +] class BankConnector(Document): - pass + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def check_permission(self): + if not frappe.has_permission("Payment Order", "write"): + frappe.throw(_("Not permitted"), frappe.PermissionError) + + @property + def headers(self): + return { + "Authorization": f"token {self.get_password("api_key")}:{self.get_password("api_secret")}", + "Content-Type": "application/json", + } + + @property + def url(self): + return f"{self.url}/api/method/india_banking_connector.api.connect" + + def check_otp_enabled(self, otp=None): + if (self.bank, self.bulk_transaction) in OTP_ENABLED_BANK and otp is None: + return True + elif (self.bank, self.bulk_transaction) in OTP_ENABLED_BANK and not otp: + frappe.throw(_("OTP is required for this transaction")) + + def make_payment(self, payment_order, otp=None): + payment_order = frappe.get_doc("Payment Order", payment_order) + + if self.check_otp_enabled(otp): + self.generate_otp(payment_order) + + + def generate_otp(self, payment_order): + payment_order.update_unique_and_file_reference_id(save=True) + payment_order.reload() + + def make_bulk_payment(): + pass + + +def get_bank_connector(bank_account, company): + # Fetch the connector information + bank_connector = frappe.db.exists( + "Bank Connector", + { + "company": company, + "bank_account": bank_account, + }, + ) + + if not bank_connector: + frappe.throw("Bank Connector is not initialized") + + bank_connector = frappe.get_doc("Bank Connector", bank_connector) + + +@frappe.whitelist() +def make_payment(payment_order, otp=None): + payment_order = frappe.get_doc("Payment Order", payment_order) + bank_connector = get_bank_connector(payment_order.bank_account, payment_order.company) + bank_connector.make_payment(payment_order, otp) + + +@frappe.whitelist() +def get_payment_status(payment_order): + payment_order = frappe.get_doc("Payment Order", payment_order) + bank_connector = get_bank_connector(payment_order.bank_account, payment_order.company) + return bank_connector.get_payment_status(payment_order) + diff --git a/india_banking/public/js/payment_order.js b/india_banking/public/js/payment_order.js index 452548e..2abe524 100644 --- a/india_banking/public/js/payment_order.js +++ b/india_banking/public/js/payment_order.js @@ -13,6 +13,13 @@ frappe.ui.form.on("Payment Order", { }, }; }); + frm.set_query("mode_of_transfer", "summary", function () { + return { + filters: { + disabled: 0, + }, + }; + }); }, get_payments_from_payment_request(frm) { @@ -136,7 +143,6 @@ frappe.ui.form.on("Payment Order", { __("Get Payments from") ); } - frm.trigger("remove_button"); let is_pending = false; if (frm.doc.status == "Pending" && frm.doc.docstatus == 1) { @@ -151,81 +157,15 @@ frappe.ui.form.on("Payment Order", { } } if (uninitiated_payments > 0 && is_pending) { - frappe.db.get_value( - "Bank Connector", - { bank: frm.doc.company_bank }, - "bulk_transaction", - (r) => { - if (r.bulk_transaction) { - frm.add_custom_button(__("Initiate Payment"), function () { - frappe.call({ - method: - "india_banking.india_banking.doc_events.payment_order.generate_payment_otp", - freeze: true, - freeze_message: "Initiating Payment...", - args: { - docname: frm.doc.name, - }, - callback: (res) => { - if (!res.exc) { - frappe.prompt( - { - label: "Enter OTP", - place_holder: "Enter", - fieldname: "otp", - fieldtype: "Data", - }, - (values) => { - frappe.call({ - method: - "india_banking.india_banking.doc_events.payment_order.make_bank_payment", - freeze: 1, - args: { - docname: frm.doc.name, - otp: values.otp, - }, - callback: function (r) { - if (r.message) { - frappe.msgprint(r.message); - } - frm.reload_doc(); - }, - }); - }, - "Sent an OTP to the registered account number", - "Proceed" - ); - } - }, - }); - }); - } else { - frm.add_custom_button(__("Initiate Payment"), function () { - frappe.call({ - method: - "india_banking.india_banking.doc_events.payment_order.make_bank_payment", - freeze: 1, - freeze_message: "Initiating Payment...", - args: { - docname: frm.doc.name, - }, - callback: function (r) { - if (r.message && !r.exc) { - frappe.msgprint(r.message); - } - frm.reload_doc(); - }, - }); - }); - } - } - ); + frm.add_custom_button(__("Initiate Payment"), function () { + frm.trigger("make_payment") + }); } } } if ( - (frm.doc.status == "Pending" || frm.doc.status == "Initiated") && + ( ["Pending", "Initiated"].includes(frm.doc.status)) && frm.doc.docstatus == 1 ) { if (frm.has_perm("write") && "summary" in frm.doc) { @@ -235,12 +175,12 @@ frappe.ui.form.on("Payment Order", { pending_status_check += 1; } } - + if (pending_status_check > 0) { frm.add_custom_button(__("Get Status"), function () { frappe.call({ method: - "india_banking.india_banking.doc_events.payment_order.get_payment_status", + "india_banking.india_banking.doc_events.payment_order.get_payment_status", freeze: 1, freeze_message: "Fetching payment status....", args: { @@ -258,17 +198,62 @@ frappe.ui.form.on("Payment Order", { } } - frm.set_query("mode_of_transfer", "summary", function () { - return { - filters: { - disabled: 0, - }, - }; + frm.trigger("remove_button"); + }, + + make_payment: function (frm) { + frappe.call({ + method: + "india_banking.india_banking.doc_events.payment_order.make_bank_payment", + freeze: true, + freeze_message: "Initiating Payment...", + args: { + docname: frm.doc.name, + }, + callback: (res) => { + if (!res.exc && res.message) { + if(res.message.otp_required){ + this.verify_otp(); + }else{ + frappe.msgprint(res.message); + } + } + }, }); }, + verify_otp() { + frappe.prompt( + { + label: "Enter OTP", + place_holder: "Enter", + fieldname: "otp", + fieldtype: "Data", + }, + (values) => { + frappe.call({ + method: + "india_banking.india_banking.doc_events.payment_order.make_bank_payment", + freeze: 1, + args: { + docname: frm.doc.name, + otp: values.otp, + }, + callback: function (r) { + if (r.message) { + frappe.msgprint(r.message); + } + frm.reload_doc(); + }, + }); + }, + "Sent an OTP to the registered Mobile number", + "Proceed" + ); + }, + remove_button: function (frm) { - // remove custom button of order type that is not imported + // remove custom button of order type that is not importedz frm.remove_custom_button("Create Journal Entries"); if ( (frm.doc.references.length > 0 && frm.doc.payment_order_type) || From 9aa3f2e87f7c757700ad7ea286b8dd4da2ee10f1 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Sun, 22 Dec 2024 22:25:28 +0530 Subject: [PATCH 26/83] refactor: bulk payment code add in bank connector refactor: single payment code add in bank connector --- .../doctype/bank_connector/bank_connector.py | 363 +++++++++++++++++- india_banking/overrides/payment_order.py | 1 + india_banking/public/js/payment_order.js | 33 +- 3 files changed, 365 insertions(+), 32 deletions(-) diff --git a/india_banking/india_banking/doctype/bank_connector/bank_connector.py b/india_banking/india_banking/doctype/bank_connector/bank_connector.py index 097560f..7a48ea1 100644 --- a/india_banking/india_banking/doctype/bank_connector/bank_connector.py +++ b/india_banking/india_banking/doctype/bank_connector/bank_connector.py @@ -1,19 +1,29 @@ # Copyright (c) 2024, Aerele Technologies Private Limited and contributors # For license information, please see license.txt +import json + import frappe -from frappe.model.document import Document from frappe import _ +from frappe.model.document import Document +from frappe.utils import comma_and, cstr, get_link_to_form, getdate +from requests import request + +from india_banking.india_banking.doctype.india_banking_request_log.india_banking_request_log import ( + create_api_log, +) +from india_banking.utils import get_bank_address_details OTP_ENABLED_BANK = [ - ("ICICI Bank", 1) # ICICI Bank, Bulk Transaction + ("ICICI Bank", 1), # ICICI Bank, Bulk Transaction ] + class BankConnector(Document): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - def check_permission(self): + def check_user_permission(self): if not frappe.has_permission("Payment Order", "write"): frappe.throw(_("Not permitted"), frappe.PermissionError) @@ -25,7 +35,7 @@ def headers(self): } @property - def url(self): + def base_url(self): return f"{self.url}/api/method/india_banking_connector.api.connect" def check_otp_enabled(self, otp=None): @@ -38,15 +48,340 @@ def make_payment(self, payment_order, otp=None): payment_order = frappe.get_doc("Payment Order", payment_order) if self.check_otp_enabled(otp): - self.generate_otp(payment_order) + return self.generate_otp(payment_order) + + if otp: + self.verify_otp(payment_order, otp) + + # Make the payment + if self.bulk_transaction: + return self.make_bulk_payment(payment_order, otp) + else: + return self.make_single_payment(payment_order) + + def make_single_payment(self, payment_order): + count = 0 + for payment_row in payment_order.summary: + if ( + not payment_row.payment_initiated + and payment_row.payment_status == "Pending" + ): + # handle failed or success response + payment_response = self.process_payment_and_response( + payment_row, payment_order + ) + + if ( + payment_response + and "payment_status" in payment_response + and payment_response["payment_status"] == "Initiated" + ): + frappe.db.set_value( + "Payment Order Summary", + payment_row.name, + { + "payment_status": "Initiated", + "payment_date": getdate(), + "payment_initiated": 1, + }, + ) + count += 1 + + elif ( + payment_response + and "payment_status" in payment_response + and payment_response["payment_status"] == "" + ): + if "message" in payment_response: + frappe.db.set_value( + "Payment Order Summary", + payment_row.name, + "message", + payment_response.message, + ) + else: + frappe.db.set_value( + "Payment Order Summary", + payment_row.name, + "payment_status", + "Failed", + ) + payment_entry = frappe.get_doc( + "Payment Entry", payment_row.payment_entry + ) + if payment_entry.docstatus == 1: + payment_entry.cancel() + + self.process_bank_payment_requests(payment_order, payment_row) + + if payment_response and "message" in payment_response: + frappe.db.set_value( + "Payment Order Summary", + payment_row.name, + "message", + payment_response.message, + ) + + payment_order.reload() + processed_count = 0 + for row in payment_order.summary: + if row.payment_initiated: + processed_count += 1 + + if processed_count == len(payment_order.summary): + frappe.db.set_value( + "Payment Order", payment_order.name, "status", "Initiated" + ) + + return {"message": f"{count} payments initiated"} + + def process_payment_and_response(self, payment_row, payment_order): + payment_payload = self.get_payload(payment_order, "make_payment") + payment_payload.update(payment_row.as_dict(convert_dates_to_str=True)) + party_field_name = ( + "supplier_name" if payment_row.party_type == "Supplier" else "employee_name" + ) + + party_name = frappe.db.get_value( + payment_row.party_type, payment_row.party, party_field_name + ) + + payment_payload.party_name = party_name + payment_payload.desc = ( + f"Payment to {payment_row.party} via {payment_row.parent}" + ) + + party_address = get_bank_address_details(payment_row.bank_account) + bank_link = get_link_to_form("Bank Account", payment_row.bank_account) + if not party_address: + frappe.throw( + f"Address not found for the selected bank account {bank_link} at Row #{payment_row.idx}" + ) + + payment_payload.address = json.dumps(party_address) + + response = request.post( + self.base_url, headers=self.headers, data=json.dumps(payment_payload) + ) + + # create api request log + create_api_log( + response, "Make Payment", payment_order.doctype, payment_order.name + ) + + if response.status_code == 200: + payment_response = self.get_response_details(response) + + if not payment_response.status: + return {"payment_status": "", "message": str(response)} + + elif payment_response.status == "ACCEPTED": + return { + "payment_status": "Initiated", + "message": payment_response.message, + } + + elif payment_response.status == "Request Failure": + return {"payment_status": "", "message": "Request Failure"} + + else: + return {"payment_status": "Failed", "message": payment_response.message} + else: + return {"payment_status": "", "message": "Bad Request"} + def process_bank_payment_requests(self, payment_order, payment_row): + key = ( + payment_row.party_type, + payment_row.party, + payment_row.bank_account, + payment_row.account, + payment_row.cost_center, + payment_row.project, + payment_row.tax_withholding_category, + payment_row.reference_doctype, + ) + + failed_prs = [] + for ref in payment_order.references: + ref_key = ( + ref.party_type, + ref.party, + ref.bank_account, + ref.account, + ref.cost_center, + ref.project, + ref.tax_withholding_category, + ref.reference_doctype, + ) + if key == ref_key: + failed_prs.append(ref.payment_request) + + for pr in failed_prs: + pr_doc = frappe.get_doc("Payment Request", pr) + if pr_doc.docstatus == 1: + pr_doc.check_if_payment_entry_exists() + pr_doc.set_as_cancelled() + pr_doc.db_set("docstatus", 2) + + def make_bulk_payment(self, payment_order, otp): + payment_payload = self.get_payload(payment_order, "make_payment") + payment_payload.doc.update({"otp": otp}) + + payment_account_list = [] + + # Lei number validation + for ref in payment_order.summary: + if ref.mode_of_transfer == "RTGS" and ref.amount >= 500000000: + lei_number = frappe.db.get_value( + ref.party_type, ref.party, "lei_number" + ) + payment_account_list.append(ref.account_name + "-" + lei_number) + if not lei_number: + frappe.throw("LEI Number required for payment > 50 Cr") + else: + payment_account_list.append( + ref.account_name + "-" + ref.bank_account_no + ) + + payment_payload.doc.update( + { + "desc": f"Payment to {comma_and(payment_account_list)} via {payment_order.name}" + } + ) + + response = request.post( + self.base_url, headers=self.headers, data=json.dumps(payment_payload) + ) + + # create api request log + create_api_log( + response, "Make Payment", payment_order.doctype, payment_order.name + ) + + # handle failed or success response + return self.process_bulk_payment_response(response, payment_order) + + def process_bulk_payment_response(self, response, payment_order): + payment_response = self.get_response_details(response) + if payment_response.get("status", "") == "ACCEPTED": + frappe.db.set_value( + "Payment Order", payment_order.name, "status", "Initiated" + ) + frappe.db.set_value( + "Payment Order", + payment_order.name, + "file_sequence_number", + payment_response.get("file_sequence_number"), + ) + + for row in payment_order.summary: + frappe.db.set_value( + "Payment Order Summary", + row.name, + { + "payment_status": "Initiated", + "payment_date": getdate(), + "payment_initiated": 1, + }, + ) + + return {"message": "Payment Initiated"} + + elif payment_response.get("status", "") == "Failed": + return {"message": "Failed - " + cstr(payment_response.get("message"))} + + else: + frappe.throw(_("Invalid Response: Check API Log")) + + def verify_otp(self, payment_order, otp): + pass def generate_otp(self, payment_order): payment_order.update_unique_and_file_reference_id(save=True) payment_order.reload() - def make_bulk_payment(): - pass + # Generate OTP using POST request + response = request.post( + self.base_url, + headers=self.headers, + data=json.dumps(self.get_payload(payment_order, "generate_otp")), + ) + + # create api response log + create_api_log( + response, "Generate Otp", payment_order.doctype, payment_order.name + ) + # handle failed or success response + return self.handle_otp_response(response) + + def get_response_details(self, response): + try: + return frappe._dict(response.json().get("message")) + except: + frappe.throw(_("Invalid Response: Check API Log")) + + def handle_otp_response(self, response): + if response.ok: + response_details = self.get_response_details(response) + if response_details.status == "success": + return {"otp_required": True} + else: + frappe.throw(msg=_(cstr(response_details.message)), title=_("Failed")) + else: + frappe.throw( + title=cstr(response.status_code), + msg=_("Invalid Request: Check API Log"), + ) + + def get_payload(self, payment_order, action): + payment_payload = frappe._dict() + payment_payload.doc = payment_order.as_dict(convert_dates_to_str=True) + payment_payload.method = action + payment_payload.bulk_transaction = self.bulk_transaction + return payment_payload + + def update_payment_status(self, payment_order): + try: + success_count = 0 + faild_count = 0 + rejected_count = 0 + for ref in payment_order.summary: + status = frappe.db.get_value( + "Payment Order Summary", ref.name, "payment_status" + ) + if status == "Processed": + success_count += 1 + if status == "Failed": + faild_count += 1 + if status == "Rejected": + rejected_count += 1 + + if success_count == len(payment_order.summary): + frappe.db.set_value( + "Payment Order", payment_order.name, "status", "Approved" + ) + + elif faild_count == len(payment_order.summary): + frappe.db.set_value( + "Payment Order", payment_order.name, "status", "Failed" + ) + elif rejected_count == len(payment_order.summary): + frappe.db.set_value( + "Payment Order", payment_order.name, "status", "Rejected" + ) + elif ( + success_count > 1 + and success_count + faild_count + rejected_count + == len(payment_order.summary) + ): + frappe.db.set_value( + "Payment Order", payment_order.name, "status", "Partially Approved" + ) + except: + frappe.log_error( + title="Payment Order Status Update Error", + message=frappe.get_traceback(), + ) def get_bank_connector(bank_account, company): @@ -58,23 +393,25 @@ def get_bank_connector(bank_account, company): "bank_account": bank_account, }, ) - if not bank_connector: frappe.throw("Bank Connector is not initialized") - bank_connector = frappe.get_doc("Bank Connector", bank_connector) + return frappe.get_doc("Bank Connector", bank_connector) @frappe.whitelist() def make_payment(payment_order, otp=None): payment_order = frappe.get_doc("Payment Order", payment_order) - bank_connector = get_bank_connector(payment_order.bank_account, payment_order.company) - bank_connector.make_payment(payment_order, otp) + bank_connector = get_bank_connector( + payment_order.company_bank_account, payment_order.company + ) + return bank_connector.make_payment(payment_order, otp) @frappe.whitelist() def get_payment_status(payment_order): payment_order = frappe.get_doc("Payment Order", payment_order) - bank_connector = get_bank_connector(payment_order.bank_account, payment_order.company) + bank_connector = get_bank_connector( + payment_order.company_bank_account, payment_order.company + ) return bank_connector.get_payment_status(payment_order) - diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index 4ede821..412c77e 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -27,6 +27,7 @@ def validate_bank_payment_request(self): @frappe.whitelist() def update_unique_and_file_reference_id(self, save=False): + print(save) unique_id = "".join(re.findall(r"[0-9a-zA-Z]", self.name)) unique_id = unique_id[-10:] frappe.db.set_value( diff --git a/india_banking/public/js/payment_order.js b/india_banking/public/js/payment_order.js index 2abe524..ea046f3 100644 --- a/india_banking/public/js/payment_order.js +++ b/india_banking/public/js/payment_order.js @@ -158,14 +158,14 @@ frappe.ui.form.on("Payment Order", { } if (uninitiated_payments > 0 && is_pending) { frm.add_custom_button(__("Initiate Payment"), function () { - frm.trigger("make_payment") + frm.trigger("make_payment"); }); } } } if ( - ( ["Pending", "Initiated"].includes(frm.doc.status)) && + ["Pending", "Initiated"].includes(frm.doc.status) && frm.doc.docstatus == 1 ) { if (frm.has_perm("write") && "summary" in frm.doc) { @@ -175,12 +175,12 @@ frappe.ui.form.on("Payment Order", { pending_status_check += 1; } } - + if (pending_status_check > 0) { frm.add_custom_button(__("Get Status"), function () { frappe.call({ method: - "india_banking.india_banking.doc_events.payment_order.get_payment_status", + "india_banking.india_banking.doc_events.payment_order.get_payment_status", freeze: 1, freeze_message: "Fetching payment status....", args: { @@ -200,29 +200,27 @@ frappe.ui.form.on("Payment Order", { frm.trigger("remove_button"); }, - + make_payment: function (frm) { frappe.call({ method: - "india_banking.india_banking.doc_events.payment_order.make_bank_payment", + "india_banking.india_banking.doctype.bank_connector.bank_connector.make_payment", freeze: true, freeze_message: "Initiating Payment...", args: { - docname: frm.doc.name, + payment_order: frm.doc.name, }, callback: (res) => { if (!res.exc && res.message) { - if(res.message.otp_required){ - this.verify_otp(); - }else{ - frappe.msgprint(res.message); + if (res.message.otp_required) { + frm.trigger("verify_otp"); } } }, }); }, - verify_otp() { + verify_otp(frm) { frappe.prompt( { label: "Enter OTP", @@ -233,17 +231,14 @@ frappe.ui.form.on("Payment Order", { (values) => { frappe.call({ method: - "india_banking.india_banking.doc_events.payment_order.make_bank_payment", + "india_banking.india_banking.doctype.bank_connector.bank_connector.make_payment", freeze: 1, args: { - docname: frm.doc.name, - otp: values.otp, + payment_order: frm.doc.name, + otp: values.otp || "", }, callback: function (r) { - if (r.message) { - frappe.msgprint(r.message); - } - frm.reload_doc(); + // frm.reload_doc(); }, }); }, From c8190a2a0e08385c2e6ea416a7d302636d87eff0 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 23 Dec 2024 02:10:02 +0530 Subject: [PATCH 27/83] refactor: revamped payment status code in bank connector --- .../doctype/bank_connector/bank_connector.py | 269 +++++++++++++++++- india_banking/public/js/payment_order.js | 7 +- india_banking/tasks.py | 6 +- 3 files changed, 274 insertions(+), 8 deletions(-) diff --git a/india_banking/india_banking/doctype/bank_connector/bank_connector.py b/india_banking/india_banking/doctype/bank_connector/bank_connector.py index 7a48ea1..f73f0df 100644 --- a/india_banking/india_banking/doctype/bank_connector/bank_connector.py +++ b/india_banking/india_banking/doctype/bank_connector/bank_connector.py @@ -45,7 +45,7 @@ def check_otp_enabled(self, otp=None): frappe.throw(_("OTP is required for this transaction")) def make_payment(self, payment_order, otp=None): - payment_order = frappe.get_doc("Payment Order", payment_order) + self.check_user_permission() if self.check_otp_enabled(otp): return self.generate_otp(payment_order) @@ -59,6 +59,230 @@ def make_payment(self, payment_order, otp=None): else: return self.make_single_payment(payment_order) + def get_payment_status(self, payment_order): + self.check_user_permission() + if self.bulk_transaction: + return self.get_bulk_payment_status(payment_order) + else: + return self.get_single_payment_status(payment_order) + + def get_single_payment_status(self, payment_order): + for summary_row in payment_order.summary: + if summary_row.payment_status == "Initiated": + self.get_status_response(summary_row, payment_order) + + payment_order.reload() + self.update_payment_status(payment_order) + frappe.msgprint(_("Payment Status Updated")) + + def get_bulk_payment_status(self, payment_order): + response = request.post( + self.base_url, + headers=self.headers, + data=json.dumps(self.get_payload(payment_order, "get_payment_status")), + ) + + # create api request log + create_api_log( + response, "Get Payment Status", payment_order.doctype, payment_order.name + ) + + if response.ok: + status_response = self.get_response_details(response) + payment_status_details = status_response.payment_status_details + + if status_response.status == "Processed": + frappe.msgprint(status_response.message, status_response.file_status) + fs = status_response.file_status + if status_response.file_status in ["FAL", "REJ", "REC"]: + for summary_row in payment_order.summary: + frappe.db.set_value( + "Payment Order Summary", + summary_row.name, + "payment_status", + "Failed" if fs == "FAL" else "Rejected", + ) + payment_entry_doc = frappe.get_doc( + "Payment Entry", summary_row.payment_entry + ) + if payment_entry_doc.docstatus == 1: + payment_entry_doc.cancel() + self.process_bank_payment_requests(payment_order, summary_row) + + if payment_status_details: + for summary_row in payment_order.summary: + summary_row_payment_status = frappe._dict( + payment_status_details.get(summary_row.name, {}) + ) + if ( + summary_row.payment_status == "Initiated" + and summary_row_payment_status + ): + if summary_row_payment_status.transaction_status == "SUC": + frappe.db.set_value( + "Payment Order Summary", + summary_row.name, + { + "reference_number": summary_row_payment_status.host_reference_number, + "payment_status": "Processed", + "message": summary_row_payment_status.host_response_message, + }, + ) + frappe.db.set_value( + "Payment Entry", + summary_row.payment_entry, + "reference_no", + summary_row_payment_status.host_reference_number, + ) + elif summary_row_payment_status.transaction_status == "FAL": + frappe.db.set_value( + "Payment Order Summary", + summary_row.name, + "payment_status", + "Failed", + ) + payment_entry_doc = frappe.get_doc( + "Payment Entry", summary_row.payment_entry + ) + if payment_entry_doc.docstatus == 1: + payment_entry_doc.cancel() + self.process_bank_payment_requests( + payment_order, summary_row + ) + + elif summary_row_payment_status.transaction_status in [ + "RVS", + "REJ", + ]: + frappe.db.set_value( + "Payment Order Summary", + summary_row.name, + "payment_status", + "Rejected", + ) + payment_entry_doc = frappe.get_doc( + "Payment Entry", summary_row.payment_entry + ) + if payment_entry_doc.docstatus == 1: + payment_entry_doc.cancel() + self.process_bank_payment_requests( + payment_order, summary_row + ) + + self.update_payment_status(payment_order) + else: + frappe.throw(msg=status_response.server_message, title="Failed") + else: + frappe.throw("Invalid Request") + + def get_status_response(self, summary_row, payment_order): + response = request.post( + self.base_url, + headers=self.headers, + data=json.dumps(self.get_payload(payment_order, "get_payment_status")), + ) + + # create api request log + create_api_log( + response, "Get Payment Status", payment_order.doctype, payment_order.name + ) + + if response.ok: + status_response = self.get_response_details(response) + if status_response.status == "Processed": + if status_response.utr_number: + frappe.db.set_value( + "Payment Order Summary", + summary_row.name, + "reference_number", + status_response.utr_number, + ) + if summary_row.payment_entry: + frappe.db.set_value( + "Payment Entry", + summary_row.payment_entry, + "reference_no", + status_response.utr_number, + ) + if summary_row.journal_entry_account: + frappe.db.set_value( + "Journal Entry Account", + summary_row.journal_entry_account, + { + "payment_status": "Paid", + "reference_number": status_response.utr_number, + }, + ) + + self.notify_party(summary_row) + + frappe.db.set_value( + "Payment Order Summary", + summary_row.name, + "payment_status", + "Processed", + ) + elif status_response.status == "Pending": + frappe.db.set_value( + "Payment Order Summary", + summary_row.name, + "message", + status_response.message, + ) + + elif status_response.status == "Failed": + frappe.db.set_value( + "Payment Order Summary", + summary_row.name, + { + "payment_status": status_response.status, + "message": status_response.message, + }, + ) + + if summary_row.payment_entry: + payment_entry_doc = frappe.get_doc( + "Payment Entry", summary_row.payment_entry + ) + if payment_entry_doc.docstatus == 1: + payment_entry_doc.cancel() + self.process_bank_payment_requests(payment_order, summary_row) + + if summary_row.journal_entry_account: + frappe.db.set_value( + "Journal Entry Account", + summary_row.journal_entry_account, + "payment_status", + "Failed", + ) + + elif status_response.status == "Rejected": + frappe.db.set_value( + "Payment Order Summary", + summary_row.name, + { + "payment_status": status_response.status, + "message": status_response.message, + }, + ) + + if summary_row.payment_entry: + payment_entry_doc = frappe.get_doc( + "Payment Entry", summary_row.payment_entry + ) + if payment_entry_doc.docstatus == 1: + payment_entry_doc.cancel() + + self.process_bank_payment_requests(payment_order, summary_row) + + if summary_row.journal_entry_account: + frappe.db.set_value( + "Journal Entry Account", + summary_row.journal_entry_account, + "payment_status", + "Failed", + ) + def make_single_payment(self, payment_order): count = 0 for payment_row in payment_order.summary: @@ -383,6 +607,49 @@ def update_payment_status(self, payment_order): message=frappe.get_traceback(), ) + def notify_party(self, summary_row): + if not frappe.get_value( + "India Banking Settings", "India Banking Settings", "notify_party" + ): + return + if summary_row.payment_entry: + default_email_format = ( + frappe.get_single("India Banking Settings").default_email_format + or "Payment Advice" + ) + if default_email_format: + try: + payment_entry = frappe.get_doc( + "Payment Entry", summary_row.payment_entry + ) + frappe.sendmail( + recipients=[ + summary_row.email + or frappe.db.get_value( + "Bank Account", summary_row.bank_account, "email" + ) + ], + subject="Payment Notification", + message="Payment for {0} is completed. Please check the attachment for details".format( + summary_row.party + ), + attachments=[ + { + "fname": "payment_details.pdf", + "fcontent": frappe.get_print( + "Payment Entry", + payment_entry.name, + default_email_format, + as_pdf=True, + ), + } + ], + ) + except Exception as e: + frappe.log_error( + "Payment Email Notification Failed", frappe.get_traceback() + ) + def get_bank_connector(bank_account, company): # Fetch the connector information diff --git a/india_banking/public/js/payment_order.js b/india_banking/public/js/payment_order.js index ea046f3..720e20a 100644 --- a/india_banking/public/js/payment_order.js +++ b/india_banking/public/js/payment_order.js @@ -180,16 +180,13 @@ frappe.ui.form.on("Payment Order", { frm.add_custom_button(__("Get Status"), function () { frappe.call({ method: - "india_banking.india_banking.doc_events.payment_order.get_payment_status", + "india_banking.india_banking.doctype.bank_connector.bank_connector.get_payment_status", freeze: 1, freeze_message: "Fetching payment status....", args: { - docname: frm.doc.name, + payment_order: frm.doc.name, }, callback: function (r) { - if (r.message && !r.exc) { - frappe.msgprint(r.message); - } frm.reload_doc(); }, }); diff --git a/india_banking/tasks.py b/india_banking/tasks.py index 48b805f..80ef4dc 100644 --- a/india_banking/tasks.py +++ b/india_banking/tasks.py @@ -1,6 +1,8 @@ import frappe -from india_banking.india_banking.doc_events.payment_order import get_payment_status +from india_banking.india_banking.doctype.bank_connector.bank_connector import ( + get_payment_status, +) def daily(): @@ -13,7 +15,7 @@ def daily(): for order in orders: try: - frappe.enqueue(get_payment_status, docname=order, queue="short") + frappe.enqueue(get_payment_status, payment_order=order, queue="short") except: frappe.log_error( title="Error in Payment Order Status Cron", From f2e03d1201cb436913b75439de095cc82fcc488e Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 23 Dec 2024 12:29:31 +0530 Subject: [PATCH 28/83] fix: convert the request log to look prettier fix: add filter to the bank account fix: fetch company bank account details during the API call --- .../doctype/bank_connector/bank_connector.py | 19 ++++++++++++------- .../india_banking_request_log.py | 15 ++++++++++++--- india_banking/install.py | 15 ++++++++------- india_banking/public/js/payment_request.js | 12 ++++++++++++ 4 files changed, 44 insertions(+), 17 deletions(-) diff --git a/india_banking/india_banking/doctype/bank_connector/bank_connector.py b/india_banking/india_banking/doctype/bank_connector/bank_connector.py index f73f0df..488fbb7 100644 --- a/india_banking/india_banking/doctype/bank_connector/bank_connector.py +++ b/india_banking/india_banking/doctype/bank_connector/bank_connector.py @@ -7,7 +7,7 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import comma_and, cstr, get_link_to_form, getdate -from requests import request +import requests as request from india_banking.india_banking.doctype.india_banking_request_log.india_banking_request_log import ( create_api_log, @@ -397,21 +397,21 @@ def process_payment_and_response(self, payment_row, payment_order): payment_response = self.get_response_details(response) if not payment_response.status: - return {"payment_status": "", "message": str(response)} + return frappe._dict({"payment_status": "", "message": str(response)}) elif payment_response.status == "ACCEPTED": - return { + return frappe._dict({ "payment_status": "Initiated", "message": payment_response.message, - } + }) elif payment_response.status == "Request Failure": - return {"payment_status": "", "message": "Request Failure"} + return frappe._dict({"payment_status": "", "message": "Request Failure"}) else: - return {"payment_status": "Failed", "message": payment_response.message} + return frappe._dict({"payment_status": "Failed", "message": payment_response.message}) else: - return {"payment_status": "", "message": "Bad Request"} + return frappe._dict({"payment_status": "", "message": "Bad Request"}) def process_bank_payment_requests(self, payment_order, payment_row): key = ( @@ -558,9 +558,14 @@ def handle_otp_response(self, response): ) def get_payload(self, payment_order, action): + bank_account = frappe.get_doc("Bank Account", payment_order.company_bank_account) payment_payload = frappe._dict() payment_payload.doc = payment_order.as_dict(convert_dates_to_str=True) payment_payload.method = action + payment_payload.company_account_number = bank_account.bank_account_no + payment_payload.company_ifsc = bank_account.branch_code + payment_payload.company_bank_account_name = bank_account.account_name + payment_payload.mobile_number = bank_account.mobile_number payment_payload.bulk_transaction = self.bulk_transaction return payment_payload diff --git a/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py b/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py index 7cfa7b6..0f122fb 100644 --- a/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py +++ b/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py @@ -5,10 +5,19 @@ import json from frappe.model.document import Document from requests.models import Response +import requests class IndiaBankingRequestLog(Document): pass +def format_with_indent(data): + if isinstance(data, dict): + return json.dumps(data, indent=4) + elif isinstance(data, requests.structures.CaseInsensitiveDict): + return json.dumps(dict(data), indent=4) + else: + return format_with_indent(json.loads(data)) + @frappe.whitelist() def create_api_log(res, action= None, ref_doctype= None, ref_docname= None): @@ -25,9 +34,9 @@ def create_api_log(res, action= None, ref_doctype= None, ref_docname= None): log_doc.action = action log_doc.url = res.request.url log_doc.method = res.request.method - log_doc.header = json.dumps(dict(res.request.headers), indent=4) - log_doc.payload =json.dumps(res.request.body, indent=4) - log_doc.response = json.dumps(res.json(), indent=4) + log_doc.header = format_with_indent(res.request.headers) + log_doc.payload =format_with_indent(res.request.body) + log_doc.response = format_with_indent(res.text) log_doc.status_code = res.status_code log_doc.reference_doctype = ref_doctype log_doc.reference_docname = ref_docname diff --git a/india_banking/install.py b/india_banking/install.py index 8ed8423..eb3ec44 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -259,12 +259,6 @@ def create_payment_order_custom_fields(): "fieldtype": "Button", "insert_after": "references", }, - { - "label": "Default Mode of Transfer", - "fieldname": "default_mode_of_transfer", - "fieldtype": "Link", - "options": "Mode of Transfer", - }, { "label": "Payment Summary", "fieldname": "payment_summary", @@ -275,10 +269,16 @@ def create_payment_order_custom_fields(): "label": "Is Party Wise", "fieldname": "is_party_wise", "fieldtype": "Check", - "read_only": 1, "hidden": 1, "insert_after": "payment_summary", }, + { + "label": "Default Mode of Transfer", + "fieldname": "default_mode_of_transfer", + "fieldtype": "Link", + "options": "Mode of Transfer", + "insert_after": "payment_summary", + }, { "label": "Summary", "fieldname": "summary", @@ -412,6 +412,7 @@ def create_bank_account_custom_fields(): "label": "Mobile Number", "fieldname": "mobile_number", "insert_after": "iban", + "mandatory_depends_on": "is_company_account", "fieldtype": "Data", }, { diff --git a/india_banking/public/js/payment_request.js b/india_banking/public/js/payment_request.js index 43c147c..99a1773 100644 --- a/india_banking/public/js/payment_request.js +++ b/india_banking/public/js/payment_request.js @@ -19,6 +19,18 @@ frappe.ui.form.on("Payment Request", { }, }; }); + frm.set_query("bank_account", function () { + return { + filters: { + company: frm.doc.company, + is_default: 1, + disabled: 0, + workflow_state: "Approved", + party_type: frm.doc.party_type, + party: frm.doc.party, + }, + }; + }); }, company(frm) { frm.set_query("payment_type", function () { From 041b86f94e0dc65e78e09c7cf7ec8131b79335ac Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 23 Dec 2024 15:40:48 +0530 Subject: [PATCH 29/83] fix: handle payment response json validation erro fix: rename method action in payload --- .../doctype/bank_connector/bank_connector.py | 68 +++++++++++++------ .../india_banking_request_log.py | 42 ++++++++---- .../payment_order_summary.json | 45 +++++++++++- india_banking/public/js/payment_order.js | 3 +- 4 files changed, 118 insertions(+), 40 deletions(-) diff --git a/india_banking/india_banking/doctype/bank_connector/bank_connector.py b/india_banking/india_banking/doctype/bank_connector/bank_connector.py index 488fbb7..f69dbaf 100644 --- a/india_banking/india_banking/doctype/bank_connector/bank_connector.py +++ b/india_banking/india_banking/doctype/bank_connector/bank_connector.py @@ -4,10 +4,10 @@ import json import frappe +import requests as request from frappe import _ from frappe.model.document import Document from frappe.utils import comma_and, cstr, get_link_to_form, getdate -import requests as request from india_banking.india_banking.doctype.india_banking_request_log.india_banking_request_log import ( create_api_log, @@ -294,7 +294,6 @@ def make_single_payment(self, payment_order): payment_response = self.process_payment_and_response( payment_row, payment_order ) - if ( payment_response and "payment_status" in payment_response @@ -360,7 +359,7 @@ def make_single_payment(self, payment_order): return {"message": f"{count} payments initiated"} def process_payment_and_response(self, payment_row, payment_order): - payment_payload = self.get_payload(payment_order, "make_payment") + payment_payload = self.get_payload(payment_order, "intiate_payment") payment_payload.update(payment_row.as_dict(convert_dates_to_str=True)) party_field_name = ( "supplier_name" if payment_row.party_type == "Supplier" else "employee_name" @@ -397,19 +396,30 @@ def process_payment_and_response(self, payment_row, payment_order): payment_response = self.get_response_details(response) if not payment_response.status: - return frappe._dict({"payment_status": "", "message": str(response)}) + return frappe._dict( + {"payment_status": "", "message": str(response.text)} + ) elif payment_response.status == "ACCEPTED": - return frappe._dict({ - "payment_status": "Initiated", - "message": payment_response.message, - }) + return frappe._dict( + { + "payment_status": "Initiated", + "message": payment_response.message, + } + ) elif payment_response.status == "Request Failure": - return frappe._dict({"payment_status": "", "message": "Request Failure"}) + return frappe._dict( + { + "payment_status": "", + "message": payment_response.message or "Request Failure", + } + ) else: - return frappe._dict({"payment_status": "Failed", "message": payment_response.message}) + return frappe._dict( + {"payment_status": "Failed", "message": payment_response.message} + ) else: return frappe._dict({"payment_status": "", "message": "Bad Request"}) @@ -448,23 +458,26 @@ def process_bank_payment_requests(self, payment_order, payment_row): pr_doc.db_set("docstatus", 2) def make_bulk_payment(self, payment_order, otp): - payment_payload = self.get_payload(payment_order, "make_payment") + payment_payload = self.get_payload(payment_order, "intiate_payment") payment_payload.doc.update({"otp": otp}) payment_account_list = [] # Lei number validation - for ref in payment_order.summary: - if ref.mode_of_transfer == "RTGS" and ref.amount >= 500000000: + for summary_row in payment_order.summary: + if ( + summary_row.mode_of_transfer == "RTGS" + and summary_row.amount >= 500000000 + ): lei_number = frappe.db.get_value( - ref.party_type, ref.party, "lei_number" + summary_row.party_type, summary_row.party, "lei_number" ) - payment_account_list.append(ref.account_name + "-" + lei_number) + payment_account_list.append(summary_row.account_name + "-" + lei_number) if not lei_number: frappe.throw("LEI Number required for payment > 50 Cr") else: payment_account_list.append( - ref.account_name + "-" + ref.bank_account_no + summary_row.account_name + "-" + summary_row.bank_account_no ) payment_payload.doc.update( @@ -521,6 +534,7 @@ def verify_otp(self, payment_order, otp): pass def generate_otp(self, payment_order): + return {"otp_required": True} payment_order.update_unique_and_file_reference_id(save=True) payment_order.reload() @@ -545,12 +559,16 @@ def get_response_details(self, response): frappe.throw(_("Invalid Response: Check API Log")) def handle_otp_response(self, response): + # return {"otp_required": True} if response.ok: response_details = self.get_response_details(response) if response_details.status == "success": return {"otp_required": True} else: - frappe.throw(msg=_(cstr(response_details.message)), title=_("Failed")) + frappe.throw( + title=_("Invalid Request"), + msg=_("OTP Initiation Failed: Check API Log"), + ) else: frappe.throw( title=cstr(response.status_code), @@ -558,14 +576,20 @@ def handle_otp_response(self, response): ) def get_payload(self, payment_order, action): - bank_account = frappe.get_doc("Bank Account", payment_order.company_bank_account) + bank_account = frappe.get_doc( + "Bank Account", payment_order.company_bank_account + ) payment_payload = frappe._dict() payment_payload.doc = payment_order.as_dict(convert_dates_to_str=True) + payment_payload.doc.update( + { + "company_account_number": bank_account.bank_account_no, + "company_bank_account_name": bank_account.account_name, + "company_ifsc": bank_account.branch_code, + "mobile_number": bank_account.mobile_number, + } + ) payment_payload.method = action - payment_payload.company_account_number = bank_account.bank_account_no - payment_payload.company_ifsc = bank_account.branch_code - payment_payload.company_bank_account_name = bank_account.account_name - payment_payload.mobile_number = bank_account.mobile_number payment_payload.bulk_transaction = self.bulk_transaction return payment_payload diff --git a/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py b/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py index 0f122fb..dc53068 100644 --- a/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py +++ b/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py @@ -1,33 +1,43 @@ # Copyright (c) 2024, Aerele Technologies Private Limited and contributors # For license information, please see license.txt -import frappe import json + +import frappe +import requests from frappe.model.document import Document from requests.models import Response -import requests + class IndiaBankingRequestLog(Document): pass + def format_with_indent(data): - if isinstance(data, dict): - return json.dumps(data, indent=4) - elif isinstance(data, requests.structures.CaseInsensitiveDict): - return json.dumps(dict(data), indent=4) - else: - return format_with_indent(json.loads(data)) + try: + if isinstance(data, dict): + return json.dumps(data, indent=4) + elif isinstance(data, requests.structures.CaseInsensitiveDict): + return json.dumps(dict(data), indent=4) + else: + return format_with_indent(json.loads(data)) + except: + frappe.log_error( + title="Error in formatting data", message=frappe.get_traceback() + ) + return data @frappe.whitelist() -def create_api_log(res, action= None, ref_doctype= None, ref_docname= None): +def create_api_log(res, action=None, ref_doctype=None, ref_docname=None): """Can create API log From response Args: - res (response object): It is used to obtain an API response. - request_from (str): It is optional for the purposes of the API... + res (response object): It is used to obtain an API response. + request_from (str): It is optional for the purposes of the API... """ - if not isinstance(res, Response): return + if not isinstance(res, Response): + return try: log_doc = frappe.new_doc("India Banking Request Log") @@ -35,13 +45,15 @@ def create_api_log(res, action= None, ref_doctype= None, ref_docname= None): log_doc.url = res.request.url log_doc.method = res.request.method log_doc.header = format_with_indent(res.request.headers) - log_doc.payload =format_with_indent(res.request.body) + log_doc.payload = format_with_indent(res.request.body) log_doc.response = format_with_indent(res.text) log_doc.status_code = res.status_code log_doc.reference_doctype = ref_doctype log_doc.reference_docname = ref_docname log_doc.save() except: - frappe.log_error(title='Error in creating API Log', message=frappe.get_traceback()) + frappe.log_error( + title="Error in creating API Log", message=frappe.get_traceback() + ) else: - frappe.db.commit() \ No newline at end of file + frappe.db.commit() diff --git a/india_banking/india_banking/doctype/payment_order_summary/payment_order_summary.json b/india_banking/india_banking/doctype/payment_order_summary/payment_order_summary.json index 0528c22..3dfce72 100644 --- a/india_banking/india_banking/doctype/payment_order_summary/payment_order_summary.json +++ b/india_banking/india_banking/doctype/payment_order_summary/payment_order_summary.json @@ -15,6 +15,11 @@ "message", "reference_number", "bank_account", + "email", + "branch_code", + "account_name", + "bank_account_no", + "bank", "accounting_section", "account", "tax_withholding_category", @@ -169,7 +174,7 @@ }, { "fieldname": "reference_name", - "fieldtype": "Data", + "fieldtype": "Dynamic Link", "label": "Reference Name", "options": "reference_doctype", "read_only": 1 @@ -191,12 +196,48 @@ "fieldtype": "Data", "label": "Journal Entry Account", "read_only": 1 + }, + { + "fetch_from": "bank_account.email", + "fetch_if_empty": 1, + "fieldname": "email", + "fieldtype": "Data", + "label": "Email", + "options": "Email" + }, + { + "fetch_from": "bank_account.branch_code", + "fetch_if_empty": 1, + "fieldname": "branch_code", + "fieldtype": "Data", + "label": "Branch Code" + }, + { + "fetch_from": "bank_account.account_name", + "fetch_if_empty": 1, + "fieldname": "account_name", + "fieldtype": "Data", + "label": "Account Name" + }, + { + "fetch_from": "account.account_number", + "fetch_if_empty": 1, + "fieldname": "bank_account_no", + "fieldtype": "Data", + "label": "Bank Account No" + }, + { + "fetch_from": "bank_account.bank", + "fetch_if_empty": 1, + "fieldname": "bank", + "fieldtype": "Data", + "label": "Bank" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-09-26 11:13:44.056821", + "modified": "2024-12-23 15:36:49.167883", "modified_by": "Administrator", "module": "India Banking", "name": "Payment Order Summary", diff --git a/india_banking/public/js/payment_order.js b/india_banking/public/js/payment_order.js index 720e20a..226eee4 100644 --- a/india_banking/public/js/payment_order.js +++ b/india_banking/public/js/payment_order.js @@ -212,6 +212,7 @@ frappe.ui.form.on("Payment Order", { if (res.message.otp_required) { frm.trigger("verify_otp"); } + frm.reload_doc(); } }, }); @@ -235,7 +236,7 @@ frappe.ui.form.on("Payment Order", { otp: values.otp || "", }, callback: function (r) { - // frm.reload_doc(); + frm.reload_doc(); }, }); }, From 1c0b7262672eea7685e949410ee31198a152b203 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 24 Dec 2024 13:04:07 +0530 Subject: [PATCH 30/83] refactor: refactor and optimize --- .../doctype/bank_connector/bank_connector.py | 9 +- india_banking/install.py | 4 +- india_banking/overrides/payment_order.py | 166 ++++----- india_banking/public/js/payment_order.js | 335 ++++++++++-------- 4 files changed, 248 insertions(+), 266 deletions(-) diff --git a/india_banking/india_banking/doctype/bank_connector/bank_connector.py b/india_banking/india_banking/doctype/bank_connector/bank_connector.py index f69dbaf..547cfa8 100644 --- a/india_banking/india_banking/doctype/bank_connector/bank_connector.py +++ b/india_banking/india_banking/doctype/bank_connector/bank_connector.py @@ -294,6 +294,7 @@ def make_single_payment(self, payment_order): payment_response = self.process_payment_and_response( payment_row, payment_order ) + if ( payment_response and "payment_status" in payment_response @@ -500,6 +501,7 @@ def make_bulk_payment(self, payment_order, otp): def process_bulk_payment_response(self, response, payment_order): payment_response = self.get_response_details(response) + if payment_response.get("status", "") == "ACCEPTED": frappe.db.set_value( "Payment Order", payment_order.name, "status", "Initiated" @@ -522,10 +524,10 @@ def process_bulk_payment_response(self, response, payment_order): }, ) - return {"message": "Payment Initiated"} + frappe.msgprint(_("Payment Initiated")) elif payment_response.get("status", "") == "Failed": - return {"message": "Failed - " + cstr(payment_response.get("message"))} + frappe.msgprint(_("Failed - " + cstr(payment_response.get("message")))) else: frappe.throw(_("Invalid Response: Check API Log")) @@ -534,10 +536,11 @@ def verify_otp(self, payment_order, otp): pass def generate_otp(self, payment_order): - return {"otp_required": True} payment_order.update_unique_and_file_reference_id(save=True) payment_order.reload() + # return {"otp_required": True} # For testing response + # Generate OTP using POST request response = request.post( self.base_url, diff --git a/india_banking/install.py b/india_banking/install.py index eb3ec44..29f9ddd 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -2,7 +2,6 @@ import frappe from frappe import make_property_setter from frappe.custom.doctype.custom_field.custom_field import create_custom_fields -from frappe.custom.doctype.property_setter.property_setter import delete_property_setter from india_banking.default import DEFAULT_MODE_OF_TRANSFERS, STD_BANK_LIST @@ -269,7 +268,8 @@ def create_payment_order_custom_fields(): "label": "Is Party Wise", "fieldname": "is_party_wise", "fieldtype": "Check", - "hidden": 1, + "hidden": 0, + "no_copy": 1, "insert_after": "payment_summary", }, { diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index 412c77e..0f37ef0 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -27,7 +27,6 @@ def validate_bank_payment_request(self): @frappe.whitelist() def update_unique_and_file_reference_id(self, save=False): - print(save) unique_id = "".join(re.findall(r"[0-9a-zA-Z]", self.name)) unique_id = unique_id[-10:] frappe.db.set_value( @@ -39,6 +38,7 @@ def update_unique_and_file_reference_id(self, save=False): frappe.db.commit() def validate(self): + frappe.throw("validate") self.validate_summary() for payment_info in self.summary: if ( @@ -232,132 +232,88 @@ def update_payment_status(self, cancel=False): @frappe.whitelist() -def get_party_summary(references, company_bank_account): +def get_party_summary(references, company_bank_account, is_party_wise=None): references = json.loads(references) if not len(references) or not company_bank_account: return # Considering the following dimensions to group payments # (party_type, party, bank_account, account, cost_center, project) - def _get_unique_key(ref=None, summarise_field=False): - summarise_payment_based_on = frappe.get_single( - "India Banking Settings" - ).summarise_payment_based_on - - if summarise_payment_based_on == "Party": - if summarise_field: - return ( - "party_type", - "party", - "bank_account", - "account", - "cost_center", - "project", - "tax_withholding_category", - "reference_doctype", - "payment_entry", - "journal_entry", - "journal_entry_account", - ) - - return ( - ref.party_type, - ref.party, - ref.bank_account, - ref.account, - ref.cost_center, - ref.project, - ref.tax_withholding_category, - ref.reference_doctype, - ref.payment_entry, - ref.journal_entry, - ref.journal_entry_account, - ) - - elif summarise_payment_based_on == "Voucher": - if summarise_field: - return ( - "party_type", - "party", - "reference_doctype", - "reference_name", - "bank_account", - "account", - "cost_center", - "project", - "tax_withholding_category", - "payment_entry", - "journal_entry", - "journal_entry_account", - ) - - return ( - ref.party_type, - ref.party, - ref.reference_doctype, - ref.reference_name, - ref.bank_account, - ref.account, - ref.cost_center, - ref.project, - ref.tax_withholding_category, - ref.payment_entry, - ref.journal_entry, - ref.journal_entry_account, - ) + def _get_unique_key(reference=None, summarise_field_only=False): + summarise_field = [ + "party_type", + "party", + "bank_account", + "account", + "cost_center", + "project", + "tax_withholding_category", + "reference_doctype", + "reference_name", + "payment_entry", + "journal_entry", + "journal_entry_account", + ] + if is_party_wise: + summarise_field.remove("reference_name") + + if summarise_field_only: + return tuple(summarise_field) + else: + return tuple([reference.get(field, "") for field in summarise_field]) summary = {} - for ref in references: - ref = frappe._dict(ref) - key = _get_unique_key(ref) + for reference in references: + reference = frappe._dict(reference) + key = _get_unique_key(reference) if key in summary: - summary[key] += ref.amount + summary[key] += reference.amount else: - summary[key] = ref.amount + summary[key] = reference.amount result = [] for key, val in summary.items(): summary_line_item = { - k: v for k, v in zip(_get_unique_key(summarise_field=True), key) + k: v for k, v in zip(_get_unique_key(summarise_field_only=True), key) } - summary_line_item["amount"] = val - summarise_payment_based_on = frappe.get_single( - "India Banking Settings" - ).summarise_payment_based_on - if summarise_payment_based_on == "Party": - summary_line_item["is_party_wise"] = 1 - else: - summary_line_item["is_party_wise"] = 0 + party_bank = frappe.db.get_value( + "Bank Account", summary_line_item["bank_account"], "bank" + ) + company_bank = frappe.db.get_value("Bank Account", company_bank_account, "bank") - result.append(summary_line_item) + summary_line_item.update( + { + "amount": val, + "mode_of_transfer": get_mode_of_transfer(val, party_bank, company_bank), + } + ) - for row in result: - party_bank = frappe.db.get_value("Bank Account", row["bank_account"], "bank") - company_bank = frappe.db.get_value("Bank Account", company_bank_account, "bank") - row["mode_of_transfer"] = None - if party_bank == company_bank: - mode_of_transfer = frappe.db.get_value( - "Mode of Transfer", {"is_bank_specific": 1, "bank": party_bank} - ) - if mode_of_transfer: - row["mode_of_transfer"] = mode_of_transfer - else: - mot = frappe.db.get_value( - "Mode of Transfer", - { - "minimum_limit": ["<=", row["amount"]], - "maximum_limit": [">", row["amount"]], - "is_bank_specific": 0, - }, - order_by="priority asc", - ) - if mot: - row["mode_of_transfer"] = mot + result.append(summary_line_item) return result +def get_mode_of_transfer(amount, party_bank, company_bank): + mode_of_transfer = None + if party_bank == company_bank: + mode_of_transfer = frappe.db.get_value( + "Mode of Transfer", {"is_bank_specific": 1, "bank": party_bank} + ) + else: + mode_of_transfer = frappe.db.get_value( + "Mode of Transfer", + { + "minimum_limit": ["<=", amount], + "maximum_limit": [">", amount], + "is_bank_specific": 0, + }, + order_by="priority asc", + ) + + return mode_of_transfer + + def get_bank_entry_for_payroll(filters=None): if filters and filters.get("refrence_name"): condition = "AND jea.reference_name = '{0}'".format( diff --git a/india_banking/public/js/payment_order.js b/india_banking/public/js/payment_order.js index 226eee4..7b3792c 100644 --- a/india_banking/public/js/payment_order.js +++ b/india_banking/public/js/payment_order.js @@ -1,33 +1,67 @@ frappe.ui.form.on("Payment Order", { onload(frm) { + // Set summary based on party or voucher + frappe.db + .get_single_value("India Banking Settings", "summarise_payment_based_on") + .then((res) => { + if (res === "Party") { + frm.set_value("is_party_wise", 1); + } + }); + + // Clear the references table for new documents if (frm.is_new()) { - cur_frm.clear_table("references"); + if (frm.doc.references) { + cur_frm.clear_table("references"); + } } - frm.set_query("company_bank_account", function (doc) { + // Set query for the company_bank_account field + frm.set_query("company_bank_account", () => { return { filters: { - company: doc.company, + company: frm.doc.company, is_company_account: 1, - workflow_state: "Approved", }, }; }); - frm.set_query("mode_of_transfer", "summary", function () { + + // Set query for the mode_of_transfer field in the summary child table + frm.set_query("mode_of_transfer", "summary", () => { return { filters: { disabled: 0, }, }; }); + + // Set properties for the summary table + const summaryField = "summary"; + frm.set_df_property(summaryField, "cannot_delete_rows", true); + frm.set_df_property(summaryField, "cannot_add_rows", true); + }, + + refresh(frm) { + frm.remove_custom_button("Payment Entry", "Get Payments from"); + frm.remove_custom_button("Payment Request", "Get Payments from"); + + frm.trigger("set_get_payments_from_buttons"); + + frm.trigger("set_payment_and_status_buttons"); + + frm.trigger("remove_button"); }, get_payments_from_payment_request(frm) { + // Ensure references table is clean before processing frm.trigger("remove_row_if_empty"); - let docs = frm.doc.references?.map((doc) => { - return doc.payment_request; - }); + // Collect existing payment requests from references table, if any + const existingPaymentRequests = (frm.doc.references || []).map( + (reference) => reference.payment_request + ); + + // Use map_current_doc utility to fetch and map payment requests erpnext.utils.map_current_doc({ method: "india_banking.overrides.payment_request.make_payment_order", source_doctype: "Payment Request", @@ -41,18 +75,22 @@ frappe.ui.form.on("Payment Order", { docstatus: 1, status: ["=", "Initiated"], bank: frm.doc.bank, - name: ["not in", docs], + name: ["not in", existingPaymentRequests], company: frm.doc.company, }, }); }, get_payments_from_payment_entry(frm) { + // Ensure references table is clean before processing frm.trigger("remove_row_if_empty"); - let docs = frm.doc.references?.map((doc) => { - return doc.payment_entry; - }); + // Collect existing payment entries from the references table, if any + const existingPaymentEntries = (frm.doc.references || []).map( + (reference) => reference.payment_entry + ); + + // Use map_current_doc utility to fetch and map payment entries erpnext.utils.map_current_doc({ method: "india_banking.overrides.payment_entry.make_payment_order", source_doctype: "Payment Entry", @@ -63,7 +101,7 @@ frappe.ui.form.on("Payment Order", { }, get_query_filters: { docstatus: 1, - name: ["not in", docs], + name: ["not in", existingPaymentEntries], source_doctype: ["!=", "Payment Request"], }, }); @@ -87,115 +125,111 @@ frappe.ui.form.on("Payment Order", { label: "Entry Type", fieldname: "voucher_type", options: "Bank Entry", - hidden: 1, + hidden: true, }, { fieldtype: "Currency", label: "Amount", fieldname: "total", - hidden: 1, + hidden: true, }, ], get_query: function () { - let docs = frm.doc.references?.map((doc) => { - return doc.reference_name; - }); - let unique_accounts = [...new Set(docs)]; + // Extract unique reference names from the references table + const uniqueAccounts = [ + ...new Set( + (frm.doc.references || []).map( + (reference) => reference.reference_name + ) + ), + ]; + return { query: "india_banking.overrides.journal_entry.get_bank_entry", filters: { - docs: unique_accounts, + docs: uniqueAccounts, }, }; }, }); }, - refresh(frm) { - frm.set_df_property("summary", "cannot_delete_rows", true); - frm.set_df_property("summary", "cannot_add_rows", true); - - frm.remove_custom_button("Payment Entry", "Get Payments from"); - frm.remove_custom_button("Payment Request", "Get Payments from"); - - if (frm.doc.docstatus == 0) { - frm.add_custom_button( - __("Payment Request"), - function () { - frm.trigger("get_payments_from_payment_request"); + set_get_payments_from_buttons(frm) { + if (frm.doc.docstatus === 0) { + // Define an array of payment sources and their respective triggers + const paymentSources = [ + { + label: __("Payment Request"), + trigger: "get_payments_from_payment_request", }, - __("Get Payments from") - ); - - frm.add_custom_button( - __("Payment Entry"), - function () { - frm.trigger("get_payments_from_payment_entry"); + { + label: __("Payment Entry"), + trigger: "get_payments_from_payment_entry", }, - __("Get Payments from") - ); - - frm.add_custom_button( - __("Bank Entry(JV)"), - function () { - frm.trigger("get_payments_from_journal_entry"); + { + label: __("Bank Entry(JV)"), + trigger: "get_payments_from_journal_entry", }, - __("Get Payments from") - ); + ]; + + // Add custom buttons for each payment source + paymentSources.forEach((source) => { + frm.add_custom_button( + source.label, + () => frm.trigger(source.trigger), + __("Get Payments from") + ); + }); } + }, - let is_pending = false; - if (frm.doc.status == "Pending" && frm.doc.docstatus == 1) { - if (frm.has_perm("write") && "summary" in frm.doc) { - var uninitiated_payments = 0; - for (var i = 0; i < frm.doc.summary.length; i++) { - if (!frm.doc.summary[i].payment_initiated) { - uninitiated_payments += 1; - } - if (frm.doc.summary[i].payment_status == "Pending") { - is_pending = true; - } - } - if (uninitiated_payments > 0 && is_pending) { - frm.add_custom_button(__("Initiate Payment"), function () { - frm.trigger("make_payment"); - }); - } + set_payment_and_status_buttons(frm) { + // Check if the document is in a pending state and user has write permissions + if ( + frm.doc.status === "Pending" && + frm.doc.docstatus === 1 && + frm.has_perm("write") + ) { + // Check if any summary item has a payment status of "Pending" + const hasPendingPayments = frm.doc.summary.some( + (item) => item.payment_status === "Pending" + ); + + if (hasPendingPayments) { + // Add a custom button to initiate payment + frm.add_custom_button(__("Initiate Payment"), () => { + frm.trigger("make_payment"); + }); } } if ( ["Pending", "Initiated"].includes(frm.doc.status) && - frm.doc.docstatus == 1 + frm.doc.docstatus === 1 && + frm.has_perm("write") ) { - if (frm.has_perm("write") && "summary" in frm.doc) { - var pending_status_check = 0; - for (var j = 0; j < frm.doc.summary.length; j++) { - if (frm.doc.summary[j].payment_status == "Initiated") { - pending_status_check += 1; - } - } + // Check if any summary item has a payment status of "Initiated" + const hasInitiatedStatus = frm.doc.summary.some( + (item) => item.payment_status === "Initiated" + ); - if (pending_status_check > 0) { - frm.add_custom_button(__("Get Status"), function () { - frappe.call({ - method: - "india_banking.india_banking.doctype.bank_connector.bank_connector.get_payment_status", - freeze: 1, - freeze_message: "Fetching payment status....", - args: { - payment_order: frm.doc.name, - }, - callback: function (r) { - frm.reload_doc(); - }, - }); + if (hasInitiatedStatus) { + frm.add_custom_button(__("Get Status"), () => { + frappe.call({ + method: + "india_banking.india_banking.doctype.bank_connector.bank_connector.get_payment_status", + freeze: true, + freeze_message: __("Fetching payment status..."), + args: { + payment_order: frm.doc.name, + }, + callback: function (response) { + frm.reload_doc(); + }, }); - } + }); } } - - frm.trigger("remove_button"); }, make_payment: function (frm) { @@ -203,17 +237,18 @@ frappe.ui.form.on("Payment Order", { method: "india_banking.india_banking.doctype.bank_connector.bank_connector.make_payment", freeze: true, - freeze_message: "Initiating Payment...", + freeze_message: __("Initiating Payment..."), args: { payment_order: frm.doc.name, }, callback: (res) => { - if (!res.exc && res.message) { - if (res.message.otp_required) { - frm.trigger("verify_otp"); - } - frm.reload_doc(); + if (res.message && res.message.otp_required) { + // If OTP is required, trigger OTP verification + frm.trigger("verify_otp"); } + + // Reload the form to reflect any changes (whether OTP is required or not) + frm.reload_doc(); }, }); }, @@ -221,58 +256,68 @@ frappe.ui.form.on("Payment Order", { verify_otp(frm) { frappe.prompt( { - label: "Enter OTP", - place_holder: "Enter", + label: __("Enter OTP"), + place_holder: "Enter the OTP sent to your registered mobile number", fieldname: "otp", fieldtype: "Data", + reqd: true, // Make the OTP field mandatory }, (values) => { + // Ensure the OTP is not blank + const otp = values.otp || ""; + if (!otp.trim()) { + frappe.msgprint({ + title: __("Invalid OTP"), + message: __("Please enter a valid OTP."), + indicator: "red", + }); + return; + } + frappe.call({ method: "india_banking.india_banking.doctype.bank_connector.bank_connector.make_payment", - freeze: 1, + freeze: true, + freeze_message: __("Verifying OTP and processing payment..."), args: { payment_order: frm.doc.name, - otp: values.otp || "", + otp: otp, }, callback: function (r) { - frm.reload_doc(); + if (!r.exc) { + frm.reload_doc(); // Reload form to reflect changes + } }, }); }, - "Sent an OTP to the registered Mobile number", - "Proceed" + __("Sent an OTP to your registered mobile number"), + __("Proceed") ); }, remove_button: function (frm) { - // remove custom button of order type that is not importedz + // Remove the "Create Journal Entries" button frm.remove_custom_button("Create Journal Entries"); + + // Check conditions for removing "Get Payments from" buttons if ( (frm.doc.references.length > 0 && frm.doc.payment_order_type) || frm.doc.docstatus != 0 ) { - if ( - frm.doc.payment_order_type == "Payment Request" || - frm.doc.docstatus != 0 - ) { - frm.remove_custom_button("Bank Entry(JV)", "Get Payments from"); - frm.remove_custom_button("Payment Entry", "Get Payments from"); - } - if ( - frm.doc.payment_order_type == "Payment Entry" || - frm.doc.docstatus != 0 - ) { - frm.remove_custom_button("Bank Entry(JV)", "Get Payments from"); - frm.remove_custom_button("Payment Request", "Get Payments from"); - } - if ( - frm.doc.payment_order_type == "Payment Entry" || - frm.doc.docstatus != 0 - ) { - frm.remove_custom_button("Payment Request", "Get Payments from"); - frm.remove_custom_button("Bank Entry(JV)", "Get Payments from"); - } + // Define the mapping of payment_order_type to buttons + const buttonMapping = { + "Payment Request": ["Bank Entry(JV)", "Payment Entry"], + "Payment Entry": ["Bank Entry(JV)", "Payment Request"], + "Journal Entry": ["Payment Request", "Payment Entry"], + }; + + // Get the relevant buttons based on the payment_order_type + const buttonsToRemove = buttonMapping[frm.doc.payment_order_type] || []; + + // Iterate over the buttons and remove them + buttonsToRemove.forEach((button) => { + frm.remove_custom_button(button, "Get Payments from"); + }); } }, @@ -290,42 +335,20 @@ frappe.ui.form.on("Payment Order", { args: { references: frm.doc.references, company_bank_account: frm.doc.company_bank_account, + is_party_wise: frm.doc.is_party_wise, }, freeze: true, callback: function (r) { - let is_party_wise = 0; if (r.message && !r.exc) { - let summary_data = r.message; frm.clear_table("summary"); - var doc_total = 0; - for (var i = 0; i < summary_data.length; i++) { - if (summary_data[i].is_party_wise && !is_party_wise) { - is_party_wise = 1; - } - doc_total += summary_data[i].amount; - let row = frm.add_child("summary"); - row.party_type = summary_data[i].party_type; - row.party = summary_data[i].party; - row.amount = summary_data[i].amount; - row.bank_account = summary_data[i].bank_account; - row.account = summary_data[i].account; - row.mode_of_transfer = summary_data[i].mode_of_transfer; - row.cost_center = summary_data[i].cost_center; - row.project = summary_data[i].project; - row.tax_withholding_category = - summary_data[i].tax_withholding_category; - row.reference_doctype = summary_data[i].reference_doctype; - row.reference_name = summary_data[i].reference_name; - row.payment_entry = summary_data[i].payment_entry; - row.journal_entry = summary_data[i].journal_entry; - row.journal_entry_account = summary_data[i].journal_entry_account; - } - if (is_party_wise) { - frm.set_value("is_party_wise", 1); - } else { - frm.set_value("is_party_wise", 0); - } - frm.refresh_field("summary"); + const summary_data = r.message; + let doc_total = 0; + summary_data.forEach(function (item) { + frm.add_child("summary", item); + doc_total += item.amount; // Calculate total amount + }); + + // Set total amount in the form frm.doc.total = doc_total; frm.refresh_fields(); } From 62fe81f19a8dbfc96ab762670e5927b63c27db79 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 24 Dec 2024 13:08:49 +0530 Subject: [PATCH 31/83] typo: remove debugger-throw on payment order validation --- india_banking/overrides/payment_order.py | 1 - 1 file changed, 1 deletion(-) diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index 0f37ef0..1bddfec 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -38,7 +38,6 @@ def update_unique_and_file_reference_id(self, save=False): frappe.db.commit() def validate(self): - frappe.throw("validate") self.validate_summary() for payment_info in self.summary: if ( From a12094fc80ff20814df604e374b3291baaa6bcfd Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 24 Dec 2024 13:34:02 +0530 Subject: [PATCH 32/83] feat: implement workflow enabling/disabling functionality in IndiaBankingSettings --- .../india_banking_settings.json | 11 +++++-- .../india_banking_settings.py | 15 +++++++-- india_banking/overrides/journal_entry.py | 32 ++++++++++++------- india_banking/overrides/payment_request.py | 8 ++++- india_banking/public/js/payment_request.js | 11 +++---- 5 files changed, 54 insertions(+), 23 deletions(-) diff --git a/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.json b/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.json index ef34a1e..e07f69a 100644 --- a/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.json +++ b/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.json @@ -8,7 +8,8 @@ "summarise_payment_based_on", "notify_party", "column_break_wjye", - "default_email_format" + "default_email_format", + "activate_workflow_on_bank_account" ], "fields": [ { @@ -32,12 +33,18 @@ "fieldname": "notify_party", "fieldtype": "Check", "label": "Notify Party" + }, + { + "default": "1", + "fieldname": "activate_workflow_on_bank_account", + "fieldtype": "Check", + "label": "Activate Workflow on Bank Account" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-12-09 16:07:39.909616", + "modified": "2024-12-24 13:59:21.431509", "modified_by": "Administrator", "module": "India Banking", "name": "India Banking Settings", diff --git a/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.py b/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.py index c7f4809..18fb72e 100644 --- a/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.py +++ b/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.py @@ -1,9 +1,20 @@ # Copyright (c) 2024, Aerele Technologies Private Limited and contributors # For license information, please see license.txt -# import frappe +import frappe from frappe.model.document import Document class IndiaBankingSettings(Document): - pass + def validate(self): + self.enable_or_disable_workflow_to_bank_account() + + def enable_or_disable_workflow_to_bank_account(self): + """Enable or disable workflow to bank account based on settings.""" + if frappe.db.exists("Workflow", "Bank Account Approval"): + frappe.set_value( + "Workflow", + "Bank Account Approval", + "is_active", + self.activate_workflow_on_bank_account, + ) diff --git a/india_banking/overrides/journal_entry.py b/india_banking/overrides/journal_entry.py index d28d913..ec52dea 100644 --- a/india_banking/overrides/journal_entry.py +++ b/india_banking/overrides/journal_entry.py @@ -43,20 +43,25 @@ def update_bank_entry(source, target): def get_employee_payemnt_details(journal_accounts): + fields = [ + "name", + "party", + "party_type", + "bank", + "branch_code", + "bank_account_no", + "mobile_number", + "email", + ] + activate_workflow_on_bank_account = frappe.get_single( + "India Banking Settings" + ).activate_workflow_on_bank_account + if activate_workflow_on_bank_account: + fields.append("workflow_state") + employee_bank_account_details = frappe.db.get_all( "Bank Account", {"party_type": "Employee", "disabled": 0, "is_default": 1}, - [ - "name", - "party", - "party_type", - "bank", - "branch_code", - "bank_account_no", - "mobile_number", - "email", - "workflow_state", - ], ) employee_bank_account_details = { @@ -76,7 +81,10 @@ def get_employee_payemnt_details(journal_accounts): ) ) else: - if employee_bank_details.get("workflow_state") != "Approved": + if ( + employee_bank_details.get("workflow_state") != "Approved" + and activate_workflow_on_bank_account + ): link = frappe.utils.get_link_to_form( "Bank Account", employee_bank_details.get("name") ) diff --git a/india_banking/overrides/payment_request.py b/india_banking/overrides/payment_request.py index 9a646ff..7975adb 100644 --- a/india_banking/overrides/payment_request.py +++ b/india_banking/overrides/payment_request.py @@ -114,7 +114,13 @@ def valdidate_bank_for_wire_transfer(self): "Bank Account", self.bank_account, "workflow_state" ) - if self.mode_of_payment == "Wire Transfer" and status != "Approved": + if ( + self.mode_of_payment == "Wire Transfer" + and status != "Approved" + and frappe.get_single( + "India Banking Settings" + ).activate_workflow_on_bank_account + ): frappe.throw("Cannot proceed with un-approved bank account") except Exception: frappe.throw("Workflow Not Found for Bank Account") diff --git a/india_banking/public/js/payment_request.js b/india_banking/public/js/payment_request.js index 99a1773..670da06 100644 --- a/india_banking/public/js/payment_request.js +++ b/india_banking/public/js/payment_request.js @@ -25,7 +25,6 @@ frappe.ui.form.on("Payment Request", { company: frm.doc.company, is_default: 1, disabled: 0, - workflow_state: "Approved", party_type: frm.doc.party_type, party: frm.doc.party, }, @@ -42,7 +41,7 @@ frappe.ui.form.on("Payment Request", { }); }, mode_of_payment(frm) { - var conditions = get_bank_query_conditions(frm); + let conditions = get_bank_query_conditions(frm); if (frm.doc.mode_of_payment == "Wire Transfer") { frm.set_query("bank_account", function () { return { @@ -52,7 +51,7 @@ frappe.ui.form.on("Payment Request", { } }, party_type(frm) { - var conditions = get_bank_query_conditions(frm); + let conditions = get_bank_query_conditions(frm); if (frm.doc.mode_of_payment == "Wire Transfer") { frm.set_query("bank_account", function () { return { @@ -62,7 +61,7 @@ frappe.ui.form.on("Payment Request", { } }, party(frm) { - var conditions = get_bank_query_conditions(frm); + let conditions = get_bank_query_conditions(frm); if (frm.doc.mode_of_payment == "Wire Transfer") { frm.set_query("bank_account", function () { return { @@ -73,8 +72,8 @@ frappe.ui.form.on("Payment Request", { }, }); -var get_bank_query_conditions = function (frm) { - var conditions = {}; +const get_bank_query_conditions = function (frm) { + let conditions = {}; if (frm.doc.party_type) { conditions["party_type"] = frm.doc.party_type; } From a82af1a1ad3afb3de913953542b8ddc9ed64651c Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 24 Dec 2024 20:37:40 +0530 Subject: [PATCH 33/83] safety_commit --- india_banking/overrides/journal_entry.py | 146 +++++++++------------ india_banking/overrides/payment_entry.py | 79 ++++++----- india_banking/overrides/payment_order.py | 35 ----- india_banking/overrides/payment_request.py | 40 +++--- 4 files changed, 128 insertions(+), 172 deletions(-) diff --git a/india_banking/overrides/journal_entry.py b/india_banking/overrides/journal_entry.py index ec52dea..052da60 100644 --- a/india_banking/overrides/journal_entry.py +++ b/india_banking/overrides/journal_entry.py @@ -1,4 +1,9 @@ import frappe +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_accounting_dimensions, +) +from frappe.query_builder import DocType +from frappe.utils import comma_sep @frappe.whitelist() @@ -6,26 +11,66 @@ def make_payment_order(source_name, target_doc=None, args=None): from frappe.model.mapper import get_mapped_doc def update_bank_entry(source, target): + JournalEntryAccount = DocType("Journal Entry Account") + BankAccount = DocType("Bank Account") + + select_field = [ + "name", + "account", + "cost_center", + "project", + "debit as amount", + "party as employee", + "party_type", + "parent as journal", + ] + select_field.extend(get_accounting_dimensions()) + + # Build the query + query = ( + frappe.qb.from_(JournalEntryAccount) + .join(BankAccount) + .on(JournalEntryAccount.party == BankAccount.party) + .select( + *[ + getattr(JournalEntryAccount, field.split(" as ")[0]).as_( + field.split(" as ")[1] + ) + if " as " in field + else getattr(JournalEntryAccount, field) + for field in select_field + ], + BankAccount.name.as_("party_bank_account"), + ) + .where( + (JournalEntryAccount.parent == source.name) + & (JournalEntryAccount.party_type == "Employee") + & (JournalEntryAccount.payment_status.notin(["Paid", "Ordered"])) + & (BankAccount.disabled == 0) + & (BankAccount.is_default == 1) + ) + ) + + journal_accounts = query.run(as_dict=True) + target.payment_order_type = "Journal Entry" target.docstaus = 0 target.status = "Pending" - journal_accounts = frappe.db.sql( - """ - SELECT - name, account, against_account, cost_center, debit as amount, exchange_rate, parent, - party as employee, party_type, parent as journal - FROM - `tabJournal Entry Account` - WHERE - parent = %s AND party_type = 'Employee' AND payment_status NOT IN ('Paid', 'Ordered')""", - source.name, - as_dict=1, - ) - - if employee_payemnt_details := get_employee_payemnt_details(journal_accounts): - for ref in employee_payemnt_details: - target.append("references", ref) + for journal_account in journal_accounts: + journal_account = frappe._dict(journal_account) + details = { + "reference_doctype": "Journal Entry", + "reference_name": journal_account.journal, + "journal_entry_account": journal_account.name, + "amount": journal_account.amount, + "party_type": "Employee", + "party": journal_account.employee, + "mode_of_payment": "", + "bank_account": journal_account.party_bank_account, + "account": journal_account.account, + } + target.append("references", details) doclist = get_mapped_doc( "Journal Entry", @@ -42,75 +87,6 @@ def update_bank_entry(source, target): return doclist -def get_employee_payemnt_details(journal_accounts): - fields = [ - "name", - "party", - "party_type", - "bank", - "branch_code", - "bank_account_no", - "mobile_number", - "email", - ] - activate_workflow_on_bank_account = frappe.get_single( - "India Banking Settings" - ).activate_workflow_on_bank_account - if activate_workflow_on_bank_account: - fields.append("workflow_state") - - employee_bank_account_details = frappe.db.get_all( - "Bank Account", - {"party_type": "Employee", "disabled": 0, "is_default": 1}, - ) - - employee_bank_account_details = { - detail.get("party"): detail for detail in employee_bank_account_details - } - - payment_details = [] - - for journal_account in journal_accounts: - employee_bank_details = employee_bank_account_details.get( - journal_account.get("employee", "") - ) - if not employee_bank_details: - frappe.throw( - "Default Bank Account not found for Employee {0}".format( - journal_account.get("employee") - ) - ) - else: - if ( - employee_bank_details.get("workflow_state") != "Approved" - and activate_workflow_on_bank_account - ): - link = frappe.utils.get_link_to_form( - "Bank Account", employee_bank_details.get("name") - ) - frappe.throw( - "Bank Account({1}) for Employee {0} is not approved".format( - journal_account.get("employee"), link - ) - ) - - journal_account = frappe._dict(journal_account) - details = { - "reference_doctype": "Journal Entry", - "reference_name": journal_account.journal, - "journal_entry_account": journal_account.name, - "amount": journal_account.amount, - "party_type": "Employee", - "party": journal_account.employee, - "mode_of_payment": "", - "bank_account": employee_bank_details.name, - "account": journal_account.account, - } - payment_details.append(details) - - return payment_details - - @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_bank_entry(doctype, txt, searchfield, start, page_len, filters, as_dict): diff --git a/india_banking/overrides/payment_entry.py b/india_banking/overrides/payment_entry.py index 392690e..f966c1b 100644 --- a/india_banking/overrides/payment_entry.py +++ b/india_banking/overrides/payment_entry.py @@ -28,49 +28,58 @@ def set_missing_values(source, target): if source.paid_to: account = source.paid_to - for dimension in get_accounting_dimensions(): - target.update({dimension: source.get(dimension, "")}) + def _update_dimensions(source): + return { + dimension: source.get(dimension, "") + for dimension in get_accounting_dimensions() + } if source.references: + reference = { + "reference_doctype": source.references[0].reference_doctype, + "reference_name": source.references[0].reference_name, + "amount": source.references[0].total_amount, + "party_type": source.party_type, + "party": source.party, + "mode_of_payment": source.mode_of_payment, + "bank_account": get_party_bank_account( + source.get("party_type"), source.get("party") + ) + if source.get("party_type") + else "", + "account": account, + "cost_center": source.cost_center, + "project": source.project, + "payment_entry": source.name, + } + reference.update(_update_dimensions(source)) + target.append( "references", - { - "reference_doctype": source.references[0].reference_doctype, - "reference_name": source.references[0].reference_name, - "amount": source.references[0].total_amount, - "party_type": source.party_type, - "party": source.party, - "mode_of_payment": source.mode_of_payment, - "bank_account": get_party_bank_account( - source.get("party_type"), source.get("party") - ) - if source.get("party_type") - else "", - "account": account, - "cost_center": source.cost_center, - "project": source.project, - "payment_entry": source.name, - }, + reference, ) else: + reference = { + "reference_doctype": "Payment Entry", + "reference_name": source.name, + "amount": source.paid_amount, + "party_type": source.party_type, + "party": source.party, + "mode_of_payment": "Wire Transfer", + "bank_account": source.party_bank_account + or get_party_bank_account( + source.get("party_type"), source.get("party") + ), + "account": source.paid_to, + "cost_center": source.cost_center, + "project": source.project, + "payment_entry": source.name, + } + reference.update(_update_dimensions(source)) + target.append( "references", - { - "reference_doctype": "Payment Entry", - "reference_name": source.name, - "amount": source.paid_amount, - "party_type": source.party_type, - "party": source.party, - "mode_of_payment": "Wire Transfer", - "bank_account": source.party_bank_account - or get_party_bank_account( - source.get("party_type"), source.get("party") - ), - "account": source.paid_to, - "cost_center": source.cost_center, - "project": source.project, - "payment_entry": source.name, - }, + reference, ) target.status = "Pending" diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index 1bddfec..741ca44 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -149,41 +149,6 @@ def update_payemnt_status(self, action=None): order_status, ) - def make_payroll_bank_entry(self, submit=False): - self.docstatus = 0 - payroll_entry = ( - set([ref.payroll_entry for ref in self.references if ref.payroll_entry]) - if self.references - else [] - ) - if payroll_entry: - for pe in payroll_entry: - payroll_entry = frappe.get_doc("Payroll Entry", pe) - if not payroll_entry.payment_account: - link = frappe.utils.get_link_to_form("Payroll Entry", pe) - frappe.throw( - f"Payment Account is mandatory for Payroll Entry {link}" - ) - - journal_entry = get_bank_entry_for_payroll({"refrence_name": pe}) - if not journal_entry: - journal = payroll_entry.make_bank_entry(for_withheld_salaries=False) - else: - journal = frappe.get_doc("Journal Entry", journal_entry) - - frappe.db.set_value( - "Journal Entry", - journal.name, - { - "payment_order": self.name, - "cheque_no": self.name, - "cheque_date": getdate(), - }, - ) - journal.reload() - if submit and not journal.docstatus: - journal.submit() - def on_update_after_submit(self): frappe.throw("You cannot modify a payment order") return diff --git a/india_banking/overrides/payment_request.py b/india_banking/overrides/payment_request.py index 7975adb..4d4fcb8 100644 --- a/india_banking/overrides/payment_request.py +++ b/india_banking/overrides/payment_request.py @@ -142,26 +142,32 @@ def set_missing_values(source, target): source.reference_doctype, source.reference_name, "credit_to" ) - for dimension in get_accounting_dimensions(): - target.update({dimension: source.get(dimension, "")}) + def _update_dimensions(source): + return { + dimension: source.get(dimension, "") + for dimension in get_accounting_dimensions() + } + + reference = { + "reference_doctype": source.reference_doctype, + "reference_name": source.reference_name, + "amount": source.grand_total, + "party_type": source.party_type, + "party": source.party, + "payment_request": source_name, + "mode_of_payment": source.mode_of_payment, + "bank_account": source.bank_account, + "account": account, + "is_adhoc": source.is_adhoc, + "cost_center": source.cost_center, + "project": source.project, + "tax_withholding_category": source.tax_withholding_category, + } + reference.update(_update_dimensions(source)) target.append( "references", - { - "reference_doctype": source.reference_doctype, - "reference_name": source.reference_name, - "amount": source.grand_total, - "party_type": source.party_type, - "party": source.party, - "payment_request": source_name, - "mode_of_payment": source.mode_of_payment, - "bank_account": source.bank_account, - "account": account, - "is_adhoc": source.is_adhoc, - "cost_center": source.cost_center, - "project": source.project, - "tax_withholding_category": source.tax_withholding_category, - }, + reference, ) target.status = "Pending" From a8cd08a95419941604d6bf9c7bf3735d3c18d973 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 27 Dec 2024 10:40:21 +0530 Subject: [PATCH 34/83] fix: change variable name to python snake case fix: add diabled filter in default mode of payment field --- india_banking/public/js/payment_order.js | 44 ++++++++++++++---------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/india_banking/public/js/payment_order.js b/india_banking/public/js/payment_order.js index 7b3792c..c4eca7d 100644 --- a/india_banking/public/js/payment_order.js +++ b/india_banking/public/js/payment_order.js @@ -34,11 +34,18 @@ frappe.ui.form.on("Payment Order", { }, }; }); + frm.set_query("default_mode_of_transfer", () => { + return { + filters: { + disabled: 0, + }, + }; + }); // Set properties for the summary table - const summaryField = "summary"; - frm.set_df_property(summaryField, "cannot_delete_rows", true); - frm.set_df_property(summaryField, "cannot_add_rows", true); + const summary_field = "summary"; + frm.set_df_property(summary_field, "cannot_delete_rows", true); + frm.set_df_property(summary_field, "cannot_add_rows", true); }, refresh(frm) { @@ -57,7 +64,7 @@ frappe.ui.form.on("Payment Order", { frm.trigger("remove_row_if_empty"); // Collect existing payment requests from references table, if any - const existingPaymentRequests = (frm.doc.references || []).map( + const existing_payment_requests = (frm.doc.references || []).map( (reference) => reference.payment_request ); @@ -75,7 +82,7 @@ frappe.ui.form.on("Payment Order", { docstatus: 1, status: ["=", "Initiated"], bank: frm.doc.bank, - name: ["not in", existingPaymentRequests], + name: ["not in", existing_payment_requests], company: frm.doc.company, }, }); @@ -86,7 +93,7 @@ frappe.ui.form.on("Payment Order", { frm.trigger("remove_row_if_empty"); // Collect existing payment entries from the references table, if any - const existingPaymentEntries = (frm.doc.references || []).map( + const existing_payment_entries = (frm.doc.references || []).map( (reference) => reference.payment_entry ); @@ -101,8 +108,9 @@ frappe.ui.form.on("Payment Order", { }, get_query_filters: { docstatus: 1, - name: ["not in", existingPaymentEntries], + name: ["not in", existing_payment_entries], source_doctype: ["!=", "Payment Request"], + payment_type: "Pay", }, }); }, @@ -136,7 +144,7 @@ frappe.ui.form.on("Payment Order", { ], get_query: function () { // Extract unique reference names from the references table - const uniqueAccounts = [ + const unique_accounts = [ ...new Set( (frm.doc.references || []).map( (reference) => reference.reference_name @@ -147,7 +155,7 @@ frappe.ui.form.on("Payment Order", { return { query: "india_banking.overrides.journal_entry.get_bank_entry", filters: { - docs: uniqueAccounts, + docs: unique_accounts, }, }; }, @@ -157,7 +165,7 @@ frappe.ui.form.on("Payment Order", { set_get_payments_from_buttons(frm) { if (frm.doc.docstatus === 0) { // Define an array of payment sources and their respective triggers - const paymentSources = [ + const payment_sources = [ { label: __("Payment Request"), trigger: "get_payments_from_payment_request", @@ -173,7 +181,7 @@ frappe.ui.form.on("Payment Order", { ]; // Add custom buttons for each payment source - paymentSources.forEach((source) => { + payment_sources.forEach((source) => { frm.add_custom_button( source.label, () => frm.trigger(source.trigger), @@ -191,11 +199,11 @@ frappe.ui.form.on("Payment Order", { frm.has_perm("write") ) { // Check if any summary item has a payment status of "Pending" - const hasPendingPayments = frm.doc.summary.some( + const has_pending_payments = frm.doc.summary.some( (item) => item.payment_status === "Pending" ); - if (hasPendingPayments) { + if (has_pending_payments) { // Add a custom button to initiate payment frm.add_custom_button(__("Initiate Payment"), () => { frm.trigger("make_payment"); @@ -209,11 +217,11 @@ frappe.ui.form.on("Payment Order", { frm.has_perm("write") ) { // Check if any summary item has a payment status of "Initiated" - const hasInitiatedStatus = frm.doc.summary.some( + const has_initiated_status = frm.doc.summary.some( (item) => item.payment_status === "Initiated" ); - if (hasInitiatedStatus) { + if (has_initiated_status) { frm.add_custom_button(__("Get Status"), () => { frappe.call({ method: @@ -305,17 +313,17 @@ frappe.ui.form.on("Payment Order", { frm.doc.docstatus != 0 ) { // Define the mapping of payment_order_type to buttons - const buttonMapping = { + const button_mapping = { "Payment Request": ["Bank Entry(JV)", "Payment Entry"], "Payment Entry": ["Bank Entry(JV)", "Payment Request"], "Journal Entry": ["Payment Request", "Payment Entry"], }; // Get the relevant buttons based on the payment_order_type - const buttonsToRemove = buttonMapping[frm.doc.payment_order_type] || []; + const buttons_to_remove = button_mapping[frm.doc.payment_order_type] || []; // Iterate over the buttons and remove them - buttonsToRemove.forEach((button) => { + buttons_to_remove.forEach((button) => { frm.remove_custom_button(button, "Get Payments from"); }); } From e28e4c3f16888c53fbe38e1b615eb78510813a5b Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 27 Dec 2024 11:38:04 +0530 Subject: [PATCH 35/83] refactor: add summarise based on field fix: remove is party wise field --- india_banking/install.py | 10 +++++----- india_banking/uninstall.py | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/india_banking/install.py b/india_banking/install.py index 29f9ddd..bfe7def 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -265,10 +265,10 @@ def create_payment_order_custom_fields(): "insert_after": "get_summary", }, { - "label": "Is Party Wise", - "fieldname": "is_party_wise", - "fieldtype": "Check", - "hidden": 0, + "label": "Summarise Payment Based On", + "fieldname": "summarise_payment_based_on", + "fieldtype": "Select", + "options": "Party\nVoucher", "no_copy": 1, "insert_after": "payment_summary", }, @@ -284,7 +284,7 @@ def create_payment_order_custom_fields(): "fieldname": "summary", "fieldtype": "Table", "options": "Payment Order Summary", - "insert_after": "is_party_wise", + "insert_after": "summarise_payment_based_on", "no_copy": 1, }, { diff --git a/india_banking/uninstall.py b/india_banking/uninstall.py index 2b69dd3..0597a01 100644 --- a/india_banking/uninstall.py +++ b/india_banking/uninstall.py @@ -56,7 +56,6 @@ def delete_custom_fields(): "get_summary", "payment_summary", "default_mode_of_transfer", - "is_party_wise", "summary", "total", "status", From 83a69199c1479168fed8ed0691ca863802e4dad2 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 27 Dec 2024 11:43:47 +0530 Subject: [PATCH 36/83] refactor: modify all is party-wise condition to summarise based on condition --- india_banking/india_banking/doc_events/payment_order.py | 2 +- india_banking/overrides/payment_order.py | 4 ++-- india_banking/public/js/payment_order.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/india_banking/india_banking/doc_events/payment_order.py b/india_banking/india_banking/doc_events/payment_order.py index d076176..421527b 100644 --- a/india_banking/india_banking/doc_events/payment_order.py +++ b/india_banking/india_banking/doc_events/payment_order.py @@ -584,7 +584,7 @@ def make_payment_entries(docname): == row.tax_withholding_category and reference.reference_doctype == row.reference_doctype ) - if not payment_order_doc.is_party_wise: + if payment_order_doc.summarise_payment_based_on != "Party": filter_condition = filter_condition and ( reference.reference_doctype == row.reference_doctype and reference.reference_name == row.reference_name diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index 741ca44..a7b7561 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -196,7 +196,7 @@ def update_payment_status(self, cancel=False): @frappe.whitelist() -def get_party_summary(references, company_bank_account, is_party_wise=None): +def get_party_summary(references, company_bank_account, summarise_payment_based_on=None): references = json.loads(references) if not len(references) or not company_bank_account: return @@ -218,7 +218,7 @@ def _get_unique_key(reference=None, summarise_field_only=False): "journal_entry", "journal_entry_account", ] - if is_party_wise: + if summarise_payment_based_on == "Party": summarise_field.remove("reference_name") if summarise_field_only: diff --git a/india_banking/public/js/payment_order.js b/india_banking/public/js/payment_order.js index c4eca7d..0ec89d0 100644 --- a/india_banking/public/js/payment_order.js +++ b/india_banking/public/js/payment_order.js @@ -5,7 +5,7 @@ frappe.ui.form.on("Payment Order", { .get_single_value("India Banking Settings", "summarise_payment_based_on") .then((res) => { if (res === "Party") { - frm.set_value("is_party_wise", 1); + frm.set_value("summarise_payment_based_on", res); } }); @@ -343,7 +343,7 @@ frappe.ui.form.on("Payment Order", { args: { references: frm.doc.references, company_bank_account: frm.doc.company_bank_account, - is_party_wise: frm.doc.is_party_wise, + summarise_payment_based_on: frm.doc.summarise_payment_based_on, }, freeze: true, callback: function (r) { From c360f36a67c9b5aabcd10faebf1d46eff114abbd Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 27 Dec 2024 11:59:02 +0530 Subject: [PATCH 37/83] refactor: pretify --- india_banking/public/js/payment_order.js | 66 ++++++++++++------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/india_banking/public/js/payment_order.js b/india_banking/public/js/payment_order.js index 0ec89d0..bc20430 100644 --- a/india_banking/public/js/payment_order.js +++ b/india_banking/public/js/payment_order.js @@ -53,12 +53,40 @@ frappe.ui.form.on("Payment Order", { frm.remove_custom_button("Payment Request", "Get Payments from"); frm.trigger("set_get_payments_from_buttons"); - frm.trigger("set_payment_and_status_buttons"); frm.trigger("remove_button"); }, + set_get_payments_from_buttons(frm) { + if (frm.doc.docstatus === 0) { + // Define an array of payment sources and their respective triggers + const payment_sources = [ + { + label: __("Payment Request"), + trigger: "get_payments_from_payment_request", + }, + { + label: __("Payment Entry"), + trigger: "get_payments_from_payment_entry", + }, + { + label: __("Bank Entry(JV)"), + trigger: "get_payments_from_journal_entry", + }, + ]; + + // Add custom buttons for each payment source + payment_sources.forEach((source) => { + frm.add_custom_button( + source.label, + () => frm.trigger(source.trigger), + __("Get Payments from") + ); + }); + } + }, + get_payments_from_payment_request(frm) { // Ensure references table is clean before processing frm.trigger("remove_row_if_empty"); @@ -144,7 +172,7 @@ frappe.ui.form.on("Payment Order", { ], get_query: function () { // Extract unique reference names from the references table - const unique_accounts = [ + const existing_journal_entries = [ ...new Set( (frm.doc.references || []).map( (reference) => reference.reference_name @@ -155,42 +183,13 @@ frappe.ui.form.on("Payment Order", { return { query: "india_banking.overrides.journal_entry.get_bank_entry", filters: { - docs: unique_accounts, + docs: existing_journal_entries, }, }; }, }); }, - set_get_payments_from_buttons(frm) { - if (frm.doc.docstatus === 0) { - // Define an array of payment sources and their respective triggers - const payment_sources = [ - { - label: __("Payment Request"), - trigger: "get_payments_from_payment_request", - }, - { - label: __("Payment Entry"), - trigger: "get_payments_from_payment_entry", - }, - { - label: __("Bank Entry(JV)"), - trigger: "get_payments_from_journal_entry", - }, - ]; - - // Add custom buttons for each payment source - payment_sources.forEach((source) => { - frm.add_custom_button( - source.label, - () => frm.trigger(source.trigger), - __("Get Payments from") - ); - }); - } - }, - set_payment_and_status_buttons(frm) { // Check if the document is in a pending state and user has write permissions if ( @@ -320,7 +319,8 @@ frappe.ui.form.on("Payment Order", { }; // Get the relevant buttons based on the payment_order_type - const buttons_to_remove = button_mapping[frm.doc.payment_order_type] || []; + const buttons_to_remove = + button_mapping[frm.doc.payment_order_type] || []; // Iterate over the buttons and remove them buttons_to_remove.forEach((button) => { From 6e8a23c5ade3876845fd4ca558a892b2451b61a7 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 27 Dec 2024 12:09:21 +0530 Subject: [PATCH 38/83] feat: update dimension while summarising the payment order --- india_banking/overrides/payment_order.py | 10 ++++++++-- india_banking/overrides/payment_request.py | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index a7b7561..d6d60fe 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -2,8 +2,11 @@ import re import frappe +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_accounting_dimensions, +) from erpnext.accounts.doctype.payment_order.payment_order import PaymentOrder -from frappe.utils import get_link_to_form, getdate +from frappe.utils import get_link_to_form from india_banking.india_banking.doc_events.payment_order import make_payment_entries @@ -196,7 +199,9 @@ def update_payment_status(self, cancel=False): @frappe.whitelist() -def get_party_summary(references, company_bank_account, summarise_payment_based_on=None): +def get_party_summary( + references, company_bank_account, summarise_payment_based_on=None +): references = json.loads(references) if not len(references) or not company_bank_account: return @@ -218,6 +223,7 @@ def _get_unique_key(reference=None, summarise_field_only=False): "journal_entry", "journal_entry_account", ] + summarise_field.extend(get_accounting_dimensions()) if summarise_payment_based_on == "Party": summarise_field.remove("reference_name") diff --git a/india_banking/overrides/payment_request.py b/india_banking/overrides/payment_request.py index 4d4fcb8..cc3f3e5 100644 --- a/india_banking/overrides/payment_request.py +++ b/india_banking/overrides/payment_request.py @@ -127,7 +127,7 @@ def valdidate_bank_for_wire_transfer(self): @frappe.whitelist() -def make_payment_order(source_name, target_doc=None, args=None): +def make_payment_order(source_name, target_doc=None): from frappe.model.mapper import get_mapped_doc def set_missing_values(source, target): From 62b6087f3b0aa343637c31e6e09452f3ee5c8848 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 27 Dec 2024 12:49:16 +0530 Subject: [PATCH 39/83] refactor: add default bankaccount validation fix: add company bank account filter --- india_banking/install.py | 16 +++++------ india_banking/overrides/payment_entry.py | 36 +++++++++++++++--------- india_banking/public/js/payment_order.js | 2 ++ 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/india_banking/install.py b/india_banking/install.py index bfe7def..9c1ed40 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -252,17 +252,11 @@ def create_payment_order_custom_fields(): "fieldtype": "Data", "insert_after": "file_sequence_number", }, - { - "label": "Get Summary", - "fieldname": "get_summary", - "fieldtype": "Button", - "insert_after": "references", - }, { "label": "Payment Summary", "fieldname": "payment_summary", "fieldtype": "Section Break", - "insert_after": "get_summary", + "insert_after": "references", }, { "label": "Summarise Payment Based On", @@ -272,6 +266,12 @@ def create_payment_order_custom_fields(): "no_copy": 1, "insert_after": "payment_summary", }, + { + "label": "Get Summary", + "fieldname": "get_summary", + "fieldtype": "Button", + "insert_after": "summarise_payment_based_on", + }, { "label": "Default Mode of Transfer", "fieldname": "default_mode_of_transfer", @@ -284,7 +284,7 @@ def create_payment_order_custom_fields(): "fieldname": "summary", "fieldtype": "Table", "options": "Payment Order Summary", - "insert_after": "summarise_payment_based_on", + "insert_after": "default_mode_of_transfer", "no_copy": 1, }, { diff --git a/india_banking/overrides/payment_entry.py b/india_banking/overrides/payment_entry.py index f966c1b..29a8569 100644 --- a/india_banking/overrides/payment_entry.py +++ b/india_banking/overrides/payment_entry.py @@ -4,6 +4,7 @@ ) from erpnext.accounts.doctype.payment_entry.payment_entry import PaymentEntry from erpnext.accounts.party import get_party_bank_account +from frappe import _ class CustomPaymentEntry(PaymentEntry): @@ -16,7 +17,7 @@ def validate_duplicate_entry(self): @frappe.whitelist() -def make_payment_order(source_name, target_doc=None, args=None): +def make_payment_order(source_name, target_doc=None): from frappe.model.mapper import get_mapped_doc def set_missing_values(source, target): @@ -34,19 +35,27 @@ def _update_dimensions(source): for dimension in get_accounting_dimensions() } - if source.references: + def _get_default_bank_account(party_type, party): + party_bank_account = get_party_bank_account(party_type, party) + if not party_bank_account: + frappe.throw( + _("Default Bank Account is missing for {0} - {1}").format( + party_type, party + ) + ) + return party_bank_account + + for count, reference in enumerate(source.references, 1): reference = { - "reference_doctype": source.references[0].reference_doctype, - "reference_name": source.references[0].reference_name, - "amount": source.references[0].total_amount, + "reference_doctype": reference.reference_doctype, + "reference_name": reference.reference_name, + "amount": reference.allocated_amount, "party_type": source.party_type, "party": source.party, "mode_of_payment": source.mode_of_payment, - "bank_account": get_party_bank_account( - source.get("party_type"), source.get("party") - ) - if source.get("party_type") - else "", + "bank_account": _get_default_bank_account( + source.party_type, source.party + ), "account": account, "cost_center": source.cost_center, "project": source.project, @@ -58,6 +67,8 @@ def _update_dimensions(source): "references", reference, ) + if count == len(source.references): + break else: reference = { "reference_doctype": "Payment Entry", @@ -66,9 +77,8 @@ def _update_dimensions(source): "party_type": source.party_type, "party": source.party, "mode_of_payment": "Wire Transfer", - "bank_account": source.party_bank_account - or get_party_bank_account( - source.get("party_type"), source.get("party") + "bank_account": _get_default_bank_account( + source.party_type, source.party ), "account": source.paid_to, "cost_center": source.cost_center, diff --git a/india_banking/public/js/payment_order.js b/india_banking/public/js/payment_order.js index bc20430..4d28984 100644 --- a/india_banking/public/js/payment_order.js +++ b/india_banking/public/js/payment_order.js @@ -139,6 +139,8 @@ frappe.ui.form.on("Payment Order", { name: ["not in", existing_payment_entries], source_doctype: ["!=", "Payment Request"], payment_type: "Pay", + mode_of_payment: "Wire Transfer", + bank_account: frm.doc.company_bank_account, }, }); }, From 818b1b07b1030376c2a5113f71af445d7072034c Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 27 Dec 2024 13:08:33 +0530 Subject: [PATCH 40/83] refactor: modify SQL query using query builder --- india_banking/overrides/journal_entry.py | 64 +++++++++++++----------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/india_banking/overrides/journal_entry.py b/india_banking/overrides/journal_entry.py index 052da60..d3a0bd7 100644 --- a/india_banking/overrides/journal_entry.py +++ b/india_banking/overrides/journal_entry.py @@ -3,8 +3,7 @@ get_accounting_dimensions, ) from frappe.query_builder import DocType -from frappe.utils import comma_sep - +from frappe.query_builder.functions import Sum @frappe.whitelist() def make_payment_order(source_name, target_doc=None, args=None): @@ -90,34 +89,39 @@ def update_bank_entry(source, target): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_bank_entry(doctype, txt, searchfield, start, page_len, filters, as_dict): - search_condition = "" - if filters and filters.get("docs"): - filters.get("docs").append("") - exist_account = str(tuple(filters.get("docs"))) - search_condition += f" AND je.name NOT IN {exist_account} " - - if filters and filters.get("company"): - search_condition += f"AND je.company = '{filters.get('company')}'" - if txt: - search_condition += f"AND je.name LIKE '%{txt}%'" - - bank_entries = frappe.db.sql( - f""" - SELECT - DISTINCT je.name, je.company, sum(jea.debit) as total, je.voucher_type - FROM - `tabJournal Entry`je - JOIN - `tabJournal Entry Account`jea - ON - je.name = jea.parent - WHERE - je.docstatus = 1 AND jea.payment_status NOT IN ('Paid', 'Ordered') AND - je.voucher_type = 'Bank Entry' AND jea.party_type= "Employee" {search_condition} - GROUP BY - je.name, je.company, je.voucher_type - """, - as_dict=1, + filters = frappe._dict(filters) + JournalEntry = DocType("Journal Entry") + JournalEntryAccount = DocType("Journal Entry Account") + + query = ( + frappe.qb.from_(JournalEntry) + .join(JournalEntryAccount) + .on(JournalEntry.name == JournalEntryAccount.parent) + .where( + (JournalEntry.docstatus == 1) + & (JournalEntryAccount.payment_status.notin(["Paid", "Ordered"])) + & (JournalEntry.voucher_type == "Bank Entry") + & (JournalEntryAccount.party_type == "Employee") + ) + .groupby(JournalEntry.name, JournalEntry.company, JournalEntry.voucher_type) + .select( + JournalEntry.name, + JournalEntry.company, + Sum(JournalEntryAccount.debit).as_("total"), + JournalEntry.voucher_type, + ) ) + if filters: + if filters.docs: + existing_entries = tuple(filters.docs or []) + query = query.where(JournalEntry.name.notin(existing_entries)) + if filters.company: + query = query.where(JournalEntry.company == filters.company) + + if txt: + query = query.where(JournalEntry.name.like(f"%{txt}%")) + + bank_entries = query.run(as_dict=as_dict) + return bank_entries From 94af04df5e2fa0d8d5e6fe98b41d21b3d682eba0 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 27 Dec 2024 14:09:50 +0530 Subject: [PATCH 41/83] fix: use bank account filter to fetch bank entry refactor: update accounting dimention --- india_banking/overrides/journal_entry.py | 14 +++++++++++++- india_banking/public/js/payment_order.js | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/india_banking/overrides/journal_entry.py b/india_banking/overrides/journal_entry.py index d3a0bd7..5c95e03 100644 --- a/india_banking/overrides/journal_entry.py +++ b/india_banking/overrides/journal_entry.py @@ -5,6 +5,7 @@ from frappe.query_builder import DocType from frappe.query_builder.functions import Sum + @frappe.whitelist() def make_payment_order(source_name, target_doc=None, args=None): from frappe.model.mapper import get_mapped_doc @@ -50,12 +51,18 @@ def update_bank_entry(source, target): ) ) - journal_accounts = query.run(as_dict=True) + journal_accounts = query.run(as_dict=True, debug=1) target.payment_order_type = "Journal Entry" target.docstaus = 0 target.status = "Pending" + def _update_dimensions(source): + return { + dimension: source.get(dimension, "") + for dimension in get_accounting_dimensions() + } + for journal_account in journal_accounts: journal_account = frappe._dict(journal_account) details = { @@ -68,7 +75,11 @@ def update_bank_entry(source, target): "mode_of_payment": "", "bank_account": journal_account.party_bank_account, "account": journal_account.account, + "project": journal_account.project, + "cost_center": journal_account.cost_center, } + details.update(_update_dimensions(journal_account)) + target.append("references", details) doclist = get_mapped_doc( @@ -102,6 +113,7 @@ def get_bank_entry(doctype, txt, searchfield, start, page_len, filters, as_dict) & (JournalEntryAccount.payment_status.notin(["Paid", "Ordered"])) & (JournalEntry.voucher_type == "Bank Entry") & (JournalEntryAccount.party_type == "Employee") + & (JournalEntryAccount.against_account == filters.company_account) ) .groupby(JournalEntry.name, JournalEntry.company, JournalEntry.voucher_type) .select( diff --git a/india_banking/public/js/payment_order.js b/india_banking/public/js/payment_order.js index 4d28984..b924143 100644 --- a/india_banking/public/js/payment_order.js +++ b/india_banking/public/js/payment_order.js @@ -181,11 +181,11 @@ frappe.ui.form.on("Payment Order", { ) ), ]; - return { query: "india_banking.overrides.journal_entry.get_bank_entry", filters: { docs: existing_journal_entries, + company_account: frm.doc.account, }, }; }, From 7a4f927a709aebbe53a5a83b30fca84d6489c0ab Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 27 Dec 2024 15:25:02 +0530 Subject: [PATCH 42/83] refactor: recreate Bank Payment Request with disabled create options --- .../doctype/bank_payment_request/__init__.py | 0 .../bank_payment_request.js | 107 ++++ .../bank_payment_request.json | 460 ++++++++++++++++++ .../bank_payment_request.py | 424 ++++++++++++++++ .../bank_payment_request_list.js | 21 + .../test_bank_payment_request.py | 9 + 6 files changed, 1021 insertions(+) create mode 100644 india_banking/india_banking/doctype/bank_payment_request/__init__.py create mode 100644 india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js create mode 100644 india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.json create mode 100644 india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.py create mode 100644 india_banking/india_banking/doctype/bank_payment_request/bank_payment_request_list.js create mode 100644 india_banking/india_banking/doctype/bank_payment_request/test_bank_payment_request.py diff --git a/india_banking/india_banking/doctype/bank_payment_request/__init__.py b/india_banking/india_banking/doctype/bank_payment_request/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js new file mode 100644 index 0000000..d3a77f8 --- /dev/null +++ b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js @@ -0,0 +1,107 @@ +// Copyright (c) 2024, Aerele Technologies Private Limited and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Bank Payment Request', { + setup: function (frm) { + frm.set_query("party_type", function () { + return { + filters: { + "name": ["in", ["Supplier", "Employee"]] + } + }; + }); + }, + refresh(frm) { + frm.set_query("payment_type", function() { + return { + filters: { + "company": frm.doc.company + } + }; + }); + + frm.set_query("cost_center", function() { + return { + filters: { + "is_group": 0, + "disabled": 0 + } + }; + }); + + frm.set_query("mode_of_payment", function() { + return { + filters: { + "name": "Wire Transfer" + } + }; + }); + + setTimeout(() => { + frm.trigger('toggle_custom_button') + }, 500); + }, + toggle_custom_button(frm){ + if(frm.doc.status == "Initiated") { + frm.remove_custom_button(__('Create Payment Entry')) + } + }, + + company (frm) { + frm.set_query("payment_type", function() { + return { + filters: { + "company": frm.doc.company + } + }; + }); + }, + mode_of_payment (frm) { + var conditions = get_bank_query_conditions(frm); + if (frm.doc.mode_of_payment == "Wire Transfer") { + frm.set_query("bank_account", function() { + return { + filters: conditions + }; + }); + } + }, + party_type (frm) { + var conditions = get_bank_query_conditions(frm); + if (frm.doc.mode_of_payment == "Wire Transfer") { + frm.set_query("bank_account", function() { + return { + filters: conditions + }; + }); + } + }, + party (frm) { + var conditions = get_bank_query_conditions(frm); + if (frm.doc.mode_of_payment == "Wire Transfer") { + frm.set_query("bank_account", function() { + return { + filters: conditions + }; + }); + } + } +}); + +const get_bank_query_conditions = function(frm) { + var conditions = {} + if (frm.doc.party_type) { + conditions["party_type"] = frm.doc.party_type; + } + if (frm.doc.party) { + conditions["party"] = frm.doc.party; + } + if (frm.doc.mode_of_payment == "Wire Transfer") { + frm.set_query("bank_account", function() { + return { + filters: conditions + }; + }); + } + return conditions; +}; \ No newline at end of file diff --git a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.json b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.json new file mode 100644 index 0000000..8e52631 --- /dev/null +++ b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.json @@ -0,0 +1,460 @@ +{ + "actions": [], + "autoname": "naming_series:", + "creation": "2024-04-30 10:42:52.086041", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "payment_request_type", + "transaction_date", + "mode_of_payment", + "column_break_2", + "naming_series", + "company", + "payment_type", + "is_adhoc", + "party_details", + "party_type", + "reference_doctype", + "column_break_4", + "party", + "reference_name", + "transaction_details", + "net_total", + "taxes_deducted", + "grand_total", + "column_break_18", + "currency", + "apply_tax_withholding_amount", + "tax_withholding_category", + "bank_account_details", + "bank_account", + "bank", + "bank_account_no", + "account", + "column_break_11", + "iban", + "branch_code", + "swift_number", + "accounting_dimensions_section", + "cost_center", + "dimension_col_break", + "project", + "recipient_and_message", + "print_format", + "email_to", + "subject", + "column_break_9", + "payment_gateway_account", + "status", + "make_sales_invoice", + "section_break_10", + "payment_url", + "section_break_7", + "payment_account", + "payment_order", + "amended_from", + "column_break_uumn", + "payment_gateway", + "payment_channel", + "payment_term" + ], + "fields": [ + { + "default": "Outward", + "fieldname": "payment_request_type", + "fieldtype": "Select", + "label": "Payment Request Type", + "options": "Outward", + "reqd": 1 + }, + { + "default": "Today", + "fieldname": "transaction_date", + "fieldtype": "Date", + "label": "Transaction Date", + "mandatory_depends_on": "eval:doc.mode_of_payment == 'Wire Transfer'" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Series", + "no_copy": 1, + "options": "ACC-BPRQ-.YYYY.-", + "print_hide": 1, + "reqd": 1 + }, + { + "default": "Wire Transfer", + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "label": "Mode of Payment", + "options": "Mode of Payment" + }, + { + "fieldname": "party_details", + "fieldtype": "Section Break", + "label": "Party Details" + }, + { + "fieldname": "party_type", + "fieldtype": "Link", + "label": "Party Type", + "options": "DocType" + }, + { + "fieldname": "party", + "fieldtype": "Dynamic Link", + "label": "Party", + "options": "party_type" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "reference_doctype", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Reference Doctype", + "no_copy": 1, + "options": "DocType", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Reference Name", + "no_copy": 1, + "options": "reference_doctype", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "transaction_details", + "fieldtype": "Section Break", + "label": "Transaction Details" + }, + { + "description": "Amount in customer's currency", + "fieldname": "grand_total", + "fieldtype": "Currency", + "label": "Amount", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "fieldname": "currency", + "fieldtype": "Link", + "label": "Transaction Currency", + "options": "Currency", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "bank_account_details", + "fieldtype": "Section Break", + "label": "Bank Account Details" + }, + { + "fieldname": "bank_account", + "fieldtype": "Link", + "label": "Bank Account", + "mandatory_depends_on": "eval:doc.mode_of_payment == 'Wire Transfer'", + "options": "Bank Account" + }, + { + "fetch_from": "bank_account.bank", + "fieldname": "bank", + "fieldtype": "Link", + "label": "Bank", + "options": "Bank", + "read_only": 1 + }, + { + "fetch_from": "bank_account.bank_account_no", + "fieldname": "bank_account_no", + "fieldtype": "Read Only", + "label": "Bank Account No" + }, + { + "fetch_from": "bank_account.account", + "fieldname": "account", + "fieldtype": "Read Only", + "label": "Account", + "read_only": 1 + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fetch_from": "bank_account.iban", + "fieldname": "iban", + "fieldtype": "Read Only", + "label": "IBAN" + }, + { + "fetch_from": "bank_account.branch_code", + "fetch_if_empty": 1, + "fieldname": "branch_code", + "fieldtype": "Read Only", + "label": "Branch Code" + }, + { + "fetch_from": "bank.swift_number", + "fieldname": "swift_number", + "fieldtype": "Read Only", + "label": "SWIFT Number" + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" + }, + { + "depends_on": "eval: doc.payment_request_type == 'Inward'", + "fieldname": "recipient_and_message", + "fieldtype": "Section Break", + "label": "Recipient Message And Payment Details" + }, + { + "depends_on": "eval: doc.payment_channel != 'Phone'", + "fieldname": "print_format", + "fieldtype": "Select", + "label": "Print Format" + }, + { + "fieldname": "email_to", + "fieldtype": "Data", + "in_global_search": 1, + "label": "To" + }, + { + "depends_on": "eval: doc.payment_channel != 'Phone'", + "fieldname": "subject", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Subject" + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval: doc.payment_request_type == 'Inward'", + "fieldname": "payment_gateway_account", + "fieldtype": "Link", + "label": "Payment Gateway Account", + "options": "Payment Gateway Account" + }, + { + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "hidden": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "\nDraft\nRequested\nInitiated\nPartially Paid\nPayment Ordered\nPaid\nFailed\nCancelled", + "read_only": 1 + }, + { + "default": "0", + "depends_on": "eval:doc.reference_doctype== 'Sales Order'", + "fieldname": "make_sales_invoice", + "fieldtype": "Check", + "hidden": 1, + "label": "Make Sales Invoice", + "read_only": 1 + }, + { + "depends_on": "eval: doc.payment_request_type == 'Inward' || doc.payment_channel != 'Phone'", + "fieldname": "section_break_10", + "fieldtype": "Section Break" + }, + { + "fieldname": "payment_url", + "fieldtype": "Data", + "hidden": 1, + "length": 500, + "options": "URL", + "read_only": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval: doc.payment_gateway_account", + "depends_on": "eval: doc.payment_request_type == 'Inward'", + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "label": "Payment Gateway Details" + }, + { + "fetch_from": "payment_gateway_account.payment_gateway", + "fieldname": "payment_gateway", + "fieldtype": "Read Only", + "label": "Payment Gateway" + }, + { + "fetch_from": "payment_gateway_account.payment_account", + "fieldname": "payment_account", + "fieldtype": "Read Only", + "label": "Payment Account", + "read_only": 1 + }, + { + "fetch_from": "payment_gateway_account.payment_channel", + "fieldname": "payment_channel", + "fieldtype": "Select", + "label": "Payment Channel", + "options": "\nEmail\nPhone", + "read_only": 1 + }, + { + "fieldname": "payment_order", + "fieldtype": "Link", + "label": "Payment Order", + "options": "Payment Order", + "read_only": 1 + }, + { + "fieldname": "payment_type", + "fieldtype": "Link", + "label": "Payment Type", + "mandatory_depends_on": "eval:doc.mode_of_payment == 'Wire Transfer' && doc.reference_doctype != 'Purchase Invoice' && doc.payment_request_type == 'Outward'", + "options": "Payment Type" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Bank Payment Request", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "depends_on": "eval:doc.tax_withholding_category", + "fieldname": "taxes_deducted", + "fieldtype": "Currency", + "label": "Taxes Deducted" + }, + { + "default": "0", + "depends_on": "eval:doc.party_type == 'Supplier' && doc.reference_doctype != 'Purchase Invoice' && doc.payment_request_type == 'Outward'", + "fieldname": "apply_tax_withholding_amount", + "fieldtype": "Check", + "label": "Apply Tax Withholding Amount" + }, + { + "depends_on": "eval:doc.apply_tax_withholding_amount", + "fieldname": "tax_withholding_category", + "fieldtype": "Link", + "label": "Tax Withholding Category", + "options": "Tax Withholding Category" + }, + { + "fieldname": "net_total", + "fieldtype": "Currency", + "label": "Net Total" + }, + { + "default": "0", + "depends_on": "eval:doc.mode_of_payment == 'Wire Transfer' && doc.payment_request_type == 'Outward'", + "fieldname": "is_adhoc", + "fieldtype": "Check", + "label": "Is AdHoc" + }, + { + "fieldname": "column_break_uumn", + "fieldtype": "Column Break" + }, + { + "fieldname": "payment_term", + "fieldtype": "Link", + "label": "Payment Term", + "options": "Payment Term", + "read_only": 1 + } + ], + "in_create": 1, + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2024-12-27 15:23:52.373306", + "modified_by": "Administrator", + "module": "India Banking", + "name": "Bank Payment Request", + "naming_rule": "By \"Naming Series\" field", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "read_only": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.py b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.py new file mode 100644 index 0000000..92fa282 --- /dev/null +++ b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.py @@ -0,0 +1,424 @@ +# Copyright (c) 2024, Aerele Technologies Private Limited and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document + +from erpnext.accounts.doctype.payment_request.payment_request import PaymentRequest, get_existing_payment_request_amount +from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details + + +from erpnext.accounts.doctype.payment_request import payment_request as PR + +from erpnext.accounts.party import get_party_bank_account +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_accounting_dimensions, +) +from frappe.utils.data import flt, today, cstr + + +class BankPaymentRequest(PaymentRequest): + def validate_subscription_details(self): + pass + + def validate(self): + frappe.log_error(title="India - banking", message=cstr(self.as_dict())) + if self.apply_tax_withholding_amount and self.tax_withholding_category and self.payment_request_type == "Outward": + if not self.net_total: + self.net_total = self.grand_total + tds_amount = self.calculate_pr_tds(self.net_total) + self.taxes_deducted = tds_amount + self.grand_total = self.net_total - self.taxes_deducted + else: + if self.net_total and not self.grand_total: + self.grand_total = self.net_total + if self.grand_total and self.net_total != self.grand_total and not self.apply_tax_withholding_amount: + self.grand_total = self.net_total + + if not self.is_adhoc: + super().validate() + else: + if self.get("__islocal"): + self.status = "Draft" + if self.reference_doctype or self.reference_name: + frappe.throw("Payments with references cannot be marked as ad-hoc") + + self.valdidate_bank_for_wire_transfer() + + def validate_payment_request_amount(self): + existing_payment_request_amount = flt( + get_existing_payment_request_amount(self.reference_doctype, self.reference_name) + ) + + docname = None + if frappe.flags.update_amount or not self.is_new(): + docname = self.name + + existing_payment_request_amount_drafted = flt( + get_existing_payment_request_amount(self.reference_doctype, self.reference_name, submitted=False, update=docname) + ) + + total_existing_payment_request_amount = existing_payment_request_amount + existing_payment_request_amount_drafted + + + ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) + + if not hasattr(ref_doc, "order_type") or getattr(ref_doc, "order_type") != "Shopping Cart": + if self.reference_doctype in ["Purchase Order"]: + ref_amount = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total) + elif self.reference_doctype in ["Purchase Invoice"]: + if ref_doc.rounded_total: + ref_amount = flt(ref_doc.rounded_total) + else: + ref_amount = flt(ref_doc.grand_total) + else: + ref_amount = get_amount(ref_doc, self.payment_account) + + frappe.log_error("existing_payment_request_amount_drafted", existing_payment_request_amount_drafted ) + frappe.log_error("existing_payment_request_amount", existing_payment_request_amount) + frappe.log_error("ref_amount", ref_amount) + frappe.log_error("self.net_total", self.net_total) + + if total_existing_payment_request_amount + flt(self.net_total) > ref_amount: + frappe.throw( + frappe._("Total Bank Payment Request amount cannot be greater than {0} amount").format( + self.reference_doctype + ) + ) + + def on_submit(self): + debit_account = None + if self.payment_type: + debit_account = frappe.db.get_value("Payment Type", self.payment_type, "account") + elif self.reference_doctype == "Purchase Invoice": + debit_account = frappe.db.get_value(self.reference_doctype, self.reference_name, "credit_to") + + if not debit_account: + frappe.throw("Debit account for Payment Type {} cannot be determined".format(self.payment_type)) + if not self.is_adhoc: + super().on_submit() + else: + if self.payment_request_type == "Outward": + self.db_set("status", "Initiated") + return + + def create_payment_entry(self, submit=True): + payment_entry = super().create_payment_entry(submit=submit) + payment_entry.source_doctype = self.payment_order_type + if payment_entry.docstatus != 1 and self.payment_type: + payment_entry.paid_to = frappe.db.get_value("Payment Type", self.payment_type, "account") or "" + + return payment_entry + + def calculate_pr_tds(self, amount): + doc = self + doc.supplier = self.party + doc.company = self.company + doc.base_tax_withholding_net_total = amount + doc.tax_withholding_net_total = amount + doc.taxes = [] + taxes = get_party_tax_withholding_details(doc, self.tax_withholding_category) + if taxes: + return taxes["tax_amount"] + else: + return 0 + + def valdidate_bank_for_wire_transfer(self): + if self.mode_of_payment == "Wire Transfer" and not self.bank_account: + frappe.throw(frappe._("Bank Account is missing for Wire Transfer Payments")) + + try: + status = frappe.db.get_value("Bank Account", self.bank_account, "workflow_state") + + if self.mode_of_payment == "Wire Transfer" and status != "Approved": + frappe.throw("Cannot proceed with un-approved bank account") + except: + frappe.throw("Workflow Not Found for Bank Account") + + +@frappe.whitelist() +def validate_payment_request_status(**args): + total_bank_payment_request_amount = frappe.db.get_all( + "Bank Payment Request", { + "reference_doctype": args.get('ref_doctype'), + "reference_name": args.get('ref_name'), + "docstatus": 1 + }, + "sum(grand_total) as grand_total") + + if total_bank_payment_request_amount[0] and total_bank_payment_request_amount[0].get('grand_total'): + if flt(total_bank_payment_request_amount[0].get('grand_total')) >= flt(args.get('grand_total')): + return 'Completed' + + return "" + + +@frappe.whitelist(allow_guest=True) +def make_bank_payment_request(**args): + """Make Bank payment request""" + + args = frappe._dict(args) + + ref_doc = frappe.get_doc(args.dt, args.dn) + gateway_account = PR.get_gateway_details(args) or frappe._dict() + + grand_total = get_amount(ref_doc, gateway_account.get("payment_account")) + + bank_account = ( + get_party_bank_account(args.get("party_type"), args.get("party")) if args.get("party_type") else "" + ) + + if not bank_account: + frappe.throw(frappe._("Default Bank Account is missing for {0} - {1}").format(args.get("party_type"), args.get("party"))) + + draft_payment_request = frappe.db.get_value( + "Bank Payment Request", + {"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": 0}, + ) + + existing_payment_request_amount = get_existing_payment_request_amount(args.dt, args.dn) + + if existing_payment_request_amount: + grand_total -= existing_payment_request_amount + + if draft_payment_request: + bpr = frappe.get_doc("Bank Payment Request", draft_payment_request) + bpr.db_set("net_total", grand_total, update_modified=False) + bpr.validate() + frappe.db.set_value( + "Bank Payment Request", draft_payment_request, { + "net_total": bpr.net_total, + "grand_total": bpr.grand_total, + "taxes_deducted": bpr.taxes_deducted + }, + update_modified=False + ) + + else: + bpr = frappe.new_doc("Bank Payment Request") + + if not args.get("payment_request_type"): + args["payment_request_type"] = ( + "Outward" if args.get("dt") in ["Purchase Order", "Purchase Invoice"] else "Inward" + ) + + bpr.payment_type = "Pay" + + bpr.update( + { + "payment_gateway_account": gateway_account.get("name"), + "payment_gateway": gateway_account.get("payment_gateway"), + "payment_account": gateway_account.get("payment_account"), + "payment_channel": gateway_account.get("payment_channel"), + "payment_request_type": args.get("payment_request_type"), + "currency": ref_doc.currency, + "company": ref_doc.company, + "grand_total": grand_total, + "mode_of_payment": "Wire Transfer", + "transaction_date": today(), + "email_to": args.recipient_id or ref_doc.owner, + "subject": frappe._("Bank Payment Request for {0}").format(args.dn), + "message": gateway_account.get("message") or PR.get_dummy_message(ref_doc), + "reference_doctype": args.dt, + "reference_name": args.dn, + "party_type": args.get("party_type") or "Customer", + "party": args.get("party") or ref_doc.get("customer"), + "bank_account": bank_account, + "net_total": grand_total + } + ) + + # Update dimensions + bpr.update( + { + "cost_center": ref_doc.get("cost_center") or + frappe.get_value(ref_doc.get("doctype") + " Item", + {'parent': ref_doc.get("name")}, 'cost_center' + ), + "project": ref_doc.get("project") or + frappe.get_value(ref_doc.get("doctype") + " Item", + {'parent': ref_doc.get("name")}, 'project' + ) + } + ) + + for dimension in get_accounting_dimensions(): + bpr.update({dimension: ref_doc.get(dimension)}) + + bpr.insert(ignore_permissions=True) + + if args.submit_doc: + bpr.submit() + + if args.return_doc: + return bpr + + return bpr.as_dict() + +@frappe.whitelist() +def make_payment_order(source_name, target_doc=None, args= None): + from frappe.model.mapper import get_mapped_doc + + def set_missing_values(source, target): + target.payment_order_type = "Bank Payment Request" + account = "" + if source.payment_type: + account = frappe.db.get_value("Payment Type", source.payment_type, "account") + if source.reference_doctype == "Purchase Invoice": + account = frappe.db.get_value(source.reference_doctype, source.reference_name, "credit_to") + + for dimension in get_accounting_dimensions(): + target.update({dimension: source.get(dimension, '')}) + + target.append( + "references", + { + "reference_doctype": source.reference_doctype, + "reference_name": source.reference_name, + "amount": source.grand_total, + "party_type": source.party_type, + "party": source.party, + "bank_payment_request": source_name, + "mode_of_payment": source.mode_of_payment, + "bank_account": source.bank_account, + "account": account, + "is_adhoc": source.is_adhoc, + "cost_center": source.cost_center, + "project": source.project, + "tax_withholding_category": source.tax_withholding_category + }, + ) + target.status = "Pending" + + def update_missing_values(source, target): + target.payment_order_type = "Payment Entry" + target.company_bank_account = source.bank_account + + account = "" + if source.paid_to: + account = source.paid_to + + for dimension in get_accounting_dimensions(): + target.update({dimension: source.get(dimension, '')}) + + if source.references: + target.append( + "references", + { + "reference_doctype": source.references[0].reference_doctype, + "reference_name": source.references[0].reference_name, + "amount": source.references[0].total_amount, + "party_type": source.party_type, + "party": source.party, + "mode_of_payment": source.mode_of_payment, + "bank_account": get_party_bank_account(source.get("party_type"), source.get("party")) if source.get("party_type") else "", + "account": account, + "cost_center": source.cost_center, + "project": source.project, + "payment_entry": source.name + } + ) + else: + target.append( + "references", + { + "reference_doctype": "Payment Entry", + "reference_name": source.name, + "amount": source.paid_amount, + "party_type": source.party_type, + "party": source.party, + "mode_of_payment": "Wire Transfer", + "bank_account": source.party_bank_account, + "account": source.paid_to, + "cost_center": source.cost_center, + "project": source.project, + "payment_entry": source.name + } + ) + target.status = "Pending" + + if args.get('ref_doctype') != "Payment Entry": + doclist = get_mapped_doc( + "Bank Payment Request", + source_name, + { + "Bank Payment Request": { + "doctype": "Payment Order", + } + }, + target_doc, + set_missing_values, + ) + else: + doclist = get_mapped_doc( + "Payment Entry", + source_name, + { + "Payment Entry": { + "doctype": "Payment Order", + } + }, + target_doc, + update_missing_values, + ) + + return doclist + +def get_existing_payment_request_amount(ref_dt, ref_dn, submitted= True, update=None, payment_term= None): + """ + Get the existing Bank payment request which are unpaid or partially paid for payment channel other than Phone + and get the summation of existing paid Bank payment request for Phone payment channel. + """ + + docstatus = 1 if submitted else 0 + + where_conditions = "AND payment_term = '{0}'".format(payment_term.replace("%", "%%")) if payment_term else "AND payment_term is null" + + existing_payment_request_amount = frappe.db.sql( + """ + select sum(net_total) + from `tabBank Payment Request` + where + name != %s + and reference_doctype = %s + and reference_name = %s + and docstatus = %s {0} + """.format(where_conditions), + (update or "", ref_dt, ref_dn, docstatus) + ) + return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0 + +def get_amount(ref_doc, payment_account=None): + """get amount based on doctype""" + dt = ref_doc.doctype + if dt in ["Sales Order", "Purchase Order"]: + grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total) + elif dt in ["Sales Invoice", "Purchase Invoice"]: + if not ref_doc.get("is_pos"): + if ref_doc.party_account_currency == ref_doc.currency: + if ref_doc.rounded_total: + grand_total = flt(ref_doc.rounded_total) + else: + grand_total = flt(ref_doc.grand_total) + else: + if ref_doc.base_rounded_total: + grand_total = flt(ref_doc.base_rounded_total) / ref_doc.conversion_rate + else: + grand_total = flt(ref_doc.base_grand_total) / ref_doc.conversion_rate + elif dt == "Sales Invoice": + for pay in ref_doc.payments: + if pay.type == "Phone" and pay.account == payment_account: + grand_total = pay.amount + break + elif dt == "POS Invoice": + for pay in ref_doc.payments: + if pay.type == "Phone" and pay.account == payment_account: + grand_total = pay.amount + break + elif dt == "Fees": + grand_total = ref_doc.outstanding_amount + + if grand_total > 0: + return grand_total + else: + frappe.throw(frappe._("Bank Payment Entry is already created")) diff --git a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request_list.js b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request_list.js new file mode 100644 index 0000000..77a7933 --- /dev/null +++ b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request_list.js @@ -0,0 +1,21 @@ +frappe.listview_settings["Bank Payment Request"] = { + add_fields: ["status"], + get_indicator: function (doc) { + if (doc.status == "Draft") { + return [__("Draft"), "gray", "status,=,Draft"]; + } + if (doc.status == "Requested") { + return [__("Requested"), "green", "status,=,Requested"]; + } else if (doc.status == "Initiated") { + return [__("Initiated"), "green", "status,=,Initiated"]; + }else if (doc.status == 'Payment Ordered') { + return [__('Payment Ordered'), "green", "status,=,Payment Ordered"]; + }else if (doc.status == "Partially Paid") { + return [__("Partially Paid"), "orange", "status,=,Partially Paid"]; + } else if (doc.status == "Paid") { + return [__("Paid"), "blue", "status,=,Paid"]; + } else if (doc.status == "Cancelled") { + return [__("Cancelled"), "red", "status,=,Cancelled"]; + } + }, +}; diff --git a/india_banking/india_banking/doctype/bank_payment_request/test_bank_payment_request.py b/india_banking/india_banking/doctype/bank_payment_request/test_bank_payment_request.py new file mode 100644 index 0000000..f02c7ba --- /dev/null +++ b/india_banking/india_banking/doctype/bank_payment_request/test_bank_payment_request.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, Aerele Technologies Private Limited and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestBankPaymentRequest(FrappeTestCase): + pass From 0a6f8e3ce3c80d16a9805e9b6b122e63b513e8d0 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 27 Dec 2024 15:42:36 +0530 Subject: [PATCH 43/83] refactor: prettify the payment request js code --- india_banking/public/js/payment_request.js | 91 ++++++++-------------- 1 file changed, 33 insertions(+), 58 deletions(-) diff --git a/india_banking/public/js/payment_request.js b/india_banking/public/js/payment_request.js index 670da06..7b81736 100644 --- a/india_banking/public/js/payment_request.js +++ b/india_banking/public/js/payment_request.js @@ -12,74 +12,35 @@ frappe.ui.form.on("Payment Request", { .addClass("btn-primary"); } - frm.set_query("payment_type", function () { - return { - filters: { - company: frm.doc.company, - }, - }; - }); - frm.set_query("bank_account", function () { - return { - filters: { - company: frm.doc.company, - is_default: 1, - disabled: 0, - party_type: frm.doc.party_type, - party: frm.doc.party, - }, - }; - }); + set_payment_type_query(frm); + set_bank_account_query(frm); }, company(frm) { - frm.set_query("payment_type", function () { - return { - filters: { - company: frm.doc.company, - }, - }; - }); + set_payment_type_query(frm); }, mode_of_payment(frm) { - let conditions = get_bank_query_conditions(frm); - if (frm.doc.mode_of_payment == "Wire Transfer") { - frm.set_query("bank_account", function () { - return { - filters: conditions, - }; - }); - } + set_bank_account_query(frm); }, party_type(frm) { - let conditions = get_bank_query_conditions(frm); - if (frm.doc.mode_of_payment == "Wire Transfer") { - frm.set_query("bank_account", function () { - return { - filters: conditions, - }; - }); - } + set_bank_account_query(frm); }, party(frm) { - let conditions = get_bank_query_conditions(frm); - if (frm.doc.mode_of_payment == "Wire Transfer") { - frm.set_query("bank_account", function () { - return { - filters: conditions, - }; - }); - } + set_bank_account_query(frm); }, }); -const get_bank_query_conditions = function (frm) { - let conditions = {}; - if (frm.doc.party_type) { - conditions["party_type"] = frm.doc.party_type; - } - if (frm.doc.party) { - conditions["party"] = frm.doc.party; - } +function set_payment_type_query(frm) { + frm.set_query("payment_type", function () { + return { + filters: { + company: frm.doc.company, + }, + }; + }); +} + +function set_bank_account_query(frm) { + let conditions = get_bank_query_conditions(frm); if (frm.doc.mode_of_payment == "Wire Transfer") { frm.set_query("bank_account", function () { return { @@ -87,5 +48,19 @@ const get_bank_query_conditions = function (frm) { }; }); } +} + +function get_bank_query_conditions(frm) { + let conditions = { + company: frm.doc.company, + is_default: 1, + disabled: 0, + }; + if (frm.doc.party_type) { + conditions["party_type"] = frm.doc.party_type; + } + if (frm.doc.party) { + conditions["party"] = frm.doc.party; + } return conditions; -}; +} From 32f085158e73481c8dc1fa738e1730c714f84e11 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 27 Dec 2024 16:52:55 +0530 Subject: [PATCH 44/83] refactor: remove the bank account fixture workflow refactor: add script to create the bank account workflow --- india_banking/default.py | 59 +++++++++++++++ india_banking/fixtures/workflow.json | 73 ------------------- .../fixtures/workflow_action_master.json | 16 ---- india_banking/fixtures/workflow_state.json | 20 ----- india_banking/hooks.py | 9 --- india_banking/install.py | 48 +++++++++++- india_banking/uninstall.py | 20 +++++ 7 files changed, 126 insertions(+), 119 deletions(-) delete mode 100644 india_banking/fixtures/workflow.json delete mode 100644 india_banking/fixtures/workflow_action_master.json delete mode 100644 india_banking/fixtures/workflow_state.json diff --git a/india_banking/default.py b/india_banking/default.py index e7b842e..ebec6c8 100644 --- a/india_banking/default.py +++ b/india_banking/default.py @@ -44,3 +44,62 @@ "Axis Bank", "Kotak Mahindra Bank", ] + +DEFAULT_WORKFLOW_STATE = [ + "Pending", + "Approved", +] + +DEFAULT_WORKFLOW_ACTIONS = ["Approve", "Reject"] + +DEFAULT_WORKFLOW_LIST = [ + { + "doctype": "Workflow", + "document_type": "Bank Account", + "workflow_name": "Bank Account Approval", + "workflow_state_field": "workflow_state", + "is_active": 1, + "states": [ + { + "allow_edit": "All", + "doc_status": "0", + "parent": "Bank Account Approval", + "parentfield": "states", + "parenttype": "Workflow", + "state": "Pending", + "update_value": "Pending", + }, + { + "allow_edit": "Accounts Manager", + "doc_status": "0", + "parent": "Bank Account Approval", + "parentfield": "states", + "parenttype": "Workflow", + "state": "Approved", + "update_value": "Approved", + }, + ], + "transitions": [ + { + "state": "Pending", + "action": "Approve", + "next_state": "Approved", + "allowed": "Accounts Manager", + "allow_self_approval": 1, + "parent": "Bank Account Approval", + "parentfield": "transitions", + "parenttype": "Workflow", + }, + { + "action": "Reject", + "next_state": "Pending", + "allowed": "Accounts Manager", + "allow_self_approval": 1, + "parent": "Bank Account Approval", + "parentfield": "transitions", + "parenttype": "Workflow", + "state": "Approved", + }, + ], + } +] diff --git a/india_banking/fixtures/workflow.json b/india_banking/fixtures/workflow.json deleted file mode 100644 index 658e807..0000000 --- a/india_banking/fixtures/workflow.json +++ /dev/null @@ -1,73 +0,0 @@ -[ - { - "docstatus": 0, - "doctype": "Workflow", - "document_type": "Bank Account", - "is_active": 1, - "modified": "2024-08-13 07:38:04.636572", - "name": "Bank Account Approval", - "override_status": 0, - "send_email_alert": 0, - "states": [ - { - "allow_edit": "All", - "avoid_status_override": 0, - "doc_status": "0", - "is_optional_state": 0, - "message": null, - "next_action_email_template": null, - "parent": "Bank Account Approval", - "parentfield": "states", - "parenttype": "Workflow", - "state": "Pending", - "update_field": null, - "update_value": "Pending", - "workflow_builder_id": null - }, - { - "allow_edit": "Accounts Manager", - "avoid_status_override": 0, - "doc_status": "0", - "is_optional_state": 0, - "message": null, - "next_action_email_template": null, - "parent": "Bank Account Approval", - "parentfield": "states", - "parenttype": "Workflow", - "state": "Approved", - "update_field": null, - "update_value": "Approved", - "workflow_builder_id": null - } - ], - "transitions": [ - { - "action": "Approve", - "allow_self_approval": 1, - "allowed": "Accounts Manager", - "condition": null, - "next_state": "Approved", - "parent": "Bank Account Approval", - "parentfield": "transitions", - "parenttype": "Workflow", - "state": "Pending", - "workflow_builder_id": null - }, - { - "action": "Reject", - "allow_self_approval": 1, - "allowed": "Accounts Manager", - "condition": null, - "next_state": "Pending", - "parent": "Bank Account Approval", - "parentfield": "transitions", - "parenttype": "Workflow", - "state": "Approved", - "workflow_builder_id": null - } - ], - "workflow_data": null, - "workflow_name": "Bank Account Approval", - "workflow_state_field": "workflow_state" - } -] \ No newline at end of file diff --git a/india_banking/fixtures/workflow_action_master.json b/india_banking/fixtures/workflow_action_master.json deleted file mode 100644 index a414d0d..0000000 --- a/india_banking/fixtures/workflow_action_master.json +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "docstatus": 0, - "doctype": "Workflow Action Master", - "modified": "2024-07-30 18:01:52.999923", - "name": "Reject", - "workflow_action_name": "Reject" - }, - { - "docstatus": 0, - "doctype": "Workflow Action Master", - "modified": "2024-07-30 18:01:52.999112", - "name": "Approve", - "workflow_action_name": "Approve" - } -] \ No newline at end of file diff --git a/india_banking/fixtures/workflow_state.json b/india_banking/fixtures/workflow_state.json deleted file mode 100644 index 76660a3..0000000 --- a/india_banking/fixtures/workflow_state.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "docstatus": 0, - "doctype": "Workflow State", - "icon": "remove", - "modified": "2024-07-30 18:01:52.987894", - "name": "Rejected", - "style": "Danger", - "workflow_state_name": "Rejected" - }, - { - "docstatus": 0, - "doctype": "Workflow State", - "icon": "ok-sign", - "modified": "2024-07-30 18:01:52.987135", - "name": "Approved", - "style": "Success", - "workflow_state_name": "Approved" - } -] \ No newline at end of file diff --git a/india_banking/hooks.py b/india_banking/hooks.py index 088dd56..5b443f8 100644 --- a/india_banking/hooks.py +++ b/india_banking/hooks.py @@ -27,15 +27,6 @@ # include js in page # page_js = {"page" : "public/js/file.js"} -fixtures = [ - {"dt": "Workflow", "filters": [["name", "in", ["Bank Account Approval"]]]}, - {"dt": "Workflow State", "filters": [["name", "in", ["Approved", "Rejected"]]]}, - { - "dt": "Workflow Action Master", - "filters": [["name", "in", ["Pending", "Approve", "Reject"]]], - }, -] - doctype_js = { "Payment Order": "public/js/payment_order.js", "Purchase Invoice": "public/js/purchase_invoice.js", diff --git a/india_banking/install.py b/india_banking/install.py index 9c1ed40..16ebe26 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -3,7 +3,13 @@ from frappe import make_property_setter from frappe.custom.doctype.custom_field.custom_field import create_custom_fields -from india_banking.default import DEFAULT_MODE_OF_TRANSFERS, STD_BANK_LIST +from india_banking.default import ( + DEFAULT_MODE_OF_TRANSFERS, + DEFAULT_WORKFLOW_ACTIONS, + DEFAULT_WORKFLOW_LIST, + DEFAULT_WORKFLOW_STATE, + STD_BANK_LIST, +) def after_install(): @@ -14,6 +20,7 @@ def after_install(): toggle_reqd_for_reference_in_payment_order(False) create_default_mode_of_transfers() create_default_payment_type() + create_default_workflow() def make_custom_fields(): @@ -483,3 +490,42 @@ def create_default_payment_type(): frappe.get_doc({"doctype": "Payment Type", "payment_type": "Pay"}).insert( ignore_permissions=True, ignore_mandatory=True ) + + +def create_default_workflow(): + click.echo(" -> Updating workflow") + + create_default_workflow_state() + create_default_workflow_actions() + + for workflow in DEFAULT_WORKFLOW_LIST: + workflow = frappe._dict(workflow) + if not frappe.db.exists( + "Workflow", + { + "document_type": workflow.document_type, + "workflow_name": workflow.workflow_name, + }, + ): + click.echo( + f" |-> Creating workflow for the {workflow.document_type} Doctype." + ) + frappe.get_doc(workflow).insert(ignore_permissions=True, ignore_links=True) + + +def create_default_workflow_state(): + click.echo(" |-> Creating Workflow state") + + for state in DEFAULT_WORKFLOW_STATE: + if not frappe.db.exists("Workflow Document State", state): + workflow_state_doc = frappe.new_doc("Workflow Document State") + workflow_state_doc.workflow_state_name = state + + +def create_default_workflow_actions(): + click.echo(" |-> Creating Workflow action") + + for action in DEFAULT_WORKFLOW_ACTIONS: + if not frappe.db.exists("Workflow Action Master", action): + workflow_state_doc = frappe.new_doc("Workflow Action Master") + workflow_state_doc.workflow_action_name = action diff --git a/india_banking/uninstall.py b/india_banking/uninstall.py index 0597a01..373eacb 100644 --- a/india_banking/uninstall.py +++ b/india_banking/uninstall.py @@ -2,6 +2,7 @@ import frappe from frappe.custom.doctype.property_setter.property_setter import delete_property_setter +from india_banking.default import DEFAULT_WORKFLOW_LIST from india_banking.install import ( properties, toggle_payment_request_creation, @@ -14,6 +15,25 @@ def before_uninstall(): toggle_payment_request_creation(False) delete_propert_setters() toggle_reqd_for_reference_in_payment_order(True) + delete_default_workflow() + + +def delete_default_workflow(): + click.echo("* Removing Default workflow") + + for workflow in DEFAULT_WORKFLOW_LIST: + workflow = frappe._dict(workflow) + if workflow_name := frappe.db.exists( + "Workflow", + { + "document_type": workflow.document_type, + "workflow_name": workflow.workflow_name, + }, + ): + click.echo( + f"-> Deleting workflow for the {workflow.document_type} Doctype." + ) + frappe.delete_doc("Workflow", workflow_name) def delete_custom_fields(): From 0cf81917a3438942b445cd5f19f92a38469f66eb Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 27 Dec 2024 16:58:55 +0530 Subject: [PATCH 45/83] refactor: optimise bank doc_events code --- india_banking/india_banking/doc_events/bank.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/india_banking/india_banking/doc_events/bank.py b/india_banking/india_banking/doc_events/bank.py index 43914e3..43329ea 100644 --- a/india_banking/india_banking/doc_events/bank.py +++ b/india_banking/india_banking/doc_events/bank.py @@ -4,15 +4,16 @@ from frappe import _ from frappe.utils import cstr +IFSC_PATTERN = re.compile(r"^[A-Z]{4}0[A-Z0-9]{6}$") + def bank_on_trash(doc, method=None): - if hasattr(doc, "is_standard") and doc.is_standard: + if getattr(doc, "is_standard", False): frappe.throw( _("Standard Bank cannot be deleted"), title=_("Action Not Permitted") ) def validate_ifsc_code(doc, method=None): - pattern = re.compile("^[A-Z]{4}0[A-Z0-9]{6}$") - if not pattern.match(cstr(doc.branch_code)): + if not IFSC_PATTERN.match(cstr(doc.branch_code)): frappe.throw(_("IFSC/Branch Code is not valid")) From aab8f6b372b71e9f34fbb41c13923fd060f405ed Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 27 Dec 2024 17:04:27 +0530 Subject: [PATCH 46/83] fix: remove payment order from accounting dimention hook --- india_banking/hooks.py | 1 - 1 file changed, 1 deletion(-) diff --git a/india_banking/hooks.py b/india_banking/hooks.py index 5b443f8..dd4bbf5 100644 --- a/india_banking/hooks.py +++ b/india_banking/hooks.py @@ -159,7 +159,6 @@ accounting_dimension_doctypes = [ - "Payment Order", "Payment Order Reference", "Payment Order Summary", ] From 5f51998fd7e1433a8b47b9152aac0f8e55bd367a Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 31 Dec 2024 12:50:22 +0530 Subject: [PATCH 47/83] fix: update payment status before reinitiate payment. --- .../doctype/bank_connector/bank_connector.py | 4 +- india_banking/public/js/payment_order.js | 52 +++++++++---------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/india_banking/india_banking/doctype/bank_connector/bank_connector.py b/india_banking/india_banking/doctype/bank_connector/bank_connector.py index 547cfa8..7bf137a 100644 --- a/india_banking/india_banking/doctype/bank_connector/bank_connector.py +++ b/india_banking/india_banking/doctype/bank_connector/bank_connector.py @@ -53,6 +53,8 @@ def make_payment(self, payment_order, otp=None): if otp: self.verify_otp(payment_order, otp) + self.get_payment_status(payment_order) + # Make the payment if self.bulk_transaction: return self.make_bulk_payment(payment_order, otp) @@ -68,7 +70,7 @@ def get_payment_status(self, payment_order): def get_single_payment_status(self, payment_order): for summary_row in payment_order.summary: - if summary_row.payment_status == "Initiated": + if summary_row.payment_status in ["Initiated", "Pending"]: self.get_status_response(summary_row, payment_order) payment_order.reload() diff --git a/india_banking/public/js/payment_order.js b/india_banking/public/js/payment_order.js index b924143..b8d4bd9 100644 --- a/india_banking/public/js/payment_order.js +++ b/india_banking/public/js/payment_order.js @@ -212,33 +212,33 @@ frappe.ui.form.on("Payment Order", { } } - if ( - ["Pending", "Initiated"].includes(frm.doc.status) && - frm.doc.docstatus === 1 && - frm.has_perm("write") - ) { - // Check if any summary item has a payment status of "Initiated" - const has_initiated_status = frm.doc.summary.some( - (item) => item.payment_status === "Initiated" - ); + if ( + ["Pending", "Initiated"].includes(frm.doc.status) && + frm.doc.docstatus === 1 && + frm.has_perm("write") + ) { + const has_initiated_or_non_pending = frm.doc.summary.some( + (item) => item.payment_status === "Initiated" || item.payment_status !== "Pending" + ); - if (has_initiated_status) { - frm.add_custom_button(__("Get Status"), () => { - frappe.call({ - method: - "india_banking.india_banking.doctype.bank_connector.bank_connector.get_payment_status", - freeze: true, - freeze_message: __("Fetching payment status..."), - args: { - payment_order: frm.doc.name, - }, - callback: function (response) { - frm.reload_doc(); - }, - }); - }); - } - } + if (has_initiated_or_non_pending) { + frm.dashboard.add_comment("Payment is already initiated. Check the status using the 'Get Status' button before trying again.", permanent=false); + frm.add_custom_button(__("Get Status"), () => { + frappe.call({ + method: + "india_banking.india_banking.doctype.bank_connector.bank_connector.get_payment_status", + freeze: true, + freeze_message: __("Fetching payment status..."), + args: { + payment_order: frm.doc.name, + }, + callback: function () { + frm.reload_doc(); + }, + }); + }); + } + } }, make_payment: function (frm) { From 9fa4b7276cc02ca36ed85af070999b7932cacb9e Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Thu, 2 Jan 2025 20:43:32 +0530 Subject: [PATCH 48/83] refactor: while create api log return name ref refactor: add translate --- .../india_banking_request_log/india_banking_request_log.py | 1 + india_banking/install.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py b/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py index dc53068..a31237b 100644 --- a/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py +++ b/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py @@ -57,3 +57,4 @@ def create_api_log(res, action=None, ref_doctype=None, ref_docname=None): ) else: frappe.db.commit() + return log_doc.name diff --git a/india_banking/install.py b/india_banking/install.py index 16ebe26..ac26743 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -21,6 +21,7 @@ def after_install(): create_default_mode_of_transfers() create_default_payment_type() create_default_workflow() + create_default_bank() def make_custom_fields(): @@ -470,6 +471,7 @@ def toggle_reqd_for_reference_in_payment_order(reqd=False): def create_default_bank(): + click.echo(" -> Creating Default Banks") for bank in STD_BANK_LIST: if not frappe.db.exists("Bank", bank): bank_doc = frappe.new_doc("Bank") From 71ec851db80fe7fc8022f973491b8dd3a67c45dd Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 3 Jan 2025 09:51:57 +0530 Subject: [PATCH 49/83] fix: add summarize based on only draft state refactor: pretify message content --- india_banking/install.py | 2 +- india_banking/public/js/payment_order.js | 16 +++++++++------- india_banking/uninstall.py | 4 ++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/india_banking/install.py b/india_banking/install.py index ac26743..07bd41a 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -285,7 +285,7 @@ def create_payment_order_custom_fields(): "fieldname": "default_mode_of_transfer", "fieldtype": "Link", "options": "Mode of Transfer", - "insert_after": "payment_summary", + "insert_after": "get_summary", }, { "label": "Summary", diff --git a/india_banking/public/js/payment_order.js b/india_banking/public/js/payment_order.js index b8d4bd9..8bc193a 100644 --- a/india_banking/public/js/payment_order.js +++ b/india_banking/public/js/payment_order.js @@ -1,13 +1,15 @@ frappe.ui.form.on("Payment Order", { onload(frm) { // Set summary based on party or voucher - frappe.db - .get_single_value("India Banking Settings", "summarise_payment_based_on") - .then((res) => { - if (res === "Party") { - frm.set_value("summarise_payment_based_on", res); - } - }); + if(frm.doc.docstatus==0){ + frappe.db + .get_single_value("India Banking Settings", "summarise_payment_based_on") + .then((res) => { + if (res === "Party") { + frm.set_value("summarise_payment_based_on", res); + } + }); + } // Clear the references table for new documents if (frm.is_new()) { diff --git a/india_banking/uninstall.py b/india_banking/uninstall.py index 373eacb..d04af06 100644 --- a/india_banking/uninstall.py +++ b/india_banking/uninstall.py @@ -19,7 +19,7 @@ def before_uninstall(): def delete_default_workflow(): - click.echo("* Removing Default workflow") + click.echo(" -> Removing Default workflow") for workflow in DEFAULT_WORKFLOW_LIST: workflow = frappe._dict(workflow) @@ -31,7 +31,7 @@ def delete_default_workflow(): }, ): click.echo( - f"-> Deleting workflow for the {workflow.document_type} Doctype." + f" -> Deleting workflow for the {workflow.document_type} Doctype." ) frappe.delete_doc("Workflow", workflow_name) From 660c80df3a201315d28144ead3581852fa68fed7 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 3 Jan 2025 11:59:34 +0530 Subject: [PATCH 50/83] feat: add pending payment cancel button fix: add missing fields --- .../india_banking/doc_events/payment_order.py | 65 ++++++ .../payment_order_summary.json | 16 +- india_banking/install.py | 53 +++++ india_banking/public/js/payment_order.js | 200 +++++++++++++++--- india_banking/uninstall.py | 2 + 5 files changed, 299 insertions(+), 37 deletions(-) diff --git a/india_banking/india_banking/doc_events/payment_order.py b/india_banking/india_banking/doc_events/payment_order.py index 421527b..d6af584 100644 --- a/india_banking/india_banking/doc_events/payment_order.py +++ b/india_banking/india_banking/doc_events/payment_order.py @@ -7,6 +7,7 @@ get_accounting_dimensions, ) from erpnext.accounts.doctype.payment_entry.payment_entry import get_split_invoice_rows +from frappe import _, parse_json from frappe.utils import nowdate from frappe.utils.data import comma_and, cstr @@ -17,6 +18,70 @@ from india_banking.utils import get_bank_address_details +@frappe.whitelist() +def cancel_pending_payments(data): + if isinstance(data, str) or isinstance(data, dict): + data = parse_json(data) + + success_count = 0 + for d in data: + d = parse_json(d) + if d.row_name: + if d.status == "Failed": + frappe.db.set_value( + "Payment Order Summary", + d.row_name, + {"payment_status": "Failed", "payment_initiated": 1}, + ) + + if d.payment_entry: + payment_entry_doc = frappe.get_doc("Payment Entry", d.payment_entry) + if payment_entry_doc.docstatus == 1: + payment_entry_doc.cancel() + + process_payment_requests(d.row_name) + + success_count += 1 + if success_count: + frappe.msgprint(_(f"{success_count} payment(s) updated")) + + +def process_payment_requests(payment_order_summary): + pos = frappe.get_doc("Payment Order Summary", payment_order_summary) + payment_order_doc = frappe.get_doc("Payment Order", pos.parent) + + summarise_field = [ + "party_type", + "party", + "bank_account", + "account", + "cost_center", + "project", + "tax_withholding_category", + "reference_doctype", + "reference_name", + ] + if payment_order_doc.summarise_payment_based_on == "Party": + summarise_field.remove("reference_name") + + summarise_field.extend(get_accounting_dimensions()) + key = tuple([pos.get(field, "") for field in summarise_field]) + + failed_prs = [] + for ref in payment_order_doc.references: + ref_key = tuple([ref.get(field, "") for field in summarise_field]) + + if key == ref_key and ref.payment_request: + failed_prs.append(ref.payment_request) + + for pr in failed_prs: + pr_doc = frappe.get_doc("Payment Request", pr) + if pr_doc.docstatus == 1: + pr_doc.check_if_payment_entry_exists() + pr_doc.set_as_cancelled() + pr_doc.db_set("docstatus", 2) + + @frappe.whitelist() def get_bank_balance(bank_name): bank_doc = frappe.get_doc("Bank Account", bank_name) diff --git a/india_banking/india_banking/doctype/payment_order_summary/payment_order_summary.json b/india_banking/india_banking/doctype/payment_order_summary/payment_order_summary.json index 3dfce72..426017c 100644 --- a/india_banking/india_banking/doctype/payment_order_summary/payment_order_summary.json +++ b/india_banking/india_banking/doctype/payment_order_summary/payment_order_summary.json @@ -8,6 +8,7 @@ "field_order": [ "party_type", "party", + "party_name", "amount", "banking_section", "mode_of_transfer", @@ -24,7 +25,6 @@ "account", "tax_withholding_category", "payment_entry", - "payroll_entry", "journal_entry", "journal_entry_account", "reference_doctype", @@ -179,12 +179,6 @@ "options": "reference_doctype", "read_only": 1 }, - { - "fieldname": "payroll_entry", - "fieldtype": "Data", - "label": "Payroll Entry", - "read_only": 1 - }, { "fieldname": "journal_entry", "fieldtype": "Data", @@ -232,12 +226,18 @@ "fieldname": "bank", "fieldtype": "Data", "label": "Bank" + }, + { + "fieldname": "party_name", + "fieldtype": "Data", + "label": "Party Name", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-12-23 15:36:49.167883", + "modified": "2025-01-03 12:50:25.880199", "modified_by": "Administrator", "module": "India Banking", "name": "Payment Order Summary", diff --git a/india_banking/install.py b/india_banking/install.py index 07bd41a..2cc1061 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -370,6 +370,59 @@ def create_payment_order_custom_fields(): "options": "Project", "insert_after": "cost_center", }, + { + "label": "Payment Entry", + "fieldname": "payment_entry", + "fieldtype": "Data", + "insert_after": "amount", + "read_only": 1, + }, + { + "label": "Journal Entry Account", + "fieldname": "journal_entry_account", + "fieldtype": "Data", + "hidden": 1, + "insert_after": "payment_entry", + }, + { + "label": "Payment Request", + "fieldname": "payment_request", + "fieldtype": "Data", + "read_only": 1, + "insert_after": "journal_entry_account", + }, + { + "label": "Bank", + "fieldname": "bank", + "fieldtype": "Data", + "read_only": 1, + "insert_after": "payment_request", + "fetch_from": "bank_account.bank", + }, + { + "label": "Bank Account No", + "fieldname": "bank_account_no", + "fieldtype": "Data", + "read_only": 1, + "insert_after": "bank", + "fetch_from": "bank_account.bank_account_no", + }, + { + "label": "Branch Code", + "fieldname": "branch_code", + "fieldtype": "Data", + "read_only": 1, + "insert_after": "bank_account_no", + "fetch_from": "bank_account.branch_code", + }, + { + "label": "Account Name", + "fieldname": "account_name", + "fieldtype": "Data", + "read_only": 1, + "insert_after": "branch_code", + "fetch_from": "bank_account.account_name", + }, ], } diff --git a/india_banking/public/js/payment_order.js b/india_banking/public/js/payment_order.js index 8bc193a..d433114 100644 --- a/india_banking/public/js/payment_order.js +++ b/india_banking/public/js/payment_order.js @@ -1,9 +1,12 @@ frappe.ui.form.on("Payment Order", { onload(frm) { // Set summary based on party or voucher - if(frm.doc.docstatus==0){ + if (frm.doc.docstatus == 0) { frappe.db - .get_single_value("India Banking Settings", "summarise_payment_based_on") + .get_single_value( + "India Banking Settings", + "summarise_payment_based_on" + ) .then((res) => { if (res === "Party") { frm.set_value("summarise_payment_based_on", res); @@ -56,10 +59,22 @@ frappe.ui.form.on("Payment Order", { frm.trigger("set_get_payments_from_buttons"); frm.trigger("set_payment_and_status_buttons"); + frm.trigger("set_pending_payment_cancel_button"); frm.trigger("remove_button"); }, + set_pending_payment_cancel_button(frm) { + const has_pending_payment = frm.doc.summary.some( + (item) => item.payment_status == "Pending" + ); + if (has_pending_payment && frm.doc.docstatus == 1) { + frm.add_custom_button(__("Cancel Pending Payments"), function () { + show_update_status_dialog(frm); + }); + } + }, + set_get_payments_from_buttons(frm) { if (frm.doc.docstatus === 0) { // Define an array of payment sources and their respective triggers @@ -214,33 +229,38 @@ frappe.ui.form.on("Payment Order", { } } - if ( - ["Pending", "Initiated"].includes(frm.doc.status) && - frm.doc.docstatus === 1 && - frm.has_perm("write") - ) { - const has_initiated_or_non_pending = frm.doc.summary.some( - (item) => item.payment_status === "Initiated" || item.payment_status !== "Pending" - ); - - if (has_initiated_or_non_pending) { - frm.dashboard.add_comment("Payment is already initiated. Check the status using the 'Get Status' button before trying again.", permanent=false); - frm.add_custom_button(__("Get Status"), () => { - frappe.call({ - method: - "india_banking.india_banking.doctype.bank_connector.bank_connector.get_payment_status", - freeze: true, - freeze_message: __("Fetching payment status..."), - args: { - payment_order: frm.doc.name, - }, - callback: function () { - frm.reload_doc(); - }, - }); - }); - } - } + if ( + ["Pending", "Initiated"].includes(frm.doc.status) && + frm.doc.docstatus === 1 && + frm.has_perm("write") + ) { + const has_initiated_or_non_pending = frm.doc.summary.some( + (item) => + item.payment_status === "Initiated" || + item.payment_status !== "Pending" + ); + + if (has_initiated_or_non_pending) { + frm.dashboard.add_comment( + "Payment is already initiated. Check the status using the 'Get Status' button before trying again.", + (permanent = false) + ); + frm.add_custom_button(__("Get Status"), () => { + frappe.call({ + method: + "india_banking.india_banking.doctype.bank_connector.bank_connector.get_payment_status", + freeze: true, + freeze_message: __("Fetching payment status..."), + args: { + payment_order: frm.doc.name, + }, + callback: function () { + frm.reload_doc(); + }, + }); + }); + } + } }, make_payment: function (frm) { @@ -368,3 +388,125 @@ frappe.ui.form.on("Payment Order", { }); }, }); + +const show_update_status_dialog = function (frm) { + frm.data = []; + const dialog = new frappe.ui.Dialog({ + title: __("Pending Payments"), + size: "extra-large", + fields: [ + { + fieldtype: "HTML", + options: `

Cancel any pending payments by updating the payment status to Failed.

`, + }, + { + fieldname: "summary", + fieldtype: "Table", + label: __("Summary"), + data: frm.data, + in_place_edit: true, + cannot_add_rows: true, + cannot_delete_rows: true, + get_data: () => { + return frm.data; + }, + fields: [ + { + label: __("Row Name"), + fieldname: "row_name", + fieldtype: "data", + read_only: 1, + }, + { + label: __("payment_order"), + fieldname: "payment_order", + fieldtype: "data", + hidden: 1, + }, + { + label: __("Party Type"), + fieldname: "party_type", + fieldtype: "Link", + options: "DocType", + in_list_view: 1, + columns: 1, + read_only: 1, + get_query: () => { + return { + filters: { + company: frm.doc.company, + name: ["in", ["Supplier", "Employee"]], + }, + }; + }, + }, + { + label: __("Party"), + fieldname: "party", + fieldtype: "Dynamic Link", + options: "party_type", + columns: 2, + in_list_view: 1, + read_only: 1, + }, + { + label: __("Amount"), + fieldname: "amount", + fieldtype: "Currency", + in_list_view: 1, + columns: 1, + read_only: 1, + }, + { + label: __("Status"), + fieldname: "status", + fieldtype: "Select", + options: "\nPending\nFailed", + columns: 1, + in_list_view: 1, + }, + { + label: __("Payment Entry"), + fieldname: "payment_entry", + fieldtype: "Data", + hidden: 1, + }, + ], + }, + ], + primary_action: () => { + frm.call({ + method: + "india_banking.india_banking.doc_events.payment_order.cancel_pending_payments", + args: { + data: dialog.get_values()["summary"], + }, + freeze: true, + freeze_message: __("Cancelling..."), + callback: function (r) { + dialog.hide(); + frm.reload_doc(); + }, + }); + }, + primary_action_label: __("Update"), + }); + + frm.doc.summary.forEach((d) => { + if (["Pending", "Initiated"].includes(d.payment_status)) { + dialog.fields_dict.summary.df.data.push({ + payment_order: frm.doc.name, + row_name: d.name, + party_type: d.party_type, + party: d.party, + amount: d.amount, + payment_entry: d.payment_entry, + }); + } + }); + + frm.data = []; + dialog.show(); + dialog.fields_dict.summary.grid.refresh(); + dialog.$wrapper.find(".grid-row-check").prop("disabled", 1); +}; diff --git a/india_banking/uninstall.py b/india_banking/uninstall.py index d04af06..dd3f8d9 100644 --- a/india_banking/uninstall.py +++ b/india_banking/uninstall.py @@ -89,6 +89,8 @@ def delete_custom_fields(): "remarks", "cost_center", "project", + "payment_entry", + "journal_entry_account", ], "Supplier": ["lei_number"], "Bank": ["is_standard"], From d13164d0e9b2ef6ca9430b447bae19a44fe21c66 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 3 Jan 2025 13:41:05 +0530 Subject: [PATCH 51/83] refactor: add custom field update patch --- india_banking/install.py | 57 ++++++++++--------- india_banking/patches.txt | 2 + india_banking/patches/v1_0/__init__.py | 0 .../patches/v1_0/update_custom_fields.py | 42 ++++++++++++++ india_banking/uninstall.py | 27 ++++++++- 5 files changed, 98 insertions(+), 30 deletions(-) create mode 100644 india_banking/patches/v1_0/__init__.py create mode 100644 india_banking/patches/v1_0/update_custom_fields.py diff --git a/india_banking/install.py b/india_banking/install.py index 2cc1061..ac51ff8 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -228,6 +228,14 @@ def create_payment_order_custom_fields(): click.secho(" -> Installing Custom Fields in a Payment Order") fields = { "Payment Order": [ + { + "label": "Status", + "fieldname": "status", + "fieldtype": "Select", + "options": "\nPending\nPending Approval\nPartially Approved\nApproved\nPartially Initiated\nInitiated\nRejected\nFailed", + "read_only": 1, + "insert_after": "posting_date", + }, { "label": "ICICI Bank Api Info", "fieldname": "icici_bank_api_info", @@ -241,25 +249,25 @@ def create_payment_order_custom_fields(): "hidden": 1, "insert_after": "icici_bank_api_info", }, + { + "label": "File Reference Id", + "fieldname": "file_reference_id", + "hidden": 1, + "fieldtype": "Data", + "insert_after": "unique_id", + }, { "fieldtype": "Column Break", "fieldname": "bank_api_info_column_break", - "insert_after": "unique_id", + "insert_after": "file_reference_id", }, { "label": "File Sequence Number", "fieldname": "file_sequence_number", "fieldtype": "Data", - "hidden": 1, + "read_only": 1, "insert_after": "bank_api_info_column_break", }, - { - "label": "File Reference Id", - "fieldname": "file_reference_id", - "hidden": 1, - "fieldtype": "Data", - "insert_after": "file_sequence_number", - }, { "label": "Payment Summary", "fieldname": "payment_summary", @@ -280,19 +288,29 @@ def create_payment_order_custom_fields(): "fieldtype": "Button", "insert_after": "summarise_payment_based_on", }, + { + "fieldname": "payment_summary_column_break", + "fieldtype": "Column Break", + "insert_after": "get_summary", + }, { "label": "Default Mode of Transfer", "fieldname": "default_mode_of_transfer", "fieldtype": "Link", "options": "Mode of Transfer", - "insert_after": "get_summary", + "insert_after": "payment_summary_column_break", + }, + { + "fieldname": "payment_summary2", + "fieldtype": "Section Break", + "insert_after": "default_mode_of_transfer", }, { "label": "Summary", "fieldname": "summary", "fieldtype": "Table", "options": "Payment Order Summary", - "insert_after": "default_mode_of_transfer", + "insert_after": "payment_summary2", "no_copy": 1, }, { @@ -301,14 +319,6 @@ def create_payment_order_custom_fields(): "fieldtype": "Currency", "insert_after": "summary", }, - { - "label": "Status", - "fieldname": "status", - "fieldtype": "Select", - "options": "\nPending\nPending Approval\nPartially Approved\nApproved\nPartially Initiated\nInitiated\nRejected\nFailed", - "read_only": 1, - "insert_after": "posting_date", - }, ], "Payment Order Reference": [ { @@ -384,19 +394,12 @@ def create_payment_order_custom_fields(): "hidden": 1, "insert_after": "payment_entry", }, - { - "label": "Payment Request", - "fieldname": "payment_request", - "fieldtype": "Data", - "read_only": 1, - "insert_after": "journal_entry_account", - }, { "label": "Bank", "fieldname": "bank", "fieldtype": "Data", "read_only": 1, - "insert_after": "payment_request", + "insert_after": "journal_entry_account", "fetch_from": "bank_account.bank", }, { diff --git a/india_banking/patches.txt b/india_banking/patches.txt index f15c3a9..76bdf41 100644 --- a/india_banking/patches.txt +++ b/india_banking/patches.txt @@ -2,5 +2,7 @@ # Patches added in this section will be executed before doctypes are migrated # Read docs to understand patches: https://frappeframework.com/docs/v14/user/en/database-migrations +india_banking.patches.v1_0.update_custom_fields + [post_model_sync] # Patches added in this section will be executed after doctypes are migrated \ No newline at end of file diff --git a/india_banking/patches/v1_0/__init__.py b/india_banking/patches/v1_0/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/india_banking/patches/v1_0/update_custom_fields.py b/india_banking/patches/v1_0/update_custom_fields.py new file mode 100644 index 0000000..aacf32d --- /dev/null +++ b/india_banking/patches/v1_0/update_custom_fields.py @@ -0,0 +1,42 @@ +import frappe + +from india_banking.install import make_custom_fields +from india_banking.uninstall import delete_custom_fields + + +def execute(): + delete_custom_fields() + remove_custom_section_and_column_break_fields() + remove_main_field_order() + make_custom_fields() + + +def remove_custom_section_and_column_break_fields(): + if fields := frappe.get_all( + "Custom Field", + filters={ + "dt": [ + "in", + [ + "Journal Entry Account", + "Payment Entry", + "Payment Order", + "Payment Order Reference", + "Payment Request", + ], + ], + "fieldtype": ["in", ["Section Break", "Column Break"]], + }, + pluck="name", + ): + frappe.db.delete("Custom Field", {"name": ["in", fields]}) + + +def remove_main_field_order(): + doctypes = [ + "Payment Order", + "Payment Order Reference", + "Payment Request", + ] + for doctype in doctypes: + frappe.db.delete("Property Setter", {"name": f"{doctype}-main-field_order"}) \ No newline at end of file diff --git a/india_banking/uninstall.py b/india_banking/uninstall.py index dd3f8d9..07c6dfa 100644 --- a/india_banking/uninstall.py +++ b/india_banking/uninstall.py @@ -68,19 +68,29 @@ def delete_custom_fields(): "remark_section", ], "Payment Order": [ - "icici_bank_api_info", + "company_account_number", + "company_bank_account_name", + "company_ifsc", + "mobile_number", + "is_party_wise", "unique_id", - "bank_api_info_column_break", "file_sequence_number", "file_reference_id", + "summarise_payment_based_on", "get_summary", - "payment_summary", "default_mode_of_transfer", "summary", "total", "status", + "icici_bank_api_info", + "payment_summary", + "bank_api_info_column_break", + "payment_summary_column_break", + "amended_from", ], "Payment Order Reference": [ + "bank_payment_request", + "party_name", "party_type", "party", "tax_withholding_category", @@ -89,8 +99,19 @@ def delete_custom_fields(): "remarks", "cost_center", "project", + "payroll_entry", + "expense_head", + "payment_due_date", "payment_entry", + "credit_period", + "overdue_from_credit_period", "journal_entry_account", + "bank", + "bank_account_no", + "branch_code", + "account_name", + "payment_summary", + "payment_summary2", ], "Supplier": ["lei_number"], "Bank": ["is_standard"], From dc69f0d79dcb45164e60793fe56ae1f4f3e9818a Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 3 Jan 2025 18:38:48 +0530 Subject: [PATCH 52/83] fix: while create payment entry modify filter condition with accounting dimention --- india_banking/hooks.py | 73 ++++++++++--------- .../india_banking/doc_events/payment_order.py | 42 ++++++----- india_banking/overrides/payment_order.py | 2 +- 3 files changed, 64 insertions(+), 53 deletions(-) diff --git a/india_banking/hooks.py b/india_banking/hooks.py index dd4bbf5..d8a4e0f 100644 --- a/india_banking/hooks.py +++ b/india_banking/hooks.py @@ -4,6 +4,46 @@ app_description = "Indian Banking Integration with ERPNext" app_email = "support@aerele.in" app_license = "gpl-3.0" + + +after_install = ["india_banking.install.after_install"] + +before_uninstall = "india_banking.uninstall.before_uninstall" + +doctype_js = { + "Payment Order": "public/js/payment_order.js", + "Purchase Invoice": "public/js/purchase_invoice.js", + "Payment Type": "public/js/payment_type.js", + "Bank Account": "public/js/bank_account.js", + "Payment Request": "public/js/payment_request.js", +} + +doctype_list_js = { + "Payment Order": "public/js/payment_order_list.js", +} + + +override_doctype_class = { + "Payment Order": "india_banking.overrides.payment_order.CustomPaymentOrder", + "Payment Entry": "india_banking.overrides.payment_entry.CustomPaymentEntry", + "Payment Request": "india_banking.overrides.payment_request.BankPaymentRequest", +} + +doc_events = { + "Bank": {"on_trash": "india_banking.india_banking.doc_events.bank.bank_on_trash"}, + "Bank Account": { + "validate": "india_banking.india_banking.doc_events.bank.validate_ifsc_code" + }, +} + +accounting_dimension_doctypes = [ + "Payment Order Reference", + "Payment Order Summary", +] + +scheduler_events = {"daily": ["india_banking.tasks.daily"]} + + # required_apps = [] # Includes in @@ -27,16 +67,6 @@ # include js in page # page_js = {"page" : "public/js/file.js"} -doctype_js = { - "Payment Order": "public/js/payment_order.js", - "Purchase Invoice": "public/js/purchase_invoice.js", - "Payment Type": "public/js/payment_type.js", - "Bank Account": "public/js/bank_account.js", - "Payment Request": "public/js/payment_request.js", -} - -doctype_list_js = {"Payment Order": "public/js/payment_order_list.js"} - # include js in doctype views # doctype_js = {"doctype" : "public/js/doctype.js"} # doctype_list_js = {"doctype" : "public/js/doctype_list.js"} @@ -79,9 +109,6 @@ # before_install = "india_banking.install.before_install" # after_install = "india_banking.install.after_install" -after_install = ["india_banking.install.after_install"] - -before_uninstall = "india_banking.uninstall.before_uninstall" # Uninstallation # ------------ @@ -131,12 +158,6 @@ # "ToDo": "custom_app.overrides.CustomToDo" # } -override_doctype_class = { - "Payment Order": "india_banking.overrides.payment_order.CustomPaymentOrder", - "Payment Entry": "india_banking.overrides.payment_entry.CustomPaymentEntry", - "Payment Request": "india_banking.overrides.payment_request.BankPaymentRequest", -} - # Document Events # --------------- @@ -150,18 +171,6 @@ # } # } -doc_events = { - "Bank": {"on_trash": "india_banking.india_banking.doc_events.bank.bank_on_trash"}, - "Bank Account": { - "validate": "india_banking.india_banking.doc_events.bank.validate_ifsc_code" - }, -} - - -accounting_dimension_doctypes = [ - "Payment Order Reference", - "Payment Order Summary", -] # Scheduled Tasks # --------------- @@ -184,8 +193,6 @@ # ], # } -scheduler_events = {"daily": ["india_banking.tasks.daily"]} - # Testing # ------- diff --git a/india_banking/india_banking/doc_events/payment_order.py b/india_banking/india_banking/doc_events/payment_order.py index d6af584..5ecf672 100644 --- a/india_banking/india_banking/doc_events/payment_order.py +++ b/india_banking/india_banking/doc_events/payment_order.py @@ -69,7 +69,7 @@ def process_payment_requests(payment_order_summary): failed_prs = [] for ref in payment_order_doc.references: - ref_key = tuple([ref.get(field, "") for field in summarise_field]) + ref_key = tuple([(ref.get(field, "") or "") for field in summarise_field]) if key == ref_key and ref.payment_request: failed_prs.append(ref.payment_request) @@ -638,24 +638,28 @@ def make_payment_entries(docname): pe.tax_withholding_category = row.tax_withholding_category for reference in payment_order_doc.references: if not reference.is_adhoc: - filter_condition = ( - reference.party_type == row.party_type - and reference.party == row.party - and reference.cost_center == row.cost_center - and reference.project == row.project - and reference.bank_account == row.bank_account - and reference.account == row.account - and reference.tax_withholding_category - == row.tax_withholding_category - and reference.reference_doctype == row.reference_doctype - ) - if payment_order_doc.summarise_payment_based_on != "Party": - filter_condition = filter_condition and ( - reference.reference_doctype == row.reference_doctype - and reference.reference_name == row.reference_name - ) - - if filter_condition: + filter_fields = [ + "party_type", + "party", + "bank_account", + "account", + "cost_center", + "project", + "tax_withholding_category", + "reference_doctype", + "reference_name", + ] + if payment_order_doc.summarise_payment_based_on == "Party": + filter_fields.remove("reference_name") + + filter_fields.extend(get_accounting_dimensions()) + + match_condition = [ + (reference.get(field, "") or "") == row.get(field, "") + for field in filter_fields + ] + + if all(match_condition): reference_amount = frappe.db.get_value( "Payment Request", reference.payment_request, diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index d6d60fe..4ad5436 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -181,7 +181,7 @@ def update_payment_status(self, cancel=False): if cancel: status = "Initiated" - if self.payment_order_type == "Bank Payment Request": + if self.payment_order_type == "Payment Request": ref_field = "status" ref_doc_field = frappe.scrub(self.payment_order_type) else: From b20ba2e94039c5e4224cc60a16d0682424bada32 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Sun, 5 Jan 2025 18:55:39 +0530 Subject: [PATCH 53/83] feat: set party field name for share holder and customer party type --- india_banking/overrides/payment_order.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index 4ad5436..3376abb 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -113,6 +113,10 @@ def get_party_field_name(self, party): return "supplier_name" elif party.party_type == "Employee": return "employee_name" + elif party.party_type == "Shareholder": + return "name" + elif party.part_type == "Customer": + return "customer_name" else: frappe.throw(f"Unsupported party type {party.party_type}") From 901ea7b8f4baf2e5d8c040b5d3647c53386bb134 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Sun, 5 Jan 2025 19:46:56 +0530 Subject: [PATCH 54/83] fix: reload doc once value update --- .../doctype/bank_connector/bank_connector.py | 2 -- india_banking/overrides/payment_order.py | 10 ++++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/india_banking/india_banking/doctype/bank_connector/bank_connector.py b/india_banking/india_banking/doctype/bank_connector/bank_connector.py index 7bf137a..be74d42 100644 --- a/india_banking/india_banking/doctype/bank_connector/bank_connector.py +++ b/india_banking/india_banking/doctype/bank_connector/bank_connector.py @@ -541,8 +541,6 @@ def generate_otp(self, payment_order): payment_order.update_unique_and_file_reference_id(save=True) payment_order.reload() - # return {"otp_required": True} # For testing response - # Generate OTP using POST request response = request.post( self.base_url, diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index 3376abb..60fb4c0 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -13,8 +13,8 @@ class CustomPaymentOrder(PaymentOrder): def before_submit(self): - self.update_unique_and_file_reference_id() self.validate_bank_payment_request() + self.update_unique_and_file_reference_id() def validate_bank_payment_request(self): if self.references: @@ -29,16 +29,14 @@ def validate_bank_payment_request(self): frappe.throw(title="Invalid Amount", msg=message) @frappe.whitelist() - def update_unique_and_file_reference_id(self, save=False): - unique_id = "".join(re.findall(r"[0-9a-zA-Z]", self.name)) - unique_id = unique_id[-10:] + def update_unique_and_file_reference_id(self): + unique_id = "".join(re.findall(r"[0-9a-zA-Z]", self.name))[-10:] frappe.db.set_value( "Payment Order", self.name, {"unique_id": unique_id, "file_reference_id": unique_id}, ) - if save: - frappe.db.commit() + self.reload() def validate(self): self.validate_summary() From 316ebd6f5874cd595db01991ca5ed850f029360b Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Sun, 5 Jan 2025 20:33:57 +0530 Subject: [PATCH 55/83] refactor: optimise 'validate_summary' method --- india_banking/overrides/payment_order.py | 86 +++++++++++------------- 1 file changed, 40 insertions(+), 46 deletions(-) diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index 60fb4c0..5f89dad 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -40,68 +40,62 @@ def update_unique_and_file_reference_id(self): def validate(self): self.validate_summary() - for payment_info in self.summary: - if ( - payment_info.mode_of_transfer == "RTGS" - and payment_info.amount >= 500000000 - ): + + def validate_summary(self): + if not self.summary: + frappe.throw("Please validate the summary") + + default_mode_of_transfer = ( + frappe.get_doc("Mode of Transfer", self.default_mode_of_transfer) + if self.default_mode_of_transfer + else None + ) + + summary_total = 0 + for payment in self.summary: + mode_of_transfer = ( + frappe.get_doc("Mode of Transfer", payment.mode_of_transfer) + if payment.mode_of_transfer + else default_mode_of_transfer + ) + + if mode_of_transfer.mode == "RTGS" and payment.amount >= 500000000: lei_number = frappe.db.get_value( - payment_info.party_type, payment_info.party, "lei_number" + payment.party_type, payment.party, "lei_number" ) if not lei_number: frappe.throw( - f"LEI Number required for payment > 50 Cr. For {payment_info.party_type} - {payment_info.party} - {payment_info.amount}" + f"LEI Number required for payment > 50 Cr. For {payment.party_type} - {payment.party} - {payment.amount}" ) - if ( - "A2A" in payment_info.mode_of_transfer - and payment_info.bank != self.company_bank - ): + + if "A2A" in mode_of_transfer.mode and payment.bank != self.company_bank: frappe.throw( - f"Invalid mode of transfer for {payment_info.party_type} - {payment_info.party} at row #{payment_info.idx}" + f"Invalid mode of transfer for {payment.party_type} - {payment.party} at row #{payment.idx}" ) - def validate_summary(self): - if len(self.summary) <= 0: - frappe.throw("Please validate the summary") + if not mode_of_transfer: + frappe.throw("Define a specific mode of transfer or a default one") - default_mode_of_transfer = None - if self.default_mode_of_transfer: - default_mode_of_transfer = frappe.get_doc( - "Mode of Transfer", self.default_mode_of_transfer - ) - - for payment in self.summary: - if payment.mode_of_transfer: - mode_of_transfer = frappe.get_doc( - "Mode of Transfer", payment.mode_of_transfer - ) - else: - if not default_mode_of_transfer: - frappe.throw("Define a specific mode of transfer or a default one") - mode_of_transfer = default_mode_of_transfer - payment.mode_of_transfer = default_mode_of_transfer.mode - - if ( - payment.amount < mode_of_transfer.minimum_limit - or payment.amount > mode_of_transfer.maximum_limit + if not ( + mode_of_transfer.minimum_limit + <= payment.amount + <= mode_of_transfer.maximum_limit ): frappe.throw( f"Mode of Transfer not suitable for {payment.party} for {payment.amount}. {mode_of_transfer.mode}: {mode_of_transfer.minimum_limit}-{mode_of_transfer.maximum_limit}" ) - summary_total = 0 + payment.mode_of_transfer = mode_of_transfer.mode + summary_total += payment.amount + references_total = 0 - for ref in self.references: - party_name_field = self.get_party_field_name(ref) - # update party name - ref.party_name = frappe.get_value( - ref.party_type, ref.party, party_name_field + for reference in self.references: + reference.party_name = frappe.get_value( + reference.party_type, + reference.party, + self.get_party_field_name(reference), ) - - references_total += ref.amount - - for sum in self.summary: - summary_total += sum.amount + references_total += reference.amount if summary_total != references_total: frappe.throw("Summary isn't matching the references") From 41dea6010c87b4f9b4e5b34a9cd809948a951dca Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 6 Jan 2025 01:16:06 +0530 Subject: [PATCH 56/83] refactor: optimise update payment status method refactor: removed unused function 'get_bank_entry_for_payroll' --- india_banking/install.py | 2 +- india_banking/overrides/journal_entry.py | 12 ++- india_banking/overrides/payment_order.py | 123 +++++++---------------- 3 files changed, 48 insertions(+), 89 deletions(-) diff --git a/india_banking/install.py b/india_banking/install.py index ac51ff8..aef6319 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -79,7 +79,7 @@ def create_journal_entry_custom_fields(): "label": "Payment Status", "fieldname": "payment_status", "fieldtype": "Select", - "options": "\nOrdered\nPaid\nFailed", + "options": "\nOrdered\nPayment Ordered\nPaid\nFailed", "no_copy": 1, "read_only": 1, "insert_after": "payment_details", diff --git a/india_banking/overrides/journal_entry.py b/india_banking/overrides/journal_entry.py index 5c95e03..0e06d33 100644 --- a/india_banking/overrides/journal_entry.py +++ b/india_banking/overrides/journal_entry.py @@ -45,7 +45,11 @@ def update_bank_entry(source, target): .where( (JournalEntryAccount.parent == source.name) & (JournalEntryAccount.party_type == "Employee") - & (JournalEntryAccount.payment_status.notin(["Paid", "Ordered"])) + & ( + JournalEntryAccount.payment_status.notin( + ["Paid", "Ordered", "Payment Ordered"] + ) + ) & (BankAccount.disabled == 0) & (BankAccount.is_default == 1) ) @@ -110,7 +114,11 @@ def get_bank_entry(doctype, txt, searchfield, start, page_len, filters, as_dict) .on(JournalEntry.name == JournalEntryAccount.parent) .where( (JournalEntry.docstatus == 1) - & (JournalEntryAccount.payment_status.notin(["Paid", "Ordered"])) + & ( + JournalEntryAccount.payment_status.notin( + ["Paid", "Ordered", "Payment Ordered"] + ) + ) & (JournalEntry.voucher_type == "Bank Entry") & (JournalEntryAccount.party_type == "Employee") & (JournalEntryAccount.against_account == filters.company_account) diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index 5f89dad..d7540f6 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -113,84 +113,61 @@ def get_party_field_name(self, party): frappe.throw(f"Unsupported party type {party.party_type}") def on_submit(self): - if self.payment_order_type == "Payment Request": - make_payment_entries(self.name) - frappe.db.set_value("Payment Order", self.name, "status", "Pending") + if self.payment_order_type in [ + "Payment Request", + "Payment Entry", + "Journal Entry", + ]: + if self.payment_order_type == "Payment Request": + make_payment_entries(self.name) - for ref in self.references: - if hasattr(ref, "payment_request"): - frappe.db.set_value( - "Payment Request", - ref.payment_request, - "status", - "Payment Ordered", - ) + self.update_payment_status() - if self.payment_order_type == "Journal Entry": - self.update_payemnt_status("submit") - - def update_payemnt_status(self, action=None): - order_status = "" - if action == "submit": - order_status = "Ordered" - elif action == "cancel": - order_status = "" - elif action == "Paid": - order_status = "Paid" - elif action == "Failed": - order_status = "Failed" - - for jea in self.summary: - frappe.db.set_value( - "Journal Entry Account", - jea.journal_entry_account, - "payment_status", - order_status, - ) + self.reload() def on_update_after_submit(self): frappe.throw("You cannot modify a payment order") - return - def before_cancel(self): - self.update_payemnt_status("cancel") - for summary_item in self.summary: - if summary_item.payment_status in ["Processed", "Initiated"]: + def on_cancel(self): + for summary in self.summary: + if summary.payment_status in ["Processed", "Initiated"]: frappe.throw( "You cannot cancel a payment order with Initiated/Processed payments" ) - return - for account in self.summary: - if ( - account.payment_status == "Processed" - or account.payment_status == "Initiated" - ): - frappe.throw("Cannot cancel a {} Order".format(account.payment_status)) + super().on_cancel() def on_trash(self): if self.docstatus == 1: frappe.throw("You cannot delete a payment order") - return def update_payment_status(self, cancel=False): - status = "Payment Ordered" - if cancel: - status = "Initiated" + self.db_set("status", "Pending") - if self.payment_order_type == "Payment Request": - ref_field = "status" - ref_doc_field = frappe.scrub(self.payment_order_type) - else: - ref_field = "payment_order_status" - ref_doc_field = "reference_name" - if self.payment_order_type not in [ - "Payment Entry", - "Journal Entry", - "Payroll Entry", - ]: + status = "Initiated" if cancel else "Payment Ordered" + + ref_field_map = { + "Payment Request": ("status", frappe.scrub(self.payment_order_type)), + "Payment Entry": ( + "payment_order_status", + frappe.scrub(self.payment_order_type), + ), + "Journal Entry": ( + "payment_status", + frappe.scrub(self.payment_order_type) + "_account", + ), + } + + ref_field, ref_doc_field = ref_field_map.get( + self.payment_order_type, (None, None) + ) + + if ref_field and ref_doc_field: for d in self.references: frappe.db.set_value( - self.payment_order_type, d.get(ref_doc_field), ref_field, status + self.payment_order_type + " Account", + d.get(ref_doc_field), + ref_field, + status, ) @@ -278,29 +255,3 @@ def get_mode_of_transfer(amount, party_bank, company_bank): ) return mode_of_transfer - - -def get_bank_entry_for_payroll(filters=None): - if filters and filters.get("refrence_name"): - condition = "AND jea.reference_name = '{0}'".format( - filters.get("refrence_name") - ) - - journal_entry = frappe.db.sql( - f""" - SELECT - je.name - FROM - `tabJournal Entry`je - JOIN - `tabJournal Entry Account`jea - ON - je.name = jea.parent - WHERE - je.docstatus != 2 AND jea.reference_type = 'Payroll Entry' AND je.voucher_type = 'Bank Entry' - {condition} LIMIT 1 - """, - as_dict=1, - ) - - return journal_entry if journal_entry else "" From ff3f27c04a8e67921150562935f9bf134301b236 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 6 Jan 2025 01:34:42 +0530 Subject: [PATCH 57/83] refactor: optimise payment entry py file --- india_banking/overrides/payment_entry.py | 67 ++++++++---------------- 1 file changed, 23 insertions(+), 44 deletions(-) diff --git a/india_banking/overrides/payment_entry.py b/india_banking/overrides/payment_entry.py index 29a8569..3d6137c 100644 --- a/india_banking/overrides/payment_entry.py +++ b/india_banking/overrides/payment_entry.py @@ -8,12 +8,7 @@ class CustomPaymentEntry(PaymentEntry): - def validate_duplicate_entry(self): - reference_names = [] - for d in self.get("references"): - reference_names.append( - (d.reference_doctype, d.reference_name, d.payment_term) - ) + pass @frappe.whitelist() @@ -25,9 +20,7 @@ def set_missing_values(source, target): target.company_bank_account = source.bank_account target.party = "" - account = "" - if source.paid_to: - account = source.paid_to + account = source.paid_to if source.paid_to else "" def _update_dimensions(source): return { @@ -45,52 +38,38 @@ def _get_default_bank_account(party_type, party): ) return party_bank_account - for count, reference in enumerate(source.references, 1): - reference = { - "reference_doctype": reference.reference_doctype, - "reference_name": reference.reference_name, - "amount": reference.allocated_amount, + def _get_reference_data(reference=None): + return { + "reference_doctype": reference.reference_doctype + if reference + else "Payment Entry", + "reference_name": reference.reference_name + if reference + else source.name, + "amount": reference.allocated_amount + if reference + else source.paid_amount, "party_type": source.party_type, "party": source.party, - "mode_of_payment": source.mode_of_payment, + "mode_of_payment": source.mode_of_payment + if reference + else "Wire Transfer", "bank_account": _get_default_bank_account( source.party_type, source.party ), - "account": account, + "account": account if reference else source.paid_to, "cost_center": source.cost_center, "project": source.project, "payment_entry": source.name, + **_update_dimensions(source), } - reference.update(_update_dimensions(source)) - target.append( - "references", - reference, - ) - if count == len(source.references): - break - else: - reference = { - "reference_doctype": "Payment Entry", - "reference_name": source.name, - "amount": source.paid_amount, - "party_type": source.party_type, - "party": source.party, - "mode_of_payment": "Wire Transfer", - "bank_account": _get_default_bank_account( - source.party_type, source.party - ), - "account": source.paid_to, - "cost_center": source.cost_center, - "project": source.project, - "payment_entry": source.name, - } - reference.update(_update_dimensions(source)) + for reference in source.references: + target.append("references", _get_reference_data(reference)) + + if not source.references: + target.append("references", _get_reference_data()) - target.append( - "references", - reference, - ) target.status = "Pending" doclist = get_mapped_doc( From 418623b4c214410b1c1ac84ebd2425bd9c21c4ed Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 6 Jan 2025 01:49:34 +0530 Subject: [PATCH 58/83] refactor: optimise payment request python code --- india_banking/overrides/payment_request.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/india_banking/overrides/payment_request.py b/india_banking/overrides/payment_request.py index cc3f3e5..d2e3977 100644 --- a/india_banking/overrides/payment_request.py +++ b/india_banking/overrides/payment_request.py @@ -14,31 +14,23 @@ class BankPaymentRequest(PaymentRequest): def validate(self): if not self.net_total: self.net_total = self.grand_total + if ( self.apply_tax_withholding_amount and self.tax_withholding_category and self.payment_request_type == "Outward" ): tds_amount = self.calculate_pr_tds(self.net_total) - self.taxes_deducted = tds_amount self.grand_total = self.net_total - self.taxes_deducted else: - if self.net_total and not self.grand_total: - self.grand_total = self.net_total - if ( - self.grand_total - and self.net_total != self.grand_total - and not self.apply_tax_withholding_amount - ): - self.grand_total = self.net_total + self.grand_total = self.net_total or self.grand_total if not self.is_adhoc: super().validate() else: if self.is_new(): self.status = "Draft" - if self.reference_doctype or self.reference_name: frappe.throw("Payments with references cannot be marked as ad-hoc") From f706ad06ce89ea1fc9d0af2d84bcd15b07ee45c8 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 6 Jan 2025 09:43:16 +0530 Subject: [PATCH 59/83] refactor: add translate to all message refactor: optimise payment request overrides code --- india_banking/overrides/payment_request.py | 31 +++++++++------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/india_banking/overrides/payment_request.py b/india_banking/overrides/payment_request.py index d2e3977..7a00027 100644 --- a/india_banking/overrides/payment_request.py +++ b/india_banking/overrides/payment_request.py @@ -32,7 +32,7 @@ def validate(self): if self.is_new(): self.status = "Draft" if self.reference_doctype or self.reference_name: - frappe.throw("Payments with references cannot be marked as ad-hoc") + frappe.throw(_("Payments with references cannot be marked as ad-hoc")) if self.remarks: self.remarks = self.remarks[:48] @@ -41,10 +41,9 @@ def validate(self): def on_submit(self): if not self.grand_total: - frappe.throw("Amount cannot be zero") + frappe.throw(_("Amount cannot be zero")) bank_account = get_party_bank_account(self.party_type, self.party) - if not bank_account: frappe.throw( _("Default Bank Account is missing for {0} - {1}").format( @@ -52,35 +51,29 @@ def on_submit(self): ) ) - debit_account = None - if self.payment_type: - debit_account = frappe.db.get_value( - "Payment Type", self.payment_type, "account" - ) - elif self.reference_doctype == "Purchase Invoice": - debit_account = frappe.db.get_value( - self.reference_doctype, self.reference_name, "credit_to" - ) + debit_account = frappe.db.get_value( + "Payment Type", self.payment_type, "account" + ) or frappe.db.get_value( + self.reference_doctype, self.reference_name, "credit_to" + ) if not debit_account: frappe.throw( - "Debit account for Payment Type {} cannot be determined".format( + _("Debit account for Payment Type {} cannot be determined").format( self.payment_type or "" ) ) + if not self.is_adhoc: super().on_submit() else: if self.payment_request_type == "Outward": self.db_set("status", "Initiated") - return def create_payment_entry(self, submit=True): payment_entry = super().create_payment_entry(submit=submit) if payment_entry.docstatus != 1 and self.payment_type: - payment_entry.paid_to = ( - frappe.db.get_value("Payment Type", self.payment_type, "account") or "" - ) + payment_entry.paid_to = frappe.db.get_value("Payment Type", self.payment_type, "account") or "" return payment_entry @@ -113,9 +106,9 @@ def valdidate_bank_for_wire_transfer(self): "India Banking Settings" ).activate_workflow_on_bank_account ): - frappe.throw("Cannot proceed with un-approved bank account") + frappe.throw(_("Cannot proceed with un-approved bank account")) except Exception: - frappe.throw("Workflow Not Found for Bank Account") + frappe.throw(_("Workflow Not Found for Bank Account")) @frappe.whitelist() From bf6d5c5e6a776370659fc16c41875f1cbfefa9cb Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 6 Jan 2025 09:51:51 +0530 Subject: [PATCH 60/83] fix: while process payment request summarise field extend with payment entry and journal entry account --- india_banking/india_banking/doc_events/payment_order.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/india_banking/india_banking/doc_events/payment_order.py b/india_banking/india_banking/doc_events/payment_order.py index 5ecf672..421c157 100644 --- a/india_banking/india_banking/doc_events/payment_order.py +++ b/india_banking/india_banking/doc_events/payment_order.py @@ -60,6 +60,8 @@ def process_payment_requests(payment_order_summary): "tax_withholding_category", "reference_doctype", "reference_name", + "payment_entry", + "journal_entry_account", ] if payment_order_doc.summarise_payment_based_on == "Party": summarise_field.remove("reference_name") From 336b5cf24c347d714f0623632c93bf64eb3c392d Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 6 Jan 2025 10:23:50 +0530 Subject: [PATCH 61/83] refactor: move "get_bank_balance" function in bank connector --- .../doctype/bank_connector/bank_connector.py | 37 ++++++++++ india_banking/public/js/bank_account.js | 69 ++++++++++--------- 2 files changed, 74 insertions(+), 32 deletions(-) diff --git a/india_banking/india_banking/doctype/bank_connector/bank_connector.py b/india_banking/india_banking/doctype/bank_connector/bank_connector.py index be74d42..8074e07 100644 --- a/india_banking/india_banking/doctype/bank_connector/bank_connector.py +++ b/india_banking/india_banking/doctype/bank_connector/bank_connector.py @@ -682,6 +682,36 @@ def notify_party(self, summary_row): "Payment Email Notification Failed", frappe.get_traceback() ) + def get_bank_balance(self, bank_account): + payload = { + "bank_account_number": bank_account.bank_account_no, + "method": "bank_balance", + } + response = request.post( + self.base_url, headers=self.headers, data=json.dumps(payload) + ) + # create api request log + create_api_log( + response, "Get Payment Status", "Bank Account", bank_account.bank_account_no + ) + + if response.status_code == 200: + response_details = self.get_response_details(response) + if response_details.get("server_status") == "Success": + if response_details.balance or response_details.balance == 0: + frappe.db.set_value( + "Bank Account", + bank_account.name, + "bank_balance", + response_details.balance, + ) + else: + frappe.msgprint( + title=_("API Failed"), + msg=_("Balance Fetch Failed"), + indicator="red", + ) + def get_bank_connector(bank_account, company): # Fetch the connector information @@ -714,3 +744,10 @@ def get_payment_status(payment_order): payment_order.company_bank_account, payment_order.company ) return bank_connector.get_payment_status(payment_order) + + +@frappe.whitelist() +def get_bank_balance(bank_account_name): + bank_doc = frappe.get_doc("Bank Account", bank_account_name) + bank_connector = get_bank_connector(bank_account_name, bank_doc.company) + return bank_connector.get_bank_balance(bank_doc) diff --git a/india_banking/public/js/bank_account.js b/india_banking/public/js/bank_account.js index cd308b6..3a920ba 100644 --- a/india_banking/public/js/bank_account.js +++ b/india_banking/public/js/bank_account.js @@ -1,32 +1,37 @@ -frappe.ui.form.on('Bank Account', { - refresh(frm) { - if(frm.doc.is_company_account && frm.doc.is_company_account && !frm.doc.disabled){ - frm.add_custom_button(__('Fetch Balance'), function() { - frappe.call({ - method: "india_banking.india_banking.doc_events.payment_order.get_bank_balance", - freeze: true, - args: { - bank_name: frm.doc.name - }, - callback: (res)=>{ - cur_frm.reload_doc() - } - }) - }); - } - if (frm.doc.workflow_state == 'Approved') { - frm.set_read_only(); - } - }, - onload(frm){ - if (frm.doc.workflow_state == 'Approved') { - frm.set_read_only(); - } - }, - after_workflow_action: function (frm) { - if (frm.doc.workflow_state == 'Approved') { - frm.set_read_only(); - } - frm.reload_doc(); - }, -}); \ No newline at end of file +frappe.ui.form.on("Bank Account", { + refresh(frm) { + if ( + frm.doc.is_company_account && + frm.doc.is_company_account && + !frm.doc.disabled + ) { + frm.add_custom_button(__("Fetch Balance"), function () { + frappe.call({ + method: + "india_banking.india_banking.doctype.bank_connector.bank_connector.get_bank_balance", + freeze: true, + args: { + bank_name: frm.doc.name, + }, + callback: (res) => { + cur_frm.reload_doc(); + }, + }); + }); + } + if (frm.doc.workflow_state == "Approved") { + frm.set_read_only(); + } + }, + onload(frm) { + if (frm.doc.workflow_state == "Approved") { + frm.set_read_only(); + } + }, + after_workflow_action: function (frm) { + if (frm.doc.workflow_state == "Approved") { + frm.set_read_only(); + } + frm.reload_doc(); + }, +}); From a7b871b526d5c05b096104b3226090712aec96e8 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 6 Jan 2025 10:24:58 +0530 Subject: [PATCH 62/83] refactor: remove unused function from payment_order python file --- .../india_banking/doc_events/payment_order.py | 1078 ----------------- 1 file changed, 1078 deletions(-) diff --git a/india_banking/india_banking/doc_events/payment_order.py b/india_banking/india_banking/doc_events/payment_order.py index 421c157..d637a62 100644 --- a/india_banking/india_banking/doc_events/payment_order.py +++ b/india_banking/india_banking/doc_events/payment_order.py @@ -1,21 +1,9 @@ -import json - import frappe import frappe.utils -import requests from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, ) -from erpnext.accounts.doctype.payment_entry.payment_entry import get_split_invoice_rows from frappe import _, parse_json -from frappe.utils import nowdate -from frappe.utils.data import comma_and, cstr - -from india_banking.india_banking.doctype.india_banking_request_log.india_banking_request_log import ( - create_api_log, -) -from india_banking.install import STD_BANK_LIST -from india_banking.utils import get_bank_address_details @frappe.whitelist() @@ -82,1069 +70,3 @@ def process_payment_requests(payment_order_summary): pr_doc.check_if_payment_entry_exists() pr_doc.set_as_cancelled() pr_doc.db_set("docstatus", 2) - - -@frappe.whitelist() -def get_bank_balance(bank_name): - bank_doc = frappe.get_doc("Bank Account", bank_name) - - bank_connector_exists = frappe.db.exists( - "Bank Connector", {"company": bank_doc.company, "bank": bank_doc.bank} - ) - - if not bank_connector_exists: - frappe.throw("Bank Connector is not initialized") - - bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - - app_name = frappe._dict(get_bank_info(bank_doc.bank)).app_name - - if bank_connector.bank == "ICICI Bank" and not bank_connector.bulk_transaction: - app_name += "_composite" - - url = f"{bank_connector.url}/api/method/{app_name}.{app_name}.doctype.bank_request_log.bank_request_log.get_bank_balance" - - api_key = bank_connector.api_key - api_secret = bank_connector.get_password("api_secret") - headers = { - "Authorization": f"token {api_key}:{api_secret}", - "Content-Type": "application/json", - } - - payload = json.dumps({"bank_account_number": bank_doc.bank_account_no}) - - response = requests.request("POST", url, headers=headers, data=payload) - - # create api request log - create_api_log(response, "Get Bank Balance", "Bank Account", bank_doc.name) - - if response.status_code == 200: - response = json.loads(response.text) - response_data = frappe._dict((response.get("message") or {})) - if response_data.get("server_status") == "Success": - if response_data.balance or response_data.balance == 0: - frappe.db.set_value( - "Bank Account", bank_doc.name, "bank_balance", response_data.balance - ) - else: - frappe.msgprint( - title="API Failed", msg="Balance Fetch Failed", indicator="red" - ) - - -@frappe.whitelist() -def generate_payment_otp(docname): - payment_order_doc = frappe.get_doc("Payment Order", docname) - payment_order_doc.update_unique_and_file_reference_id(save=True) - - payment_order_doc.reload() - - # Fetch the connector information - bank_connector_exists = frappe.db.exists( - "Bank Connector", - { - "company": payment_order_doc.company, - "bank_account": payment_order_doc.company_bank_account, - }, - ) - - if not bank_connector_exists: - frappe.throw("Bank Connector is not initialized") - - bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - - payment_payload = frappe._dict() - - # payload reference to this payment. - payment_payload["doc"] = payment_order_doc.as_dict(convert_dates_to_str=True) - - if not payment_order_doc.company_account_number: - frappe.throw("Source bank account number is missing") - - url = f"{bank_connector.url}/api/method/india_banking_connector.api.connect" - payment_payload.method = "generate_otp" - payment_payload.bulk_transaction = bank_connector.bulk_transaction - - api_key = bank_connector.api_key - api_secret = bank_connector.get_password("api_secret") - headers = { - "Authorization": f"token {api_key}:{api_secret}", - "Content-Type": "application/json", - } - response = requests.request( - "POST", url, headers=headers, data=json.dumps(payment_payload) - ) - - # create api response log - create_api_log( - response, "Generate Otp", payment_order_doc.doctype, payment_order_doc.name - ) - - if response.ok: - response_details = response.json().get("message") - if response_details.get("status") == "success": - frappe.msgprint(response_details.get("message"), alert=1, indicator="green") - else: - frappe.msgprint(response_details.get("message"), alert=1, indicator="red") - else: - frappe.throw("Invalid Request") - - -@frappe.whitelist() -def make_bank_payment(docname, otp=None): - if not frappe.has_permission("Payment Order", "write"): - frappe.throw("Not permitted", frappe.PermissionError) - - payment_order_doc = frappe.get_doc("Payment Order", docname) - - # Fetch the connector information - bank_connector_exists = frappe.db.exists( - "Bank Connector", - { - "company": payment_order_doc.company, - "bank_account": payment_order_doc.company_bank_account, - }, - ) - - if not bank_connector_exists: - frappe.throw("Bank Connector is not initialized") - - bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - - if ( - payment_order_doc.company_bank == "ICICI Bank" - and bank_connector.bulk_transaction - and not otp - ): - frappe.throw(title="Invalid OTP", msg="Cannot Initiate Payment without OTP") - - if ( - payment_order_doc.company_bank == "ICICI Bank" - and bank_connector.bulk_transaction - ): - payment_response = process_bulk_payment(payment_order_doc, otp) - - if payment_response.get("status") == "ACCEPTED": - frappe.db.set_value("Payment Order", docname, "status", "Initiated") - frappe.db.set_value( - "Payment Order", - docname, - "file_sequence_number", - payment_response.get("file_sequence_number"), - ) - - for row in payment_order_doc.summary: - frappe.db.set_value( - "Payment Order Summary", row.name, "payment_initiated", 1 - ) - frappe.db.set_value( - "Payment Order Summary", row.name, "payment_status", "Initiated" - ) - frappe.db.set_value( - "Payment Order Summary", row.name, "payment_date", nowdate() - ) - - elif payment_response.get("status") == "Failed": - return {"message": "Failed - " + cstr(payment_response.get("message"))} - - return {"message": "Payment Initiated"} - - else: - count = 0 - for i in payment_order_doc.summary: - if not i.payment_initiated and i.payment_status == "Pending": - payment_response = process_payment(i, payment_order_doc) - - if ( - payment_response - and "payment_status" in payment_response - and payment_response["payment_status"] == "Initiated" - ): - frappe.db.set_value( - "Payment Order Summary", i.name, "payment_initiated", 1 - ) - frappe.db.set_value( - "Payment Order Summary", i.name, "payment_status", "Initiated" - ) - frappe.db.set_value( - "Payment Order Summary", i.name, "payment_date", nowdate() - ) - count += 1 - elif ( - payment_response - and "payment_status" in payment_response - and payment_response["payment_status"] == "" - ): - if "message" in payment_response: - frappe.db.set_value( - "Payment Order Summary", - i.name, - "message", - payment_response["message"], - ) - else: - frappe.db.set_value( - "Payment Order Summary", i.name, "payment_status", "Failed" - ) - payment_entry_doc = frappe.get_doc("Payment Entry", i.payment_entry) - if payment_entry_doc.docstatus == 1: - payment_entry_doc.cancel() - - process_bank_payment_requests(i.name) - - if payment_response and "message" in payment_response: - frappe.db.set_value( - "Payment Order Summary", - i.name, - "message", - payment_response["message"], - ) - - payment_order_doc.reload() - processed_count = 0 - for i in payment_order_doc.summary: - if i.payment_initiated: - processed_count += 1 - - if processed_count == len(payment_order_doc.summary): - frappe.db.set_value("Payment Order", docname, "status", "Initiated") - - return {"message": f"{count} payments initiated"} - - -def process_bulk_payment(payment_order_doc, otp): - # Fetch the connector information - bank_connector_exists = frappe.db.exists( - "Bank Connector", - { - "company": payment_order_doc.company, - "bank_account": payment_order_doc.company_bank_account, - }, - ) - - if not bank_connector_exists: - frappe.throw("Bank Connector is not initialized") - - bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - - payment_payload = frappe._dict() - - # payment payload. - payment_payload["doc"] = payment_order_doc.as_dict(convert_dates_to_str=True) - payment_payload["doc"]["otp"] = otp - payment_payload.method = "make_payment" - payment_payload.bulk_transaction = bank_connector.bulk_transaction - - payment_account_list = [] - - # Lei number validation - for ref in payment_order_doc.summary: - if ref.mode_of_transfer == "RTGS" and ref.amount >= 500000000: - lei_number = frappe.db.get_value(ref.party_type, ref.party, "lei_number") - payment_account_list.append(ref.account_name + "-" + lei_number) - if not lei_number: - frappe.throw("LEI Number required for payment > 50 Cr") - else: - payment_account_list.append(ref.account_name + "-" + ref.bank_account_no) - - payment_payload["doc"][ - "desc" - ] = f"Payment to {comma_and(payment_account_list)} via {payment_order_doc.name}" - - if not payment_order_doc.company_account_number: - frappe.throw("Source bank account number is missing") - - url = f"{bank_connector.url}/api/method/india_banking_connector.api.connect" - - api_key = bank_connector.api_key - api_secret = bank_connector.get_password("api_secret") - headers = { - "Authorization": f"token {api_key}:{api_secret}", - "Content-Type": "application/json", - } - - response = requests.request( - "POST", url, headers=headers, data=json.dumps(payment_payload) - ) - - # create api request log - create_api_log( - response, "Make Payment", payment_order_doc.doctype, payment_order_doc.name - ) - - if response.ok: - payment_details = response.json() - return payment_details.get("message") - - frappe.throw("Invalid payment request") - - -@frappe.whitelist() -def get_bulk_payment_status(payment_order_doc): - bank_connector_exists = frappe.db.exists( - "Bank Connector", - { - "company": payment_order_doc.company, - "bank_account": payment_order_doc.company_bank_account, - }, - ) - - if not bank_connector_exists: - frappe.throw("Bank Connector is not initialized") - - bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - - payment_payload = frappe._dict() - - # payload reference to get payment status - payment_payload["doc"] = payment_order_doc.as_dict(convert_dates_to_str=True) - payment_payload.method = "get_payment_status" - payment_payload.bulk_transaction = bank_connector.bulk_transaction - - url = f"{bank_connector.url}/api/method/india_banking_connector.api.connect" - - api_key = bank_connector.api_key - api_secret = bank_connector.get_password("api_secret") - headers = { - "Authorization": f"token {api_key}:{api_secret}", - "Content-Type": "application/json", - } - - response = requests.request( - "POST", url, headers=headers, data=json.dumps(payment_payload) - ) - - # create api request log - create_api_log( - response, - "Get Payment Status", - payment_order_doc.doctype, - payment_order_doc.name, - ) - - if response.ok: - response_details = frappe._dict(response.json().get("message", {})) - payment_status_details = response_details.payment_status_details - if response_details.get("status") == "Processed": - frappe.msgprint( - response_details.get("message"), response_details.get("file_status") - ) - fs = response_details.get("file_status") - if response_details.get("file_status") in ["FAL", "REJ", "REC"]: - for row in payment_order_doc.summary: - frappe.db.set_value( - "Payment Order Summary", - row.name, - "payment_status", - "Failed" if fs == "FAL" else "Rejected", - ) - payment_entry_doc = frappe.get_doc( - "Payment Entry", row.payment_entry - ) - if payment_entry_doc.docstatus == 1: - payment_entry_doc.cancel() - process_bank_payment_requests(row.name) - - if payment_status_details: - for row in payment_order_doc.summary: - row_payment_status = frappe._dict( - payment_status_details.get(row.name, {}) - ) - if row.payment_status == "Initiated" and row_payment_status: - if row_payment_status.transaction_status == "SUC": - frappe.db.set_value( - "Payment Order Summary", - row.name, - { - "reference_number": row_payment_status.host_reference_number, - "payment_status": "Processed", - "message": row_payment_status.host_response_message, - }, - ) - frappe.db.set_value( - "Payment Entry", - row.payment_entry, - "reference_no", - row_payment_status.host_reference_number, - ) - elif row_payment_status.transaction_status == "FAL": - frappe.db.set_value( - "Payment Order Summary", - row.name, - "payment_status", - "Failed", - ) - payment_entry_doc = frappe.get_doc( - "Payment Entry", row.payment_entry - ) - if payment_entry_doc.docstatus == 1: - payment_entry_doc.cancel() - process_bank_payment_requests(row.name) - - elif row_payment_status.transaction_status in ["RVS", "REJ"]: - frappe.db.set_value( - "Payment Order Summary", - row.name, - "payment_status", - "Rejected", - ) - payment_entry_doc = frappe.get_doc( - "Payment Entry", row.payment_entry - ) - if payment_entry_doc.docstatus == 1: - payment_entry_doc.cancel() - process_bank_payment_requests(row.name) - - update_payment_status(payment_order_doc) - else: - frappe.throw(msg=response_details.server_message, title="Failed") - else: - frappe.throw("Invalid Request") - - -def update_payment_status(payment_order_doc): - try: - success_count = 0 - faild_count = 0 - rejected_count = 0 - for ref in payment_order_doc.summary: - status = frappe.db.get_value( - "Payment Order Summary", ref.name, "payment_status" - ) - if status == "Processed": - success_count += 1 - if status == "Failed": - faild_count += 1 - if status == "Rejected": - rejected_count += 1 - - if success_count == len(payment_order_doc.summary): - frappe.db.set_value( - "Payment Order", payment_order_doc.name, "status", "Approved" - ) - - elif faild_count == len(payment_order_doc.summary): - frappe.db.set_value( - "Payment Order", payment_order_doc.name, "status", "Failed" - ) - elif rejected_count == len(payment_order_doc.summary): - frappe.db.set_value( - "Payment Order", payment_order_doc.name, "status", "Rejected" - ) - elif success_count > 1 and success_count + faild_count + rejected_count == len( - payment_order_doc.summary - ): - frappe.db.set_value( - "Payment Order", payment_order_doc.name, "status", "Partially Approved" - ) - except: - frappe.log_error( - title="Payment Order Status Update Error", message=frappe.get_traceback() - ) - - -@frappe.whitelist() -def get_payment_status(docname): - payment_order_doc = frappe.get_doc("Payment Order", docname) - - # Fetch the connector information - bank_connector_exists = frappe.db.exists( - "Bank Connector", - { - "company": payment_order_doc.company, - "bank_account": payment_order_doc.company_bank_account, - }, - ) - - if not bank_connector_exists: - frappe.throw("Bank Connector is not initialized") - - bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - - if ( - payment_order_doc.company_bank == "ICICI Bank" - and bank_connector.bulk_transaction - ): - get_bulk_payment_status(payment_order_doc) - - else: - for i in payment_order_doc.summary: - if i.payment_status == "Initiated": - get_response( - i, payment_order_doc.company_bank_account, payment_order_doc.company - ) - - payment_order_doc.reload() - update_payment_status(payment_order_doc) - - -@frappe.whitelist() -def make_payment_entries(docname): - payment_order_doc = frappe.get_doc("Payment Order", docname) - """create entry""" - frappe.flags.ignore_account_permission = True - - for row in payment_order_doc.summary: - pe = frappe.new_doc("Payment Entry") - pe.payment_type = "Pay" - pe.payment_entry_type = "Pay" - pe.company = payment_order_doc.company - pe.cost_center = row.cost_center - pe.project = row.project - pe.posting_date = nowdate() - pe.mode_of_payment = "Wire Transfer" - pe.party_type = row.party_type - pe.party = row.party - pe.bank_account = payment_order_doc.company_bank_account - pe.party_bank_account = row.bank_account - if pe.party_type == "Supplier": - pe.ensure_supplier_is_not_blocked() - pe.payment_order = payment_order_doc.name - - pe.paid_from = payment_order_doc.account - if row.account: - pe.paid_to = row.account - pe.paid_from_account_currency = "INR" - pe.paid_to_account_currency = "INR" - pe.paid_amount = row.amount - pe.received_amount = row.amount - pe.letter_head = frappe.db.get_value("Letter Head", {"is_default": 1}, "name") - pe.source_doctype = payment_order_doc.payment_order_type - - for dimension in get_accounting_dimensions(): - pe.update({dimension: payment_order_doc.get(dimension, "")}) - - if row.tax_withholding_category: - net_total = 0 - - for reference in payment_order_doc.references: - if ( - reference.party_type == row.party_type - and reference.party == row.party - and reference.cost_center == row.cost_center - and reference.project == row.project - and reference.bank_account == row.bank_account - and reference.account == row.account - and reference.tax_withholding_category - == row.tax_withholding_category - and reference.reference_doctype == row.reference_doctype - ): - net_total += frappe.db.get_value( - "Payment Request", - reference.payment_request, - "net_total", - ) - pe.paid_amount = net_total - pe.received_amount = net_total - pe.apply_tax_withholding_amount = 1 - pe.tax_withholding_category = row.tax_withholding_category - for reference in payment_order_doc.references: - if not reference.is_adhoc: - filter_fields = [ - "party_type", - "party", - "bank_account", - "account", - "cost_center", - "project", - "tax_withholding_category", - "reference_doctype", - "reference_name", - ] - if payment_order_doc.summarise_payment_based_on == "Party": - filter_fields.remove("reference_name") - - filter_fields.extend(get_accounting_dimensions()) - - match_condition = [ - (reference.get(field, "") or "") == row.get(field, "") - for field in filter_fields - ] - - if all(match_condition): - reference_amount = frappe.db.get_value( - "Payment Request", - reference.payment_request, - "net_total", - ) - payment_term = "" - try: - payment_term = frappe.db.get_value( - "Payment Request", - reference.payment_request, - "payment_term", - ) - - if not payment_term: - if template := frappe.db.get_value( - reference.reference_doctype, - reference.reference_name, - "payment_terms_template", - ): - splited_invoice_rows = get_split_invoice_rows( - frappe._dict( - {"voucher_no": reference.reference_name} - ), - template, - exc_rates={ - reference.reference_name: frappe.get_doc( - "Purchase Invoice", reference.reference_name - ) - }, - ) - - is_term_applied = frappe.db.get_value( - "Payment Terms Template", - template, - "allocate_payment_based_on_payment_terms", - ) - - if splited_invoice_rows and is_term_applied: - term_row = 0 - while reference_amount > 0: - term_paid = ( - frappe.get_value( - "Payment Entry Reference", - { - "reference_doctype": reference.reference_doctype, - "reference_name": reference.reference_name, - "payment_term": splited_invoice_rows[ - term_row - ].get("payment_term"), - "docstatus": 1, - }, - "sum(allocated_amount)", - ) - or 0 - ) - - per = ( - frappe.db.get_value( - "Payment Term", - splited_invoice_rows[term_row].get( - "payment_term" - ), - "invoice_portion", - ) - / 100 - ) - invoice_amount = frappe.db.get_value( - reference.reference_doctype, - reference.reference_name, - "grand_total", - ) - to_be_pay = per * invoice_amount - - if (reference_amount + term_paid) <= to_be_pay: - paid_amount = reference_amount - reference_amount -= paid_amount - else: - paid_amount = to_be_pay - term_paid - reference_amount -= paid_amount - - if paid_amount: - pe.append( - "references", - { - "reference_doctype": reference.reference_doctype, - "reference_name": reference.reference_name, - "total_amount": invoice_amount, - "allocated_amount": paid_amount, - "payment_term": splited_invoice_rows[ - term_row - ].get("payment_term"), - }, - ) - term_row += 1 - else: - pe.append( - "references", - { - "reference_doctype": reference.reference_doctype, - "reference_name": reference.reference_name, - "total_amount": reference_amount, - "allocated_amount": reference_amount, - }, - ) - else: - pe.append( - "references", - { - "reference_doctype": reference.reference_doctype, - "reference_name": reference.reference_name, - "total_amount": reference_amount, - "allocated_amount": reference_amount, - }, - ) - else: - pe.append( - "references", - { - "reference_doctype": reference.reference_doctype, - "reference_name": reference.reference_name, - "total_amount": reference_amount, - "allocated_amount": reference_amount, - "payment_term": payment_term, - }, - ) - except: - frappe.log_error( - "Error in Payment Terms Template", frappe.get_traceback() - ) - - pe.update( - { - "reference_no": payment_order_doc.name, - "reference_date": nowdate(), - "remarks": "Payment Entry from Payment Order - {0}".format( - payment_order_doc.name - ), - } - ) - pe.setup_party_account_field() - pe.set_missing_values() - pe.validate() - group_by_invoices(pe) - pe.insert(ignore_permissions=True, ignore_mandatory=True) - pe.submit() - frappe.db.set_value("Payment Order Summary", row.name, "payment_entry", pe.name) - - -def group_by_invoices(self): - grouped_references = {} - if self.references: - for ref in self.references: - key = (ref.reference_name, ref.reference_doctype, ref.payment_term) - if key not in grouped_references: - grouped_references[key] = ref - else: - grouped_references[key].allocated_amount += ref.allocated_amount - - self.references = list(grouped_references.values()) - - -def process_payment(payment_info, payment_order_doc): - # Fetch the connector information - bank_connector_exists = frappe.db.exists( - "Bank Connector", - { - "company": payment_order_doc.company, - "bank_account": payment_order_doc.company_bank_account, - }, - ) - - if not bank_connector_exists: - frappe.throw("Bank Connector is not initialized") - - bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - - payment_payload = frappe._dict(payment_info.as_dict(convert_dates_to_str=True)) - - party_field_name = ( - "supplier_name" if payment_info.party_type == "Supplier" else "employee_name" - ) - - party_name = frappe.db.get_value( - payment_info.party_type, payment_info.party, party_field_name - ) - - payment_payload.party_name = party_name - payment_payload.desc = f"Payment to {payment_info.party} via {payment_info.parent}" - - party_address = get_bank_address_details(payment_info.bank_account) - bank_link = frappe.utils.get_link_to_form("Bank Account", payment_info.bank_account) - if not party_address: - frappe.throw( - f"Address not found for the selected bank account {bank_link} at Row #{payment_info.idx}" - ) - - payment_payload.address = json.dumps(party_address) - - payment_payload.doc = payment_order_doc.as_dict(convert_dates_to_str=True) - - if not payment_order_doc.company_account_number: - frappe.throw("Source bank account number is missing") - - url = f"{bank_connector.url}/api/method/india_banking_connector.api.connect" - - api_key = bank_connector.api_key - api_secret = bank_connector.get_password("api_secret") - headers = { - "Authorization": f"token {api_key}:{api_secret}", - "Content-Type": "application/json", - } - payment_payload.method = "intiate_payment" - - response = requests.request( - "POST", url, headers=headers, data=json.dumps(payment_payload) - ) - - # create api request log - create_api_log( - response, "Make Payment", payment_info.parenttype, payment_info.parent - ) - - if response.status_code == 200: - response = json.loads(response.text) - response_data = frappe._dict((response.get("message") or {})) - - if not response_data.status: - return {"payment_status": "", "message": str(response)} - - elif response_data.status == "ACCEPTED": - return {"payment_status": "Initiated", "message": response_data.message} - - elif response_data.status == "Request Failure": - return {"payment_status": "", "message": "Request Failure"} - - else: - return {"payment_status": "Failed", "message": response_data.message} - else: - return {"payment_status": "", "message": ""} - - -def get_bank_info(bank_name): - for bank in STD_BANK_LIST: - if bank["bank_name"] == bank_name: - return bank - return {} - - -def notify_party(payment_info, response_data): - if not frappe.get_value( - "India Banking Settings", "India Banking Settings", "notify_party" - ): - return - if payment_info.payment_entry: - default_email_format = ( - frappe.get_single("India Banking Settings").default_email_format - or "Payment Advice" - ) - if default_email_format: - try: - payment_entry = frappe.get_doc( - "Payment Entry", payment_info.payment_entry - ) - frappe.sendmail( - recipients=[ - payment_info.email - or frappe.db.get_value( - "Bank Account", payment_info.bank_account, "email" - ) - ], - subject="Payment Notification", - message="Payment for {0} is completed. Please check the attachment for details".format( - payment_info.party - ), - attachments=[ - { - "fname": "payment_details.pdf", - "fcontent": frappe.get_print( - "Payment Entry", - payment_entry.name, - default_email_format, - as_pdf=True, - ), - } - ], - ) - except Exception as e: - frappe.log_error( - "Payment Email Notification Failed", frappe.get_traceback() - ) - - -def get_response(payment_info, company_bank_account, company): - payment_order_doc = frappe.get_doc("Payment Order", payment_info.parent) - - bank_connector_exists = frappe.db.exists( - "Bank Connector", {"company": company, "bank_account": company_bank_account} - ) - - if not bank_connector_exists: - frappe.throw("Bank Connector is not initialized") - - bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) - - url = f"{bank_connector.url}/api/method/india_banking_connector.api.connect" - - api_key = bank_connector.api_key - api_secret = bank_connector.get_password("api_secret") - headers = { - "Authorization": f"token {api_key}:{api_secret}", - "Content-Type": "application/json", - } - - payment_info_payload = frappe._dict(payment_info.as_dict(convert_dates_to_str=True)) - - payment_info_payload.doc = payment_order_doc.as_dict(convert_dates_to_str=True) - payment_info_payload.method = "get_payment_status" - - response = requests.request( - "POST", url, headers=headers, data=json.dumps(payment_info_payload) - ) - - # create api request log - create_api_log( - response, "Get Payment Status", payment_info.parenttype, payment_info.parent - ) - - if response.status_code == 200: - response = json.loads(response.text) - response_data = frappe._dict((response.get("message") or {})) - - if response_data: - if response_data.status == "Processed": - if response_data.utr_number: - frappe.db.set_value( - "Payment Order Summary", - payment_info.name, - "reference_number", - response_data.utr_number, - ) - if payment_info.payment_entry: - frappe.db.set_value( - "Payment Entry", - payment_info.payment_entry, - "reference_no", - response_data.utr_number, - ) - if payment_info.journal_entry_account: - frappe.db.set_value( - "Journal Entry Account", - payment_info.journal_entry_account, - { - "payment_status": "Paid", - "reference_number": response_data.utr_number, - }, - ) - - notify_party(payment_info, response_data) - - frappe.db.set_value( - "Payment Order Summary", - payment_info.name, - "payment_status", - "Processed", - ) - - elif response_data.status == "Pending": - frappe.db.set_value( - "Payment Order Summary", - payment_info.name, - "message", - response_data.message, - ) - - elif response_data.status == "Failed": - if payment_info.journal_entry_account: - frappe.db.set_value( - "Journal Entry Account", - payment_info.journal_entry_account, - "payment_status", - "Failed", - ) - - frappe.db.set_value( - "Payment Order Summary", - payment_info.name, - { - "payment_status": response_data.status, - "message": response_data.message, - }, - ) - - if payment_info.payment_entry: - payment_entry_doc = frappe.get_doc( - "Payment Entry", payment_info.payment_entry - ) - if payment_entry_doc.docstatus == 1: - payment_entry_doc.cancel() - process_bank_payment_requests(payment_info.name) - - elif response_data.status == "Rejected": - if payment_info.journal_entry_account: - frappe.db.set_value( - "Journal Entry Account", - payment_info.journal_entry_account, - "payment_status", - "Failed", - ) - - frappe.db.set_value( - "Payment Order Summary", - payment_info.name, - { - "payment_status": response_data.status, - "message": response_data.message, - }, - ) - - if payment_info.payment_entry: - payment_entry_doc = frappe.get_doc( - "Payment Entry", payment_info.payment_entry - ) - if payment_entry_doc.docstatus == 1: - payment_entry_doc.cancel() - - process_bank_payment_requests(payment_info.name) - - -def process_bank_payment_requests(payment_order_summary): - pos = frappe.get_doc("Payment Order Summary", payment_order_summary) - payment_order_doc = frappe.get_doc("Payment Order", pos.parent) - - key = ( - pos.party_type, - pos.party, - pos.bank_account, - pos.account, - pos.cost_center, - pos.project, - pos.tax_withholding_category, - pos.reference_doctype, - ) - - failed_prs = [] - for ref in payment_order_doc.references: - ref_key = ( - ref.party_type, - ref.party, - ref.bank_account, - ref.account, - ref.cost_center, - ref.project, - ref.tax_withholding_category, - ref.reference_doctype, - ) - if key == ref_key: - failed_prs.append(ref.payment_request) - - for pr in failed_prs: - pr_doc = frappe.get_doc("Payment Request", pr) - if pr_doc.docstatus == 1: - pr_doc.check_if_payment_entry_exists() - pr_doc.set_as_cancelled() - pr_doc.db_set("docstatus", 2) - - -def get_refrence_number_for_bank_entry(payment_info): - ref_name = frappe.db.sql( - f""" - SELECT - je.name, jea.name, - FROM - `tabJournal Entry`je - JOIN - `tabJournal Entry Account`jea - ON - je.name = jea.parent - WHERE - je.docstatus != 2 AND jea.reference_type = 'Payroll Entry' AND jea.reference_name = '{payment_info.payroll_entry}' AND - je.voucher_type = 'Bank Entry' AND jea.party_type = '{payment_info.party_type}' AND jea.party = '{payment_info.party}' - LIMIT 1 - """, - as_dict=1, - debug=1, - ) - return ref_name From eb16ac012bfb88e4c23e214efc66831177c549c5 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 6 Jan 2025 10:29:04 +0530 Subject: [PATCH 63/83] refactor: stop bank payment request client actions --- .../doctype/bank_payment_request/bank_payment_request.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js index d3a77f8..b64ff96 100644 --- a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js +++ b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js @@ -1,6 +1,8 @@ // Copyright (c) 2024, Aerele Technologies Private Limited and contributors // For license information, please see license.txt +//purpose fully comman this doc-event to prevent getting errors +` frappe.ui.form.on('Bank Payment Request', { setup: function (frm) { frm.set_query("party_type", function () { @@ -104,4 +106,5 @@ const get_bank_query_conditions = function(frm) { }); } return conditions; -}; \ No newline at end of file +}; +` \ No newline at end of file From 848611fd69be0731aa96b98227caae7e499dadb2 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 6 Jan 2025 10:29:04 +0530 Subject: [PATCH 64/83] refactor: stop bank payment request client actions --- .../doctype/bank_payment_request/bank_payment_request.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js index b64ff96..989f5a9 100644 --- a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js +++ b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js @@ -1,6 +1,13 @@ // Copyright (c) 2024, Aerele Technologies Private Limited and contributors // For license information, please see license.txt +//purpose fully comman this doc-event to prevent getting errors + +frappe.ui.form.on('Bank Payment Request', { + refresh(frm) { + cur_frm.disable_form() + } +}) //purpose fully comman this doc-event to prevent getting errors ` frappe.ui.form.on('Bank Payment Request', { From c1a34104e7198e3429261f57384e817c691378de Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 6 Jan 2025 10:49:26 +0530 Subject: [PATCH 65/83] typo: use js multiline string syntax instead of useing "`" --- .../doctype/bank_payment_request/bank_payment_request.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js index 989f5a9..556cba5 100644 --- a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js +++ b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js @@ -5,11 +5,11 @@ frappe.ui.form.on('Bank Payment Request', { refresh(frm) { - cur_frm.disable_form() + frm.disable_form() } }) //purpose fully comman this doc-event to prevent getting errors -` +/* frappe.ui.form.on('Bank Payment Request', { setup: function (frm) { frm.set_query("party_type", function () { @@ -114,4 +114,4 @@ const get_bank_query_conditions = function(frm) { } return conditions; }; -` \ No newline at end of file +*/ \ No newline at end of file From c26fa4bc545fb4d83186b22580719092fa77cd1a Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 6 Jan 2025 11:02:33 +0530 Subject: [PATCH 66/83] fix: add payment create_payment_entry function in payment order --- .../india_banking/doc_events/payment_order.py | 236 +++++++++++++++++- 1 file changed, 235 insertions(+), 1 deletion(-) diff --git a/india_banking/india_banking/doc_events/payment_order.py b/india_banking/india_banking/doc_events/payment_order.py index d637a62..daa8f8d 100644 --- a/india_banking/india_banking/doc_events/payment_order.py +++ b/india_banking/india_banking/doc_events/payment_order.py @@ -1,9 +1,10 @@ import frappe -import frappe.utils from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, ) +from erpnext.accounts.doctype.payment_entry.payment_entry import get_split_invoice_rows from frappe import _, parse_json +from frappe.utils import nowdate @frappe.whitelist() @@ -70,3 +71,236 @@ def process_payment_requests(payment_order_summary): pr_doc.check_if_payment_entry_exists() pr_doc.set_as_cancelled() pr_doc.db_set("docstatus", 2) + + +@frappe.whitelist() +def make_payment_entries(docname): + payment_order_doc = frappe.get_doc("Payment Order", docname) + """create entry""" + frappe.flags.ignore_account_permission = True + for row in payment_order_doc.summary: + pe = frappe.new_doc("Payment Entry") + pe.payment_type = "Pay" + pe.payment_entry_type = "Pay" + pe.company = payment_order_doc.company + pe.cost_center = row.cost_center + pe.project = row.project + pe.posting_date = nowdate() + pe.mode_of_payment = "Wire Transfer" + pe.party_type = row.party_type + pe.party = row.party + pe.bank_account = payment_order_doc.company_bank_account + pe.party_bank_account = row.bank_account + if pe.party_type == "Supplier": + pe.ensure_supplier_is_not_blocked() + pe.payment_order = payment_order_doc.name + pe.paid_from = payment_order_doc.account + if row.account: + pe.paid_to = row.account + pe.paid_from_account_currency = "INR" + pe.paid_to_account_currency = "INR" + pe.paid_amount = row.amount + pe.received_amount = row.amount + pe.letter_head = frappe.db.get_value("Letter Head", {"is_default": 1}, "name") + pe.source_doctype = payment_order_doc.payment_order_type + for dimension in get_accounting_dimensions(): + pe.update({dimension: payment_order_doc.get(dimension, "")}) + if row.tax_withholding_category: + net_total = 0 + for reference in payment_order_doc.references: + if ( + reference.party_type == row.party_type + and reference.party == row.party + and reference.cost_center == row.cost_center + and reference.project == row.project + and reference.bank_account == row.bank_account + and reference.account == row.account + and reference.tax_withholding_category + == row.tax_withholding_category + and reference.reference_doctype == row.reference_doctype + ): + net_total += frappe.db.get_value( + "Payment Request", + reference.payment_request, + "net_total", + ) + pe.paid_amount = net_total + pe.received_amount = net_total + pe.apply_tax_withholding_amount = 1 + pe.tax_withholding_category = row.tax_withholding_category + for reference in payment_order_doc.references: + if not reference.is_adhoc: + filter_fields = [ + "party_type", + "party", + "bank_account", + "account", + "cost_center", + "project", + "tax_withholding_category", + "reference_doctype", + "reference_name", + ] + if payment_order_doc.summarise_payment_based_on == "Party": + filter_fields.remove("reference_name") + filter_fields.extend(get_accounting_dimensions()) + match_condition = [ + (reference.get(field, "") or "") == row.get(field, "") + for field in filter_fields + ] + if all(match_condition): + reference_amount = frappe.db.get_value( + "Payment Request", + reference.payment_request, + "net_total", + ) + payment_term = "" + try: + payment_term = frappe.db.get_value( + "Payment Request", + reference.payment_request, + "payment_term", + ) + if not payment_term: + if template := frappe.db.get_value( + reference.reference_doctype, + reference.reference_name, + "payment_terms_template", + ): + splited_invoice_rows = get_split_invoice_rows( + frappe._dict( + {"voucher_no": reference.reference_name} + ), + template, + exc_rates={ + reference.reference_name: frappe.get_doc( + "Purchase Invoice", reference.reference_name + ) + }, + ) + is_term_applied = frappe.db.get_value( + "Payment Terms Template", + template, + "allocate_payment_based_on_payment_terms", + ) + if splited_invoice_rows and is_term_applied: + term_row = 0 + while reference_amount > 0: + term_paid = ( + frappe.get_value( + "Payment Entry Reference", + { + "reference_doctype": reference.reference_doctype, + "reference_name": reference.reference_name, + "payment_term": splited_invoice_rows[ + term_row + ].get("payment_term"), + "docstatus": 1, + }, + "sum(allocated_amount)", + ) + or 0 + ) + per = ( + frappe.db.get_value( + "Payment Term", + splited_invoice_rows[term_row].get( + "payment_term" + ), + "invoice_portion", + ) + / 100 + ) + invoice_amount = frappe.db.get_value( + reference.reference_doctype, + reference.reference_name, + "grand_total", + ) + to_be_pay = per * invoice_amount + if (reference_amount + term_paid) <= to_be_pay: + paid_amount = reference_amount + reference_amount -= paid_amount + else: + paid_amount = to_be_pay - term_paid + reference_amount -= paid_amount + if paid_amount: + pe.append( + "references", + { + "reference_doctype": reference.reference_doctype, + "reference_name": reference.reference_name, + "total_amount": invoice_amount, + "allocated_amount": paid_amount, + "payment_term": splited_invoice_rows[ + term_row + ].get("payment_term"), + }, + ) + term_row += 1 + else: + pe.append( + "references", + { + "reference_doctype": reference.reference_doctype, + "reference_name": reference.reference_name, + "total_amount": reference_amount, + "allocated_amount": reference_amount, + }, + ) + else: + pe.append( + "references", + { + "reference_doctype": reference.reference_doctype, + "reference_name": reference.reference_name, + "total_amount": reference_amount, + "allocated_amount": reference_amount, + }, + ) + else: + pe.append( + "references", + { + "reference_doctype": reference.reference_doctype, + "reference_name": reference.reference_name, + "total_amount": reference_amount, + "allocated_amount": reference_amount, + "payment_term": payment_term, + }, + ) + except: + frappe.log_error( + "Error in Payment Terms Template", frappe.get_traceback() + ) + pe.update( + { + "reference_no": payment_order_doc.name, + "reference_date": nowdate(), + "remarks": "Payment Entry from Payment Order - {0}".format( + payment_order_doc.name + ), + } + ) + pe.setup_party_account_field() + pe.set_missing_values() + pe.validate() + + group_by_invoices(pe) + + pe.insert(ignore_permissions=True, ignore_mandatory=True) + pe.submit() + + # add payment entry reference in summary row + frappe.db.set_value("Payment Order Summary", row.name, "payment_entry", pe.name) + + +def group_by_invoices(self): + grouped_references = {} + if self.references: + for ref in self.references: + key = (ref.reference_name, ref.reference_doctype, ref.payment_term) + if key not in grouped_references: + grouped_references[key] = ref + else: + grouped_references[key].allocated_amount += ref.allocated_amount + self.references = list(grouped_references.values()) From bb6dc9140e2ab56f02f93efcfbd4eb605c6d4933 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 6 Jan 2025 11:16:01 +0530 Subject: [PATCH 67/83] fix: update Approved status as completed --- india_banking/public/js/payment_order_list.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/india_banking/public/js/payment_order_list.js b/india_banking/public/js/payment_order_list.js index b9930ed..580515d 100644 --- a/india_banking/public/js/payment_order_list.js +++ b/india_banking/public/js/payment_order_list.js @@ -6,12 +6,14 @@ frappe.listview_settings["Payment Order"] = { } else if (doc.status == "Initiated") { return [__("Initiated"), "blue", "status,=,Initiated"]; - } else if (doc.status == "Completed") { + } else if (["Completed", "Approved"].includes(doc.status)) { return [__("Completed"), "green", "status,=,Completed"]; }else if (doc.status == 'Rejected') { return [__('Rejected'), "red", "status,=,Rejected"]; }else if (doc.status == 'Failed') { return [__('Failed'), "red", "status,=,Failed"]; + }else if (doc.status == 'Partially Approved') { + return [__('Partially Approved'), "yellow", "status,=,Partially Approved"]; } }, }; From d0e178105f0fb1fa2fe8a1beaed21d8d83482ace Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 6 Jan 2025 13:14:49 +0530 Subject: [PATCH 68/83] fix: remove doc.reload avoid data loss fix: add condition payment order type --- india_banking/overrides/payment_order.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index d7540f6..ed00047 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -31,12 +31,8 @@ def validate_bank_payment_request(self): @frappe.whitelist() def update_unique_and_file_reference_id(self): unique_id = "".join(re.findall(r"[0-9a-zA-Z]", self.name))[-10:] - frappe.db.set_value( - "Payment Order", - self.name, - {"unique_id": unique_id, "file_reference_id": unique_id}, - ) - self.reload() + self.unique_id= unique_id + self.file_reference_id= unique_id def validate(self): self.validate_summary() @@ -123,8 +119,6 @@ def on_submit(self): self.update_payment_status() - self.reload() - def on_update_after_submit(self): frappe.throw("You cannot modify a payment order") @@ -163,8 +157,9 @@ def update_payment_status(self, cancel=False): if ref_field and ref_doc_field: for d in self.references: + doctype = self.payment_order_type + " Account" if self.payment_order_type == "Journal Entry" else self.payment_order_type frappe.db.set_value( - self.payment_order_type + " Account", + doctype, d.get(ref_doc_field), ref_field, status, From 80b9214cb0cbe4e17ac63616e865ff514e53f35c Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 6 Jan 2025 13:48:10 +0530 Subject: [PATCH 69/83] feat: add payment in the background useing enable_payment_in_the_background --- .../doctype/bank_connector/bank_connector.py | 153 ++++++++++++------ .../india_banking_settings.json | 9 +- 2 files changed, 109 insertions(+), 53 deletions(-) diff --git a/india_banking/india_banking/doctype/bank_connector/bank_connector.py b/india_banking/india_banking/doctype/bank_connector/bank_connector.py index 8074e07..00bd3e0 100644 --- a/india_banking/india_banking/doctype/bank_connector/bank_connector.py +++ b/india_banking/india_banking/doctype/bank_connector/bank_connector.py @@ -2,12 +2,14 @@ # For license information, please see license.txt import json +import re import frappe import requests as request from frappe import _ from frappe.model.document import Document from frappe.utils import comma_and, cstr, get_link_to_form, getdate +from frappe.utils.background_jobs import is_job_enqueued from india_banking.india_banking.doctype.india_banking_request_log.india_banking_request_log import ( create_api_log, @@ -53,7 +55,9 @@ def make_payment(self, payment_order, otp=None): if otp: self.verify_otp(payment_order, otp) + frappe.flags.ignore_message = True self.get_payment_status(payment_order) + frappe.flags.ignore_message = False # Make the payment if self.bulk_transaction: @@ -75,7 +79,8 @@ def get_single_payment_status(self, payment_order): payment_order.reload() self.update_payment_status(payment_order) - frappe.msgprint(_("Payment Status Updated")) + if not frappe.flags.ignore_message: + frappe.msgprint(_("Payment Status Updated")) def get_bulk_payment_status(self, payment_order): response = request.post( @@ -285,68 +290,112 @@ def get_status_response(self, summary_row, payment_order): "Failed", ) - def make_single_payment(self, payment_order): - count = 0 - for payment_row in payment_order.summary: + def process_single_payment(self, payment_order, payment_row): + if ( + not payment_row.payment_initiated + and payment_row.payment_status == "Pending" + ): + # handle failed or success response + payment_response = self.process_payment_and_response( + payment_row, payment_order + ) + if ( - not payment_row.payment_initiated - and payment_row.payment_status == "Pending" + payment_response + and "payment_status" in payment_response + and payment_response["payment_status"] == "Initiated" ): - # handle failed or success response - payment_response = self.process_payment_and_response( - payment_row, payment_order + frappe.db.set_value( + "Payment Order Summary", + payment_row.name, + { + "payment_status": "Initiated", + "payment_date": getdate(), + "payment_initiated": 1, + }, ) - if ( - payment_response - and "payment_status" in payment_response - and payment_response["payment_status"] == "Initiated" - ): + elif ( + payment_response + and "payment_status" in payment_response + and payment_response["payment_status"] == "" + ): + if "message" in payment_response: frappe.db.set_value( "Payment Order Summary", payment_row.name, - { - "payment_status": "Initiated", - "payment_date": getdate(), - "payment_initiated": 1, - }, + "message", + payment_response.message, ) - count += 1 - - elif ( - payment_response - and "payment_status" in payment_response - and payment_response["payment_status"] == "" - ): - if "message" in payment_response: - frappe.db.set_value( - "Payment Order Summary", - payment_row.name, - "message", - payment_response.message, - ) - else: + else: + frappe.db.set_value( + "Payment Order Summary", + payment_row.name, + "payment_status", + "Failed", + ) + payment_entry = frappe.get_doc( + "Payment Entry", payment_row.payment_entry + ) + if payment_entry.docstatus == 1: + payment_entry.cancel() + + self.process_bank_payment_requests(payment_order, payment_row) + + if payment_response and "message" in payment_response: frappe.db.set_value( "Payment Order Summary", payment_row.name, - "payment_status", - "Failed", + "message", + payment_response.message, ) - payment_entry = frappe.get_doc( - "Payment Entry", payment_row.payment_entry - ) - if payment_entry.docstatus == 1: - payment_entry.cancel() - self.process_bank_payment_requests(payment_order, payment_row) + def add_payment_in_the_background(self, payment_order): + def _add_queue(payment_row, job_id): + frappe.enqueue( + self.process_single_payment, + payment_order=payment_order, + payment_row=payment_row, + job_id=job_id, + job_name=f"Make Payment {job_id}", + enqueue_after_commit=True, + ) - if payment_response and "message" in payment_response: - frappe.db.set_value( - "Payment Order Summary", - payment_row.name, - "message", - payment_response.message, - ) + enqueue_count = 0 + for payment_row in payment_order.summary: + job_id = ( + "".join(re.findall(r"[0-9a-zA-Z]", self.name))[-10:] + + "-" + + payment_row.name + ) + if not frappe.db.exists("RQ Job", job_id): + _add_queue(payment_row=payment_row, job_id=job_id) + enqueue_count += 1 + + elif (rq_job := frappe.db.exists("RQ Job", job_id)) and not is_job_enqueued( + job_id + ): + frappe.get_doc("RQ Job", rq_job).delete() + frappe.clear_cache(doctype="RQ Job") + _add_queue(payment_row=payment_row, job_id=job_id) + enqueue_count += 1 + + frappe.msgprint(_(f"{enqueue_count} payments added in background")) + + def make_single_payment(self, payment_order): + # add payment in background + if ( + len(payment_order.summary) > 10 + or frappe.get_single( + "India Banking Settings" + ).enable_payment_in_the_background + ): + return self.add_payment_in_the_background(payment_order) + + for payment_row in payment_order.summary: + self.process_single_payment( + payment_order=payment_order, payment_row=payment_row + ) payment_order.reload() processed_count = 0 @@ -359,7 +408,7 @@ def make_single_payment(self, payment_order): "Payment Order", payment_order.name, "status", "Initiated" ) - return {"message": f"{count} payments initiated"} + frappe.msgprint(f"{processed_count} payments initiated") def process_payment_and_response(self, payment_row, payment_order): payment_payload = self.get_payload(payment_order, "intiate_payment") @@ -601,9 +650,9 @@ def update_payment_status(self, payment_order): success_count = 0 faild_count = 0 rejected_count = 0 - for ref in payment_order.summary: + for summary in payment_order.summary: status = frappe.db.get_value( - "Payment Order Summary", ref.name, "payment_status" + "Payment Order Summary", summary.name, "payment_status" ) if status == "Processed": success_count += 1 diff --git a/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.json b/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.json index e07f69a..f19c729 100644 --- a/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.json +++ b/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.json @@ -7,6 +7,7 @@ "field_order": [ "summarise_payment_based_on", "notify_party", + "enable_payment_in_the_background", "column_break_wjye", "default_email_format", "activate_workflow_on_bank_account" @@ -39,12 +40,18 @@ "fieldname": "activate_workflow_on_bank_account", "fieldtype": "Check", "label": "Activate Workflow on Bank Account" + }, + { + "default": "1", + "fieldname": "enable_payment_in_the_background", + "fieldtype": "Check", + "label": "Enable Payment in the Background" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-12-24 13:59:21.431509", + "modified": "2025-01-06 12:03:12.849508", "modified_by": "Administrator", "module": "India Banking", "name": "India Banking Settings", From d949117c9a7a703d4822290cdeacc625fc19ff49 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 7 Jan 2025 12:45:50 +0530 Subject: [PATCH 70/83] style: format codebase with linter --- india_banking/hooks.py | 229 +----------------- .../doctype/bank_connector/bank_connector.js | 24 +- .../bank_payment_request.js | 12 +- .../bank_payment_request.py | 229 ++++++++++++------ .../bank_payment_request_list.js | 38 +-- .../india_banking_request_log.py | 4 +- .../india_banking_settings.js | 18 +- india_banking/overrides/payment_order.py | 10 +- india_banking/overrides/payment_request.py | 10 +- .../patches/v1_0/update_custom_fields.py | 2 +- india_banking/public/js/payment_order_list.js | 37 +-- india_banking/public/js/payment_type.js | 26 +- india_banking/utils.py | 1 - 13 files changed, 253 insertions(+), 387 deletions(-) diff --git a/india_banking/hooks.py b/india_banking/hooks.py index d8a4e0f..fb4ed26 100644 --- a/india_banking/hooks.py +++ b/india_banking/hooks.py @@ -6,7 +6,7 @@ app_license = "gpl-3.0" -after_install = ["india_banking.install.after_install"] +after_install = "india_banking.install.after_install" before_uninstall = "india_banking.uninstall.before_uninstall" @@ -42,230 +42,3 @@ ] scheduler_events = {"daily": ["india_banking.tasks.daily"]} - - -# required_apps = [] - -# Includes in -# ------------------ - -# include js, css files in header of desk.html -# app_include_css = "/assets/india_banking/css/india_banking.css" -# app_include_js = "/assets/india_banking/js/india_banking.js" - -# include js, css files in header of web template -# web_include_css = "/assets/india_banking/css/india_banking.css" -# web_include_js = "/assets/india_banking/js/india_banking.js" - -# include custom scss in every website theme (without file extension ".scss") -# website_theme_scss = "india_banking/public/scss/website" - -# include js, css files in header of web form -# webform_include_js = {"doctype": "public/js/doctype.js"} -# webform_include_css = {"doctype": "public/css/doctype.css"} - -# include js in page -# page_js = {"page" : "public/js/file.js"} - -# include js in doctype views -# doctype_js = {"doctype" : "public/js/doctype.js"} -# doctype_list_js = {"doctype" : "public/js/doctype_list.js"} -# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} -# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} - -# Svg Icons -# ------------------ -# include app icons in desk -# app_include_icons = "india_banking/public/icons.svg" - -# Home Pages -# ---------- - -# application home page (will override Website Settings) -# home_page = "login" - -# website user home page (by Role) -# role_home_page = { -# "Role": "home_page" -# } - -# Generators -# ---------- - -# automatically create page for each record of this doctype -# website_generators = ["Web Page"] - -# Jinja -# ---------- - -# add methods and filters to jinja environment -# jinja = { -# "methods": "india_banking.utils.jinja_methods", -# "filters": "india_banking.utils.jinja_filters" -# } - -# Installation -# ------------ - -# before_install = "india_banking.install.before_install" -# after_install = "india_banking.install.after_install" - -# Uninstallation -# ------------ - -# before_uninstall = "india_banking.uninstall.before_uninstall" -# after_uninstall = "india_banking.uninstall.after_uninstall" - -# Integration Setup -# ------------------ -# To set up dependencies/integrations with other apps -# Name of the app being installed is passed as an argument - -# before_app_install = "india_banking.utils.before_app_install" -# after_app_install = "india_banking.utils.after_app_install" - -# Integration Cleanup -# ------------------- -# To clean up dependencies/integrations with other apps -# Name of the app being uninstalled is passed as an argument - -# before_app_uninstall = "india_banking.utils.before_app_uninstall" -# after_app_uninstall = "india_banking.utils.after_app_uninstall" - -# Desk Notifications -# ------------------ -# See frappe.core.notifications.get_notification_config - -# notification_config = "india_banking.notifications.get_notification_config" - -# Permissions -# ----------- -# Permissions evaluated in scripted ways - -# permission_query_conditions = { -# "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions", -# } -# -# has_permission = { -# "Event": "frappe.desk.doctype.event.event.has_permission", -# } - -# DocType Class -# --------------- -# Override standard doctype classes - -# override_doctype_class = { -# "ToDo": "custom_app.overrides.CustomToDo" -# } - - -# Document Events -# --------------- -# Hook on document methods and events - -# doc_events = { -# "*": { -# "on_update": "method", -# "on_cancel": "method", -# "on_trash": "method" -# } -# } - - -# Scheduled Tasks -# --------------- - -# scheduler_events = { -# "all": [ -# "india_banking.tasks.all" -# ], -# "daily": [ -# "india_banking.tasks.daily" -# ], -# "hourly": [ -# "india_banking.tasks.hourly" -# ], -# "weekly": [ -# "india_banking.tasks.weekly" -# ], -# "monthly": [ -# "india_banking.tasks.monthly" -# ], -# } - -# Testing -# ------- - -# before_tests = "india_banking.install.before_tests" - -# Overriding Methods -# ------------------------------ -# -# override_whitelisted_methods = { -# "frappe.desk.doctype.event.event.get_events": "india_banking.event.get_events" -# } - -# -# each overriding function accepts a `data` argument; -# generated from the base implementation of the doctype dashboard, -# along with any modifications made in other Frappe apps -# override_doctype_dashboards = { -# "Task": "india_banking.task.get_dashboard_data" -# } - -# exempt linked doctypes from being automatically cancelled -# -# auto_cancel_exempted_doctypes = ["Auto Repeat"] - -# Ignore links to specified DocTypes when deleting documents -# ----------------------------------------------------------- - -# ignore_links_on_delete = ["Communication", "ToDo"] - -# Request Events -# ---------------- -# before_request = ["india_banking.utils.before_request"] -# after_request = ["india_banking.utils.after_request"] - -# Job Events -# ---------- -# before_job = ["india_banking.utils.before_job"] -# after_job = ["india_banking.utils.after_job"] - -# User Data Protection -# -------------------- - -# user_data_fields = [ -# { -# "doctype": "{doctype_1}", -# "filter_by": "{filter_by}", -# "redact_fields": ["{field_1}", "{field_2}"], -# "partial": 1, -# }, -# { -# "doctype": "{doctype_2}", -# "filter_by": "{filter_by}", -# "partial": 1, -# }, -# { -# "doctype": "{doctype_3}", -# "strict": False, -# }, -# { -# "doctype": "{doctype_4}" -# } -# ] - -# Authentication and authorization -# -------------------------------- - -# auth_hooks = [ -# "india_banking.auth.validate" -# ] - -# Automatically update python controller files with type annotations for this app. -# export_python_type_annotations = True - -# default_log_clearing_doctypes = { -# "Logging DocType Name": 30 # days to retain logs -# } diff --git a/india_banking/india_banking/doctype/bank_connector/bank_connector.js b/india_banking/india_banking/doctype/bank_connector/bank_connector.js index f1237ea..8ce165c 100644 --- a/india_banking/india_banking/doctype/bank_connector/bank_connector.js +++ b/india_banking/india_banking/doctype/bank_connector/bank_connector.js @@ -2,16 +2,16 @@ // For license information, please see license.txt frappe.ui.form.on("Bank Connector", { - refresh(frm) { - frm.set_query("bank_account", function (doc) { - return { - filters: { - disabled: 0, - is_default: 1, - is_company_account: 1, - company: doc.company, - }, - }; - }); - }, + refresh(frm) { + frm.set_query("bank_account", function (doc) { + return { + filters: { + disabled: 0, + is_default: 1, + is_company_account: 1, + company: doc.company, + }, + }; + }); + }, }); diff --git a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js index 556cba5..ac45507 100644 --- a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js +++ b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.js @@ -3,11 +3,11 @@ //purpose fully comman this doc-event to prevent getting errors -frappe.ui.form.on('Bank Payment Request', { - refresh(frm) { - frm.disable_form() - } -}) +frappe.ui.form.on("Bank Payment Request", { + refresh(frm) { + frm.disable_form(); + }, +}); //purpose fully comman this doc-event to prevent getting errors /* frappe.ui.form.on('Bank Payment Request', { @@ -114,4 +114,4 @@ const get_bank_query_conditions = function(frm) { } return conditions; }; -*/ \ No newline at end of file +*/ diff --git a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.py b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.py index 92fa282..a0d48e1 100644 --- a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.py +++ b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.py @@ -2,19 +2,20 @@ # For license information, please see license.txt import frappe -from frappe.model.document import Document - -from erpnext.accounts.doctype.payment_request.payment_request import PaymentRequest, get_existing_payment_request_amount -from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details - - -from erpnext.accounts.doctype.payment_request import payment_request as PR - -from erpnext.accounts.party import get_party_bank_account from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, ) -from frappe.utils.data import flt, today, cstr +from erpnext.accounts.doctype.payment_request import payment_request as PR +from erpnext.accounts.doctype.payment_request.payment_request import ( + PaymentRequest, + get_existing_payment_request_amount, +) +from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( + get_party_tax_withholding_details, +) +from erpnext.accounts.party import get_party_bank_account +from frappe.model.document import Document +from frappe.utils.data import cstr, flt, today class BankPaymentRequest(PaymentRequest): @@ -23,7 +24,11 @@ def validate_subscription_details(self): def validate(self): frappe.log_error(title="India - banking", message=cstr(self.as_dict())) - if self.apply_tax_withholding_amount and self.tax_withholding_category and self.payment_request_type == "Outward": + if ( + self.apply_tax_withholding_amount + and self.tax_withholding_category + and self.payment_request_type == "Outward" + ): if not self.net_total: self.net_total = self.grand_total tds_amount = self.calculate_pr_tds(self.net_total) @@ -32,7 +37,11 @@ def validate(self): else: if self.net_total and not self.grand_total: self.grand_total = self.net_total - if self.grand_total and self.net_total != self.grand_total and not self.apply_tax_withholding_amount: + if ( + self.grand_total + and self.net_total != self.grand_total + and not self.apply_tax_withholding_amount + ): self.grand_total = self.net_total if not self.is_adhoc: @@ -47,7 +56,9 @@ def validate(self): def validate_payment_request_amount(self): existing_payment_request_amount = flt( - get_existing_payment_request_amount(self.reference_doctype, self.reference_name) + get_existing_payment_request_amount( + self.reference_doctype, self.reference_name + ) ) docname = None @@ -55,15 +66,24 @@ def validate_payment_request_amount(self): docname = self.name existing_payment_request_amount_drafted = flt( - get_existing_payment_request_amount(self.reference_doctype, self.reference_name, submitted=False, update=docname) + get_existing_payment_request_amount( + self.reference_doctype, + self.reference_name, + submitted=False, + update=docname, + ) ) - total_existing_payment_request_amount = existing_payment_request_amount + existing_payment_request_amount_drafted - + total_existing_payment_request_amount = ( + existing_payment_request_amount + existing_payment_request_amount_drafted + ) ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) - if not hasattr(ref_doc, "order_type") or getattr(ref_doc, "order_type") != "Shopping Cart": + if ( + not hasattr(ref_doc, "order_type") + or getattr(ref_doc, "order_type") != "Shopping Cart" + ): if self.reference_doctype in ["Purchase Order"]: ref_amount = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total) elif self.reference_doctype in ["Purchase Invoice"]: @@ -74,27 +94,40 @@ def validate_payment_request_amount(self): else: ref_amount = get_amount(ref_doc, self.payment_account) - frappe.log_error("existing_payment_request_amount_drafted", existing_payment_request_amount_drafted ) - frappe.log_error("existing_payment_request_amount", existing_payment_request_amount) + frappe.log_error( + "existing_payment_request_amount_drafted", + existing_payment_request_amount_drafted, + ) + frappe.log_error( + "existing_payment_request_amount", existing_payment_request_amount + ) frappe.log_error("ref_amount", ref_amount) frappe.log_error("self.net_total", self.net_total) if total_existing_payment_request_amount + flt(self.net_total) > ref_amount: frappe.throw( - frappe._("Total Bank Payment Request amount cannot be greater than {0} amount").format( - self.reference_doctype - ) + frappe._( + "Total Bank Payment Request amount cannot be greater than {0} amount" + ).format(self.reference_doctype) ) def on_submit(self): debit_account = None if self.payment_type: - debit_account = frappe.db.get_value("Payment Type", self.payment_type, "account") + debit_account = frappe.db.get_value( + "Payment Type", self.payment_type, "account" + ) elif self.reference_doctype == "Purchase Invoice": - debit_account = frappe.db.get_value(self.reference_doctype, self.reference_name, "credit_to") + debit_account = frappe.db.get_value( + self.reference_doctype, self.reference_name, "credit_to" + ) if not debit_account: - frappe.throw("Debit account for Payment Type {} cannot be determined".format(self.payment_type)) + frappe.throw( + "Debit account for Payment Type {} cannot be determined".format( + self.payment_type + ) + ) if not self.is_adhoc: super().on_submit() else: @@ -106,7 +139,9 @@ def create_payment_entry(self, submit=True): payment_entry = super().create_payment_entry(submit=submit) payment_entry.source_doctype = self.payment_order_type if payment_entry.docstatus != 1 and self.payment_type: - payment_entry.paid_to = frappe.db.get_value("Payment Type", self.payment_type, "account") or "" + payment_entry.paid_to = ( + frappe.db.get_value("Payment Type", self.payment_type, "account") or "" + ) return payment_entry @@ -128,7 +163,9 @@ def valdidate_bank_for_wire_transfer(self): frappe.throw(frappe._("Bank Account is missing for Wire Transfer Payments")) try: - status = frappe.db.get_value("Bank Account", self.bank_account, "workflow_state") + status = frappe.db.get_value( + "Bank Account", self.bank_account, "workflow_state" + ) if self.mode_of_payment == "Wire Transfer" and status != "Approved": frappe.throw("Cannot proceed with un-approved bank account") @@ -139,16 +176,22 @@ def valdidate_bank_for_wire_transfer(self): @frappe.whitelist() def validate_payment_request_status(**args): total_bank_payment_request_amount = frappe.db.get_all( - "Bank Payment Request", { - "reference_doctype": args.get('ref_doctype'), - "reference_name": args.get('ref_name'), - "docstatus": 1 + "Bank Payment Request", + { + "reference_doctype": args.get("ref_doctype"), + "reference_name": args.get("ref_name"), + "docstatus": 1, }, - "sum(grand_total) as grand_total") + "sum(grand_total) as grand_total", + ) - if total_bank_payment_request_amount[0] and total_bank_payment_request_amount[0].get('grand_total'): - if flt(total_bank_payment_request_amount[0].get('grand_total')) >= flt(args.get('grand_total')): - return 'Completed' + if total_bank_payment_request_amount[0] and total_bank_payment_request_amount[ + 0 + ].get("grand_total"): + if flt(total_bank_payment_request_amount[0].get("grand_total")) >= flt( + args.get("grand_total") + ): + return "Completed" return "" @@ -165,18 +208,26 @@ def make_bank_payment_request(**args): grand_total = get_amount(ref_doc, gateway_account.get("payment_account")) bank_account = ( - get_party_bank_account(args.get("party_type"), args.get("party")) if args.get("party_type") else "" + get_party_bank_account(args.get("party_type"), args.get("party")) + if args.get("party_type") + else "" ) if not bank_account: - frappe.throw(frappe._("Default Bank Account is missing for {0} - {1}").format(args.get("party_type"), args.get("party"))) + frappe.throw( + frappe._("Default Bank Account is missing for {0} - {1}").format( + args.get("party_type"), args.get("party") + ) + ) draft_payment_request = frappe.db.get_value( "Bank Payment Request", {"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": 0}, ) - - existing_payment_request_amount = get_existing_payment_request_amount(args.dt, args.dn) + + existing_payment_request_amount = get_existing_payment_request_amount( + args.dt, args.dn + ) if existing_payment_request_amount: grand_total -= existing_payment_request_amount @@ -186,12 +237,14 @@ def make_bank_payment_request(**args): bpr.db_set("net_total", grand_total, update_modified=False) bpr.validate() frappe.db.set_value( - "Bank Payment Request", draft_payment_request, { + "Bank Payment Request", + draft_payment_request, + { "net_total": bpr.net_total, "grand_total": bpr.grand_total, - "taxes_deducted": bpr.taxes_deducted - }, - update_modified=False + "taxes_deducted": bpr.taxes_deducted, + }, + update_modified=False, ) else: @@ -199,7 +252,9 @@ def make_bank_payment_request(**args): if not args.get("payment_request_type"): args["payment_request_type"] = ( - "Outward" if args.get("dt") in ["Purchase Order", "Purchase Invoice"] else "Inward" + "Outward" + if args.get("dt") in ["Purchase Order", "Purchase Invoice"] + else "Inward" ) bpr.payment_type = "Pay" @@ -218,27 +273,32 @@ def make_bank_payment_request(**args): "transaction_date": today(), "email_to": args.recipient_id or ref_doc.owner, "subject": frappe._("Bank Payment Request for {0}").format(args.dn), - "message": gateway_account.get("message") or PR.get_dummy_message(ref_doc), + "message": gateway_account.get("message") + or PR.get_dummy_message(ref_doc), "reference_doctype": args.dt, "reference_name": args.dn, "party_type": args.get("party_type") or "Customer", "party": args.get("party") or ref_doc.get("customer"), "bank_account": bank_account, - "net_total": grand_total + "net_total": grand_total, } ) # Update dimensions bpr.update( { - "cost_center": ref_doc.get("cost_center") or - frappe.get_value(ref_doc.get("doctype") + " Item", - {'parent': ref_doc.get("name")}, 'cost_center' - ), - "project": ref_doc.get("project") or - frappe.get_value(ref_doc.get("doctype") + " Item", - {'parent': ref_doc.get("name")}, 'project' - ) + "cost_center": ref_doc.get("cost_center") + or frappe.get_value( + ref_doc.get("doctype") + " Item", + {"parent": ref_doc.get("name")}, + "cost_center", + ), + "project": ref_doc.get("project") + or frappe.get_value( + ref_doc.get("doctype") + " Item", + {"parent": ref_doc.get("name")}, + "project", + ), } ) @@ -255,20 +315,25 @@ def make_bank_payment_request(**args): return bpr.as_dict() + @frappe.whitelist() -def make_payment_order(source_name, target_doc=None, args= None): +def make_payment_order(source_name, target_doc=None, args=None): from frappe.model.mapper import get_mapped_doc def set_missing_values(source, target): target.payment_order_type = "Bank Payment Request" account = "" if source.payment_type: - account = frappe.db.get_value("Payment Type", source.payment_type, "account") + account = frappe.db.get_value( + "Payment Type", source.payment_type, "account" + ) if source.reference_doctype == "Purchase Invoice": - account = frappe.db.get_value(source.reference_doctype, source.reference_name, "credit_to") + account = frappe.db.get_value( + source.reference_doctype, source.reference_name, "credit_to" + ) for dimension in get_accounting_dimensions(): - target.update({dimension: source.get(dimension, '')}) + target.update({dimension: source.get(dimension, "")}) target.append( "references", @@ -285,7 +350,7 @@ def set_missing_values(source, target): "is_adhoc": source.is_adhoc, "cost_center": source.cost_center, "project": source.project, - "tax_withholding_category": source.tax_withholding_category + "tax_withholding_category": source.tax_withholding_category, }, ) target.status = "Pending" @@ -299,7 +364,7 @@ def update_missing_values(source, target): account = source.paid_to for dimension in get_accounting_dimensions(): - target.update({dimension: source.get(dimension, '')}) + target.update({dimension: source.get(dimension, "")}) if source.references: target.append( @@ -311,12 +376,16 @@ def update_missing_values(source, target): "party_type": source.party_type, "party": source.party, "mode_of_payment": source.mode_of_payment, - "bank_account": get_party_bank_account(source.get("party_type"), source.get("party")) if source.get("party_type") else "", + "bank_account": get_party_bank_account( + source.get("party_type"), source.get("party") + ) + if source.get("party_type") + else "", "account": account, "cost_center": source.cost_center, "project": source.project, - "payment_entry": source.name - } + "payment_entry": source.name, + }, ) else: target.append( @@ -332,12 +401,12 @@ def update_missing_values(source, target): "account": source.paid_to, "cost_center": source.cost_center, "project": source.project, - "payment_entry": source.name - } + "payment_entry": source.name, + }, ) target.status = "Pending" - if args.get('ref_doctype') != "Payment Entry": + if args.get("ref_doctype") != "Payment Entry": doclist = get_mapped_doc( "Bank Payment Request", source_name, @@ -364,7 +433,10 @@ def update_missing_values(source, target): return doclist -def get_existing_payment_request_amount(ref_dt, ref_dn, submitted= True, update=None, payment_term= None): + +def get_existing_payment_request_amount( + ref_dt, ref_dn, submitted=True, update=None, payment_term=None +): """ Get the existing Bank payment request which are unpaid or partially paid for payment channel other than Phone and get the summation of existing paid Bank payment request for Phone payment channel. @@ -372,7 +444,11 @@ def get_existing_payment_request_amount(ref_dt, ref_dn, submitted= True, update= docstatus = 1 if submitted else 0 - where_conditions = "AND payment_term = '{0}'".format(payment_term.replace("%", "%%")) if payment_term else "AND payment_term is null" + where_conditions = ( + "AND payment_term = '{0}'".format(payment_term.replace("%", "%%")) + if payment_term + else "AND payment_term is null" + ) existing_payment_request_amount = frappe.db.sql( """ @@ -384,9 +460,14 @@ def get_existing_payment_request_amount(ref_dt, ref_dn, submitted= True, update= and reference_name = %s and docstatus = %s {0} """.format(where_conditions), - (update or "", ref_dt, ref_dn, docstatus) + (update or "", ref_dt, ref_dn, docstatus), + ) + return ( + flt(existing_payment_request_amount[0][0]) + if existing_payment_request_amount + else 0 ) - return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0 + def get_amount(ref_doc, payment_account=None): """get amount based on doctype""" @@ -402,9 +483,13 @@ def get_amount(ref_doc, payment_account=None): grand_total = flt(ref_doc.grand_total) else: if ref_doc.base_rounded_total: - grand_total = flt(ref_doc.base_rounded_total) / ref_doc.conversion_rate + grand_total = ( + flt(ref_doc.base_rounded_total) / ref_doc.conversion_rate + ) else: - grand_total = flt(ref_doc.base_grand_total) / ref_doc.conversion_rate + grand_total = ( + flt(ref_doc.base_grand_total) / ref_doc.conversion_rate + ) elif dt == "Sales Invoice": for pay in ref_doc.payments: if pay.type == "Phone" and pay.account == payment_account: diff --git a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request_list.js b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request_list.js index 77a7933..bb1715e 100644 --- a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request_list.js +++ b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request_list.js @@ -1,21 +1,21 @@ frappe.listview_settings["Bank Payment Request"] = { - add_fields: ["status"], - get_indicator: function (doc) { - if (doc.status == "Draft") { - return [__("Draft"), "gray", "status,=,Draft"]; - } - if (doc.status == "Requested") { - return [__("Requested"), "green", "status,=,Requested"]; - } else if (doc.status == "Initiated") { - return [__("Initiated"), "green", "status,=,Initiated"]; - }else if (doc.status == 'Payment Ordered') { - return [__('Payment Ordered'), "green", "status,=,Payment Ordered"]; - }else if (doc.status == "Partially Paid") { - return [__("Partially Paid"), "orange", "status,=,Partially Paid"]; - } else if (doc.status == "Paid") { - return [__("Paid"), "blue", "status,=,Paid"]; - } else if (doc.status == "Cancelled") { - return [__("Cancelled"), "red", "status,=,Cancelled"]; - } - }, + add_fields: ["status"], + get_indicator: function (doc) { + if (doc.status == "Draft") { + return [__("Draft"), "gray", "status,=,Draft"]; + } + if (doc.status == "Requested") { + return [__("Requested"), "green", "status,=,Requested"]; + } else if (doc.status == "Initiated") { + return [__("Initiated"), "green", "status,=,Initiated"]; + } else if (doc.status == "Payment Ordered") { + return [__("Payment Ordered"), "green", "status,=,Payment Ordered"]; + } else if (doc.status == "Partially Paid") { + return [__("Partially Paid"), "orange", "status,=,Partially Paid"]; + } else if (doc.status == "Paid") { + return [__("Paid"), "blue", "status,=,Paid"]; + } else if (doc.status == "Cancelled") { + return [__("Cancelled"), "red", "status,=,Cancelled"]; + } + }, }; diff --git a/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py b/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py index a31237b..4d3cbad 100644 --- a/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py +++ b/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py @@ -33,8 +33,8 @@ def create_api_log(res, action=None, ref_doctype=None, ref_docname=None): """Can create API log From response Args: - res (response object): It is used to obtain an API response. - request_from (str): It is optional for the purposes of the API... + res (response object): It is used to obtain an API response. + request_from (str): It is optional for the purposes of the API... """ if not isinstance(res, Response): return diff --git a/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.js b/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.js index 19ebcc4..033685d 100644 --- a/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.js +++ b/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.js @@ -2,13 +2,13 @@ // For license information, please see license.txt frappe.ui.form.on("India Banking Settings", { - refresh(frm) { - frm.set_query("default_email_format", function() { - return { - filters: { - "doc_type": "Payment Entry" - } - } - }) - }, + refresh(frm) { + frm.set_query("default_email_format", function () { + return { + filters: { + doc_type: "Payment Entry", + }, + }; + }); + }, }); diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index ed00047..32b9f16 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -31,8 +31,8 @@ def validate_bank_payment_request(self): @frappe.whitelist() def update_unique_and_file_reference_id(self): unique_id = "".join(re.findall(r"[0-9a-zA-Z]", self.name))[-10:] - self.unique_id= unique_id - self.file_reference_id= unique_id + self.unique_id = unique_id + self.file_reference_id = unique_id def validate(self): self.validate_summary() @@ -157,7 +157,11 @@ def update_payment_status(self, cancel=False): if ref_field and ref_doc_field: for d in self.references: - doctype = self.payment_order_type + " Account" if self.payment_order_type == "Journal Entry" else self.payment_order_type + doctype = ( + self.payment_order_type + " Account" + if self.payment_order_type == "Journal Entry" + else self.payment_order_type + ) frappe.db.set_value( doctype, d.get(ref_doc_field), diff --git a/india_banking/overrides/payment_request.py b/india_banking/overrides/payment_request.py index 7a00027..01a55db 100644 --- a/india_banking/overrides/payment_request.py +++ b/india_banking/overrides/payment_request.py @@ -59,9 +59,9 @@ def on_submit(self): if not debit_account: frappe.throw( - _("Debit account for Payment Type {} cannot be determined").format( - self.payment_type or "" - ) + _( + "Debit account for Payment Type {} cannot be determined" + ).format(self.payment_type or "") ) if not self.is_adhoc: @@ -73,7 +73,9 @@ def on_submit(self): def create_payment_entry(self, submit=True): payment_entry = super().create_payment_entry(submit=submit) if payment_entry.docstatus != 1 and self.payment_type: - payment_entry.paid_to = frappe.db.get_value("Payment Type", self.payment_type, "account") or "" + payment_entry.paid_to = ( + frappe.db.get_value("Payment Type", self.payment_type, "account") or "" + ) return payment_entry diff --git a/india_banking/patches/v1_0/update_custom_fields.py b/india_banking/patches/v1_0/update_custom_fields.py index aacf32d..b462919 100644 --- a/india_banking/patches/v1_0/update_custom_fields.py +++ b/india_banking/patches/v1_0/update_custom_fields.py @@ -39,4 +39,4 @@ def remove_main_field_order(): "Payment Request", ] for doctype in doctypes: - frappe.db.delete("Property Setter", {"name": f"{doctype}-main-field_order"}) \ No newline at end of file + frappe.db.delete("Property Setter", {"name": f"{doctype}-main-field_order"}) diff --git a/india_banking/public/js/payment_order_list.js b/india_banking/public/js/payment_order_list.js index 580515d..4c06eb5 100644 --- a/india_banking/public/js/payment_order_list.js +++ b/india_banking/public/js/payment_order_list.js @@ -1,19 +1,22 @@ frappe.listview_settings["Payment Order"] = { - add_fields: ["status"], - get_indicator: function (doc) { - if (doc.status == "Pending") { - return [__("Pending"), "orange", "status,=,Pending"]; - } - else if (doc.status == "Initiated") { - return [__("Initiated"), "blue", "status,=,Initiated"]; - } else if (["Completed", "Approved"].includes(doc.status)) { - return [__("Completed"), "green", "status,=,Completed"]; - }else if (doc.status == 'Rejected') { - return [__('Rejected'), "red", "status,=,Rejected"]; - }else if (doc.status == 'Failed') { - return [__('Failed'), "red", "status,=,Failed"]; - }else if (doc.status == 'Partially Approved') { - return [__('Partially Approved'), "yellow", "status,=,Partially Approved"]; - } - }, + add_fields: ["status"], + get_indicator: function (doc) { + if (doc.status == "Pending") { + return [__("Pending"), "orange", "status,=,Pending"]; + } else if (doc.status == "Initiated") { + return [__("Initiated"), "blue", "status,=,Initiated"]; + } else if (["Completed", "Approved"].includes(doc.status)) { + return [__("Completed"), "green", "status,=,Completed"]; + } else if (doc.status == "Rejected") { + return [__("Rejected"), "red", "status,=,Rejected"]; + } else if (doc.status == "Failed") { + return [__("Failed"), "red", "status,=,Failed"]; + } else if (doc.status == "Partially Approved") { + return [ + __("Partially Approved"), + "yellow", + "status,=,Partially Approved", + ]; + } + }, }; diff --git a/india_banking/public/js/payment_type.js b/india_banking/public/js/payment_type.js index b952e39..da68b86 100644 --- a/india_banking/public/js/payment_type.js +++ b/india_banking/public/js/payment_type.js @@ -1,13 +1,13 @@ -frappe.ui.form.on('Payment Type', { - refresh(frm) { - frm.set_query("account", function() { - return { - filters: { - "is_group": 0, - "disabled": 0, - "account_type": "Payable" - } - }; - }); - } -}) \ No newline at end of file +frappe.ui.form.on("Payment Type", { + refresh(frm) { + frm.set_query("account", function () { + return { + filters: { + is_group: 0, + disabled: 0, + account_type: "Payable", + }, + }; + }); + }, +}); diff --git a/india_banking/utils.py b/india_banking/utils.py index 1d8252c..b96b396 100644 --- a/india_banking/utils.py +++ b/india_banking/utils.py @@ -1,5 +1,4 @@ import frappe -from frappe import _ def get_bank_address_details(bank_account): From 60fee1133432a7ce45ab08d271ae8b1ad4b482a4 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 7 Jan 2025 13:39:14 +0530 Subject: [PATCH 71/83] refactor: enable mode of transfer while installing chore: rename 'bank_on_trash' to 'disallow_standard_bank_deletion' fix: update maximum limit for all mode of transfer chore: rename 'Branch Code' label to 'IFSC Code' in bank account doc --- india_banking/default.py | 40 +++++++++---------- india_banking/hooks.py | 6 ++- .../india_banking/doc_events/bank.py | 12 +----- .../india_banking/doc_events/bank_account.py | 12 ++++++ india_banking/install.py | 26 +++++++++--- india_banking/overrides/payment_order.py | 5 ++- 6 files changed, 60 insertions(+), 41 deletions(-) create mode 100644 india_banking/india_banking/doc_events/bank_account.py diff --git a/india_banking/default.py b/india_banking/default.py index ebec6c8..0906911 100644 --- a/india_banking/default.py +++ b/india_banking/default.py @@ -1,47 +1,45 @@ DEFAULT_MODE_OF_TRANSFERS = [ { - "mode": "IMPS", - "minimum_limit": 0, - "maximum_limit": 200000, + "mode": "A2A/FT/Internal", + "minimum_limit": 1, + "maximum_limit": 500000000, "start_time": "0:00:00", "end_time": "23:59:59", - "disabled": 1, + "disabled": 0, "priority": "1", }, { - "mode": "RTGS", - "minimum_limit": 200000, - "maximum_limit": 50000000, + "mode": "IMPS", + "minimum_limit": 1, + "maximum_limit": 200000, "start_time": "0:00:00", "end_time": "23:59:59", - "disabled": 1, - "priority": "1", + "disabled": 0, + "priority": "2", }, { - "mode": "NEFT", - "minimum_limit": 0, - "maximum_limit": 100000000000, + "mode": "RTGS", + "minimum_limit": 200000, + "maximum_limit": 500000000, "start_time": "0:00:00", "end_time": "23:59:59", - "disabled": 1, - "priority": "1", + "disabled": 0, + "priority": "3", }, { - "mode": "A2A/FT/Internal", - "minimum_limit": 0, - "maximum_limit": 0, + "mode": "NEFT", + "minimum_limit": 1, + "maximum_limit": 500000000, "start_time": "0:00:00", "end_time": "23:59:59", - "disabled": 1, - "priority": "1", + "disabled": 0, + "priority": "4", }, ] STD_BANK_LIST = [ - "Yes Bank", "HDFC Bank", "ICICI Bank", - "Axis Bank", "Kotak Mahindra Bank", ] diff --git a/india_banking/hooks.py b/india_banking/hooks.py index fb4ed26..a8d12dd 100644 --- a/india_banking/hooks.py +++ b/india_banking/hooks.py @@ -30,9 +30,11 @@ } doc_events = { - "Bank": {"on_trash": "india_banking.india_banking.doc_events.bank.bank_on_trash"}, + "Bank": { + "on_trash": "india_banking.india_banking.doc_events.bank.disallow_standard_bank_deletion" + }, "Bank Account": { - "validate": "india_banking.india_banking.doc_events.bank.validate_ifsc_code" + "validate": "india_banking.india_banking.doc_events.bank_account.validate_ifsc_code" }, } diff --git a/india_banking/india_banking/doc_events/bank.py b/india_banking/india_banking/doc_events/bank.py index 43329ea..58c8b75 100644 --- a/india_banking/india_banking/doc_events/bank.py +++ b/india_banking/india_banking/doc_events/bank.py @@ -1,19 +1,9 @@ -import re - import frappe from frappe import _ -from frappe.utils import cstr - -IFSC_PATTERN = re.compile(r"^[A-Z]{4}0[A-Z0-9]{6}$") -def bank_on_trash(doc, method=None): +def disallow_standard_bank_deletion(doc, method=None): if getattr(doc, "is_standard", False): frappe.throw( _("Standard Bank cannot be deleted"), title=_("Action Not Permitted") ) - - -def validate_ifsc_code(doc, method=None): - if not IFSC_PATTERN.match(cstr(doc.branch_code)): - frappe.throw(_("IFSC/Branch Code is not valid")) diff --git a/india_banking/india_banking/doc_events/bank_account.py b/india_banking/india_banking/doc_events/bank_account.py new file mode 100644 index 0000000..b58c79e --- /dev/null +++ b/india_banking/india_banking/doc_events/bank_account.py @@ -0,0 +1,12 @@ +import frappe + +from frappe.utils import cstr +from frappe import _ +import re + +IFSC_PATTERN = re.compile(r"^[A-Z]{4}0[A-Z0-9]{6}$") + + +def validate_ifsc_code(doc, method=None): + if not IFSC_PATTERN.match(cstr(doc.branch_code)): + frappe.throw(_("IFSC/Branch Code is not valid")) diff --git a/india_banking/install.py b/india_banking/install.py index aef6319..e86daff 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -33,10 +33,6 @@ def make_custom_fields(): create_journal_entry_custom_fields() -def create_property_setter(): - create_payment_request_property_setter() - - def toggle_payment_request_creation(allow=True): click.secho( " -> {} Payment Request Creation...".format( @@ -213,11 +209,29 @@ def create_payment_request_custom_fields(): "property_type": "Check", "value": 0, }, - ] + ], + "Bank Account": [ + { + "doctype_or_field": "DocField", + "doctype": "Bank Account", + "fieldname": "branch_code", + "property": "label", + "property_type": "Data", + "value": "IFSC Code", + }, + { + "doctype_or_field": "DocField", + "doctype": "Bank Account", + "fieldname": "branch_code", + "property": "reqd", + "property_type": "Data", + "value": 1, + }, + ], } -def create_payment_request_property_setter(): +def create_property_setter(): for doctype in properties.keys(): click.echo(f" -> Updating {doctype} Field Properties") for _property in properties.get(doctype): diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index 32b9f16..c6803a5 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -55,7 +55,10 @@ def validate_summary(self): else default_mode_of_transfer ) - if mode_of_transfer.mode == "RTGS" and payment.amount >= 500000000: + if ( + mode_of_transfer.mode in ["NEFT", "RTGS"] + and payment.amount >= 500000000 + ): lei_number = frappe.db.get_value( payment.party_type, payment.party, "lei_number" ) From 8cb72e64acc9a426b75a3553fa26b9c0dc0e4fc7 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 7 Jan 2025 16:39:17 +0530 Subject: [PATCH 72/83] refactor: add translate to message --- .../doctype/bank_connector/bank_connector.py | 28 +++++++++++------ .../bank_payment_request.py | 25 +++++++++------ india_banking/overrides/payment_entry.py | 6 ++-- india_banking/overrides/payment_order.py | 31 ++++++++++++------- india_banking/overrides/payment_request.py | 8 +++-- 5 files changed, 63 insertions(+), 35 deletions(-) diff --git a/india_banking/india_banking/doctype/bank_connector/bank_connector.py b/india_banking/india_banking/doctype/bank_connector/bank_connector.py index 00bd3e0..0febae7 100644 --- a/india_banking/india_banking/doctype/bank_connector/bank_connector.py +++ b/india_banking/india_banking/doctype/bank_connector/bank_connector.py @@ -99,7 +99,10 @@ def get_bulk_payment_status(self, payment_order): payment_status_details = status_response.payment_status_details if status_response.status == "Processed": - frappe.msgprint(status_response.message, status_response.file_status) + frappe.msgprint( + _(cstr(status_response.message)), + _(cstr(status_response.file_status)), + ) fs = status_response.file_status if status_response.file_status in ["FAL", "REJ", "REC"]: for summary_row in payment_order.summary: @@ -178,9 +181,11 @@ def get_bulk_payment_status(self, payment_order): self.update_payment_status(payment_order) else: - frappe.throw(msg=status_response.server_message, title="Failed") + frappe.throw( + msg=_(cstr(status_response.server_message)), title=_("Failed") + ) else: - frappe.throw("Invalid Request") + frappe.throw(_("Invalid Request")) def get_status_response(self, summary_row, payment_order): response = request.post( @@ -408,7 +413,7 @@ def make_single_payment(self, payment_order): "Payment Order", payment_order.name, "status", "Initiated" ) - frappe.msgprint(f"{processed_count} payments initiated") + frappe.msgprint(_(f"{processed_count} payments initiated")) def process_payment_and_response(self, payment_row, payment_order): payment_payload = self.get_payload(payment_order, "intiate_payment") @@ -430,7 +435,9 @@ def process_payment_and_response(self, payment_row, payment_order): bank_link = get_link_to_form("Bank Account", payment_row.bank_account) if not party_address: frappe.throw( - f"Address not found for the selected bank account {bank_link} at Row #{payment_row.idx}" + _( + f"Address not found for the selected bank account {bank_link} at Row #{payment_row.idx}" + ) ) payment_payload.address = json.dumps(party_address) @@ -526,7 +533,7 @@ def make_bulk_payment(self, payment_order, otp): ) payment_account_list.append(summary_row.account_name + "-" + lei_number) if not lei_number: - frappe.throw("LEI Number required for payment > 50 Cr") + frappe.throw(_("LEI Number required for payment > 50 Cr")) else: payment_account_list.append( summary_row.account_name + "-" + summary_row.bank_account_no @@ -578,8 +585,11 @@ def process_bulk_payment_response(self, response, payment_order): frappe.msgprint(_("Payment Initiated")) elif payment_response.get("status", "") == "Failed": - frappe.msgprint(_("Failed - " + cstr(payment_response.get("message")))) - + frappe.msgprint( + title=_("Failed"), + msg=_(cstr(payment_response.get("message"))), + indicator="red", + ) else: frappe.throw(_("Invalid Response: Check API Log")) @@ -772,7 +782,7 @@ def get_bank_connector(bank_account, company): }, ) if not bank_connector: - frappe.throw("Bank Connector is not initialized") + frappe.throw(_("Bank Connector is not initialized")) return frappe.get_doc("Bank Connector", bank_connector) diff --git a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.py b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.py index a0d48e1..ddad9ea 100644 --- a/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.py +++ b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.py @@ -14,6 +14,7 @@ get_party_tax_withholding_details, ) from erpnext.accounts.party import get_party_bank_account +from frappe import _ from frappe.model.document import Document from frappe.utils.data import cstr, flt, today @@ -50,7 +51,7 @@ def validate(self): if self.get("__islocal"): self.status = "Draft" if self.reference_doctype or self.reference_name: - frappe.throw("Payments with references cannot be marked as ad-hoc") + frappe.throw(_("Payments with references cannot be marked as ad-hoc")) self.valdidate_bank_for_wire_transfer() @@ -106,7 +107,7 @@ def validate_payment_request_amount(self): if total_existing_payment_request_amount + flt(self.net_total) > ref_amount: frappe.throw( - frappe._( + _( "Total Bank Payment Request amount cannot be greater than {0} amount" ).format(self.reference_doctype) ) @@ -124,8 +125,10 @@ def on_submit(self): if not debit_account: frappe.throw( - "Debit account for Payment Type {} cannot be determined".format( - self.payment_type + _( + "Debit account for Payment Type {} cannot be determined".format( + self.payment_type + ) ) ) if not self.is_adhoc: @@ -160,7 +163,7 @@ def calculate_pr_tds(self, amount): def valdidate_bank_for_wire_transfer(self): if self.mode_of_payment == "Wire Transfer" and not self.bank_account: - frappe.throw(frappe._("Bank Account is missing for Wire Transfer Payments")) + frappe.throw(_("Bank Account is missing for Wire Transfer Payments")) try: status = frappe.db.get_value( @@ -168,9 +171,9 @@ def valdidate_bank_for_wire_transfer(self): ) if self.mode_of_payment == "Wire Transfer" and status != "Approved": - frappe.throw("Cannot proceed with un-approved bank account") + frappe.throw(_("Cannot proceed with un-approved bank account")) except: - frappe.throw("Workflow Not Found for Bank Account") + frappe.throw(_("Workflow Not Found for Bank Account")) @frappe.whitelist() @@ -215,8 +218,10 @@ def make_bank_payment_request(**args): if not bank_account: frappe.throw( - frappe._("Default Bank Account is missing for {0} - {1}").format( - args.get("party_type"), args.get("party") + _( + "Default Bank Account is missing for {0} - {1}".format( + args.get("party_type"), args.get("party") + ) ) ) @@ -506,4 +511,4 @@ def get_amount(ref_doc, payment_account=None): if grand_total > 0: return grand_total else: - frappe.throw(frappe._("Bank Payment Entry is already created")) + frappe.throw(_("Bank Payment Entry is already created")) diff --git a/india_banking/overrides/payment_entry.py b/india_banking/overrides/payment_entry.py index 3d6137c..ff56ef9 100644 --- a/india_banking/overrides/payment_entry.py +++ b/india_banking/overrides/payment_entry.py @@ -32,8 +32,10 @@ def _get_default_bank_account(party_type, party): party_bank_account = get_party_bank_account(party_type, party) if not party_bank_account: frappe.throw( - _("Default Bank Account is missing for {0} - {1}").format( - party_type, party + _( + "Default Bank Account is missing for {0} - {1}".format( + party_type, party + ) ) ) return party_bank_account diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index c6803a5..ad154ac 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -6,6 +6,7 @@ get_accounting_dimensions, ) from erpnext.accounts.doctype.payment_order.payment_order import PaymentOrder +from frappe import _ from frappe.utils import get_link_to_form from india_banking.india_banking.doc_events.payment_order import make_payment_entries @@ -26,7 +27,7 @@ def validate_bank_payment_request(self): if payment_request.grand_total != ref.amount: link = get_link_to_form("Payment Request", ref.payment_request) message = f"The amount in #Row{ref.idx} does not match the amount of the Payment Request -{link}. The Difference is {ref.amount - payment_request.grand_total}" - frappe.throw(title="Invalid Amount", msg=message) + frappe.throw(title=_("Invalid Amount"), msg=_(message)) @frappe.whitelist() def update_unique_and_file_reference_id(self): @@ -39,7 +40,7 @@ def validate(self): def validate_summary(self): if not self.summary: - frappe.throw("Please validate the summary") + frappe.throw(_("Please validate the summary")) default_mode_of_transfer = ( frappe.get_doc("Mode of Transfer", self.default_mode_of_transfer) @@ -64,16 +65,20 @@ def validate_summary(self): ) if not lei_number: frappe.throw( - f"LEI Number required for payment > 50 Cr. For {payment.party_type} - {payment.party} - {payment.amount}" + _( + f"LEI Number required for payment > 50 Cr. For {payment.party_type} - {payment.party} - {payment.amount}" + ) ) if "A2A" in mode_of_transfer.mode and payment.bank != self.company_bank: frappe.throw( - f"Invalid mode of transfer for {payment.party_type} - {payment.party} at row #{payment.idx}" + _( + f"Invalid mode of transfer for {payment.party_type} - {payment.party} at row #{payment.idx}" + ) ) if not mode_of_transfer: - frappe.throw("Define a specific mode of transfer or a default one") + frappe.throw(_("Define a specific mode of transfer or a default one")) if not ( mode_of_transfer.minimum_limit @@ -81,7 +86,9 @@ def validate_summary(self): <= mode_of_transfer.maximum_limit ): frappe.throw( - f"Mode of Transfer not suitable for {payment.party} for {payment.amount}. {mode_of_transfer.mode}: {mode_of_transfer.minimum_limit}-{mode_of_transfer.maximum_limit}" + _( + f"Mode of Transfer not suitable for {payment.party} for {payment.amount}. {mode_of_transfer.mode}: {mode_of_transfer.minimum_limit}-{mode_of_transfer.maximum_limit}" + ) ) payment.mode_of_transfer = mode_of_transfer.mode @@ -97,7 +104,7 @@ def validate_summary(self): references_total += reference.amount if summary_total != references_total: - frappe.throw("Summary isn't matching the references") + frappe.throw(_("Summary isn't matching the references")) def get_party_field_name(self, party): if party.party_type == "Supplier": @@ -109,7 +116,7 @@ def get_party_field_name(self, party): elif party.part_type == "Customer": return "customer_name" else: - frappe.throw(f"Unsupported party type {party.party_type}") + frappe.throw(_(f"Unsupported party type {party.party_type}")) def on_submit(self): if self.payment_order_type in [ @@ -123,19 +130,21 @@ def on_submit(self): self.update_payment_status() def on_update_after_submit(self): - frappe.throw("You cannot modify a payment order") + frappe.throw(_("You cannot modify a payment order")) def on_cancel(self): for summary in self.summary: if summary.payment_status in ["Processed", "Initiated"]: frappe.throw( - "You cannot cancel a payment order with Initiated/Processed payments" + _( + "You cannot cancel a payment order with Initiated/Processed payments" + ) ) super().on_cancel() def on_trash(self): if self.docstatus == 1: - frappe.throw("You cannot delete a payment order") + frappe.throw(_("You cannot delete a payment order")) def update_payment_status(self, cancel=False): self.db_set("status", "Pending") diff --git a/india_banking/overrides/payment_request.py b/india_banking/overrides/payment_request.py index 01a55db..50e56f3 100644 --- a/india_banking/overrides/payment_request.py +++ b/india_banking/overrides/payment_request.py @@ -46,8 +46,10 @@ def on_submit(self): bank_account = get_party_bank_account(self.party_type, self.party) if not bank_account: frappe.throw( - _("Default Bank Account is missing for {0} - {1}").format( - self.party_type, self.party + _( + "Default Bank Account is missing for {0} - {1}".format( + self.party_type, self.party + ) ) ) @@ -94,7 +96,7 @@ def calculate_pr_tds(self, amount): def valdidate_bank_for_wire_transfer(self): if self.mode_of_payment == "Wire Transfer" and not self.bank_account: - frappe.throw(frappe._("Bank Account is missing for Wire Transfer Payments")) + frappe.throw(_("Bank Account is missing for Wire Transfer Payments")) try: status = frappe.db.get_value( From c1113a42b051c7e9c8b386ebff606643643dce78 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 7 Jan 2025 19:29:09 +0530 Subject: [PATCH 73/83] fix: include payload with summary row details for payment reference --- .../india_banking/doctype/bank_connector/bank_connector.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/india_banking/india_banking/doctype/bank_connector/bank_connector.py b/india_banking/india_banking/doctype/bank_connector/bank_connector.py index 0febae7..7a9f7c7 100644 --- a/india_banking/india_banking/doctype/bank_connector/bank_connector.py +++ b/india_banking/india_banking/doctype/bank_connector/bank_connector.py @@ -188,10 +188,12 @@ def get_bulk_payment_status(self, payment_order): frappe.throw(_("Invalid Request")) def get_status_response(self, summary_row, payment_order): + status_payload = self.get_payload(payment_order, "get_payment_status") + status_payload.update(summary_row.as_dict(convert_dates_to_str=True)) response = request.post( self.base_url, headers=self.headers, - data=json.dumps(self.get_payload(payment_order, "get_payment_status")), + data=json.dumps(status_payload), ) # create api request log From 206933e0ad23dc9dab77d0f16f70a488f6e501fd Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Wed, 8 Jan 2025 13:43:22 +0530 Subject: [PATCH 74/83] feat: In the log doc, add a button to display the failure reason. --- .../india_banking_request_log.json | 19 +++++++++++- .../india_banking_request_log.py | 30 +++++++++++++++++-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.json b/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.json index a100e37..78cfdbb 100644 --- a/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.json +++ b/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.json @@ -6,6 +6,9 @@ "engine": "InnoDB", "field_order": [ "action", + "column_break_xbip", + "show_failure_message", + "section_break_yxfd", "url", "method", "header", @@ -64,11 +67,25 @@ "fieldname": "reference_docname", "fieldtype": "Data", "label": "Reference Docname" + }, + { + "fieldname": "show_failure_message", + "fieldtype": "Button", + "label": "Show Failure Message", + "options": "show_failure_message" + }, + { + "fieldname": "column_break_xbip", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_yxfd", + "fieldtype": "Section Break" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-09-26 15:44:16.444407", + "modified": "2025-01-08 13:08:50.684456", "modified_by": "Administrator", "module": "India Banking", "name": "India Banking Request Log", diff --git a/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py b/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py index 4d3cbad..b9001a6 100644 --- a/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py +++ b/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py @@ -5,12 +5,36 @@ import frappe import requests +from frappe import _ from frappe.model.document import Document from requests.models import Response class IndiaBankingRequestLog(Document): - pass + @frappe.whitelist() + def show_failure_message(self): + try: + if ( + self.response + and (response := json.loads(self.response)) + and (server_messages := response.get("_server_messages")) + ): + if ( + server_messages + and (server_messages := json.loads(server_messages)) + and (server_message := server_messages[0]) + ): + server_message = json.loads(server_message) + title = _("Failure Reason") + message = _( + f'{frappe.bold(server_message.get("title", ""))}: {server_message.get("message", "")}' + ) + frappe.msgprint(title=title, msg=message) + except: + frappe.msgprint( + title=_("Error: Could not process the response"), + msg=frappe.get_traceback(with_context=1), + ) def format_with_indent(data): @@ -33,8 +57,8 @@ def create_api_log(res, action=None, ref_doctype=None, ref_docname=None): """Can create API log From response Args: - res (response object): It is used to obtain an API response. - request_from (str): It is optional for the purposes of the API... + res (response object): It is used to obtain an API response. + request_from (str): It is optional for the purposes of the API... """ if not isinstance(res, Response): return From 0e0bd260a29d13efa953b1e20dc93db66e4752e3 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Thu, 9 Jan 2025 18:40:55 +0530 Subject: [PATCH 75/83] feat: create default payment type for company refactor: customisation added --- india_banking/default.py | 1 + india_banking/hooks.py | 2 -- .../doctype/payment_type/payment_type.js | 18 ++++++++--- .../doctype/payment_type/payment_type.json | 14 ++++++--- .../doctype/payment_type/payment_type.py | 27 ++++++++++++++-- india_banking/install.py | 31 ++++++++++++++----- india_banking/overrides/journal_entry.py | 4 +-- india_banking/overrides/payment_entry.py | 8 +---- india_banking/overrides/payment_order.py | 4 +-- india_banking/overrides/payment_request.py | 26 +++++++++++++++- .../patches/v1_0/update_custom_fields.py | 3 +- india_banking/public/js/payment_request.js | 10 ++++++ india_banking/public/js/payment_type.js | 13 -------- india_banking/uninstall.py | 1 + 14 files changed, 114 insertions(+), 48 deletions(-) delete mode 100644 india_banking/public/js/payment_type.js diff --git a/india_banking/default.py b/india_banking/default.py index 0906911..2ea81ca 100644 --- a/india_banking/default.py +++ b/india_banking/default.py @@ -38,6 +38,7 @@ ] STD_BANK_LIST = [ + "Axis Bank", "HDFC Bank", "ICICI Bank", "Kotak Mahindra Bank", diff --git a/india_banking/hooks.py b/india_banking/hooks.py index a8d12dd..0af0015 100644 --- a/india_banking/hooks.py +++ b/india_banking/hooks.py @@ -13,7 +13,6 @@ doctype_js = { "Payment Order": "public/js/payment_order.js", "Purchase Invoice": "public/js/purchase_invoice.js", - "Payment Type": "public/js/payment_type.js", "Bank Account": "public/js/bank_account.js", "Payment Request": "public/js/payment_request.js", } @@ -25,7 +24,6 @@ override_doctype_class = { "Payment Order": "india_banking.overrides.payment_order.CustomPaymentOrder", - "Payment Entry": "india_banking.overrides.payment_entry.CustomPaymentEntry", "Payment Request": "india_banking.overrides.payment_request.BankPaymentRequest", } diff --git a/india_banking/india_banking/doctype/payment_type/payment_type.js b/india_banking/india_banking/doctype/payment_type/payment_type.js index ab407a7..723c4dc 100644 --- a/india_banking/india_banking/doctype/payment_type/payment_type.js +++ b/india_banking/india_banking/doctype/payment_type/payment_type.js @@ -1,8 +1,16 @@ // Copyright (c) 2024, Aerele Technologies Private Limited and contributors // For license information, please see license.txt -// frappe.ui.form.on("Payment Type", { -// refresh(frm) { - -// }, -// }); +frappe.ui.form.on("Payment Type", { + refresh(frm) { + frm.set_query("account", function () { + return { + filters: { + is_group: 0, + disabled: 0, + account_type: "Payable", + }, + }; + }); + }, +}); diff --git a/india_banking/india_banking/doctype/payment_type/payment_type.json b/india_banking/india_banking/doctype/payment_type/payment_type.json index e76e066..f25ade0 100644 --- a/india_banking/india_banking/doctype/payment_type/payment_type.json +++ b/india_banking/india_banking/doctype/payment_type/payment_type.json @@ -1,13 +1,13 @@ { "actions": [], "allow_rename": 1, - "autoname": "field:payment_type", "creation": "2024-04-29 16:50:47.339551", "default_view": "List", "doctype": "DocType", "engine": "InnoDB", "field_order": [ "section_break_905w", + "is_default", "payment_type", "account", "company" @@ -22,8 +22,7 @@ "fieldtype": "Data", "in_list_view": 1, "label": "Payment Type", - "reqd": 1, - "unique": 1 + "reqd": 1 }, { "fieldname": "account", @@ -40,15 +39,20 @@ "label": "Company", "options": "Company", "reqd": 1 + }, + { + "default": "0", + "fieldname": "is_default", + "fieldtype": "Check", + "label": "Is Default" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-04-29 16:50:47.339551", + "modified": "2025-01-09 12:08:49.819703", "modified_by": "Administrator", "module": "India Banking", "name": "Payment Type", - "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [ { diff --git a/india_banking/india_banking/doctype/payment_type/payment_type.py b/india_banking/india_banking/doctype/payment_type/payment_type.py index ad3921d..1cfae2d 100644 --- a/india_banking/india_banking/doctype/payment_type/payment_type.py +++ b/india_banking/india_banking/doctype/payment_type/payment_type.py @@ -1,9 +1,32 @@ # Copyright (c) 2024, Aerele Technologies Private Limited and contributors # For license information, please see license.txt -# import frappe +import frappe +from erpnext.accounts.utils import get_autoname_with_number +from frappe import _ from frappe.model.document import Document class PaymentType(Document): - pass + def autoname(self): + self.name = get_autoname_with_number("", self.payment_type, self.company) + + def on_update(self): + if self.is_default: + frappe.db.set_value( + "Payment Type", + {"company": self.company, "name": ["!=", self.name]}, + "is_default", + 0, + ) + else: + if not frappe.db.exists( + "Payment Type", + {"company": self.company, "is_default": 1, "name": ["!=", self.name]}, + ): + frappe.msgprint( + _( + f"set as {frappe.bold(self.name)}, the company {frappe.bold(self.company)} default payment method" + ) + ) + self.db_set("is_default", 1) diff --git a/india_banking/install.py b/india_banking/install.py index e86daff..40dc85f 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -251,8 +251,8 @@ def create_payment_order_custom_fields(): "insert_after": "posting_date", }, { - "label": "ICICI Bank Api Info", - "fieldname": "icici_bank_api_info", + "label": "File Reference Details", + "fieldname": "file_reference_details_section", "fieldtype": "Section Break", "insert_after": "account", }, @@ -261,7 +261,7 @@ def create_payment_order_custom_fields(): "fieldname": "unique_id", "fieldtype": "Data", "hidden": 1, - "insert_after": "icici_bank_api_info", + "insert_after": "file_reference_details_section", }, { "label": "File Reference Id", @@ -558,10 +558,27 @@ def create_default_mode_of_transfers(): def create_default_payment_type(): - if not frappe.db.exists("Payment Type", "Pay"): - frappe.get_doc({"doctype": "Payment Type", "payment_type": "Pay"}).insert( - ignore_permissions=True, ignore_mandatory=True - ) + companies = frappe.get_all( + "Company", ["name", "default_payable_account"], as_list=1 + ) + for company, default_payable_account in companies: + if not frappe.db.exists( + "Payment Type", + { + "payment_type": "Pay", + "account": default_payable_account, + "company": company, + }, + ): + frappe.get_doc( + { + "doctype": "Payment Type", + "payment_type": "Pay", + "company": company, + "account": default_payable_account, + "is_default": 1, + } + ).insert(ignore_permissions=True, ignore_mandatory=True) def create_default_workflow(): diff --git a/india_banking/overrides/journal_entry.py b/india_banking/overrides/journal_entry.py index 0e06d33..b885f2d 100644 --- a/india_banking/overrides/journal_entry.py +++ b/india_banking/overrides/journal_entry.py @@ -44,7 +44,6 @@ def update_bank_entry(source, target): ) .where( (JournalEntryAccount.parent == source.name) - & (JournalEntryAccount.party_type == "Employee") & ( JournalEntryAccount.payment_status.notin( ["Paid", "Ordered", "Payment Ordered"] @@ -74,7 +73,7 @@ def _update_dimensions(source): "reference_name": journal_account.journal, "journal_entry_account": journal_account.name, "amount": journal_account.amount, - "party_type": "Employee", + "party_type": journal_account.party_type, "party": journal_account.employee, "mode_of_payment": "", "bank_account": journal_account.party_bank_account, @@ -120,7 +119,6 @@ def get_bank_entry(doctype, txt, searchfield, start, page_len, filters, as_dict) ) ) & (JournalEntry.voucher_type == "Bank Entry") - & (JournalEntryAccount.party_type == "Employee") & (JournalEntryAccount.against_account == filters.company_account) ) .groupby(JournalEntry.name, JournalEntry.company, JournalEntry.voucher_type) diff --git a/india_banking/overrides/payment_entry.py b/india_banking/overrides/payment_entry.py index ff56ef9..61d5685 100644 --- a/india_banking/overrides/payment_entry.py +++ b/india_banking/overrides/payment_entry.py @@ -2,19 +2,13 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, ) -from erpnext.accounts.doctype.payment_entry.payment_entry import PaymentEntry from erpnext.accounts.party import get_party_bank_account from frappe import _ - - -class CustomPaymentEntry(PaymentEntry): - pass +from frappe.model.mapper import get_mapped_doc @frappe.whitelist() def make_payment_order(source_name, target_doc=None): - from frappe.model.mapper import get_mapped_doc - def set_missing_values(source, target): target.payment_order_type = "Payment Entry" target.company_bank_account = source.bank_account diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index ad154ac..fe1e862 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -113,10 +113,10 @@ def get_party_field_name(self, party): return "employee_name" elif party.party_type == "Shareholder": return "name" - elif party.part_type == "Customer": + elif party.party_type == "Customer": return "customer_name" else: - frappe.throw(_(f"Unsupported party type {party.party_type}")) + return "name" def on_submit(self): if self.payment_order_type in [ diff --git a/india_banking/overrides/payment_request.py b/india_banking/overrides/payment_request.py index 50e56f3..9bce27f 100644 --- a/india_banking/overrides/payment_request.py +++ b/india_banking/overrides/payment_request.py @@ -8,10 +8,12 @@ ) from erpnext.accounts.party import get_party_bank_account from frappe import _ +from frappe.utils import get_link_to_form class BankPaymentRequest(PaymentRequest): def validate(self): + self.set_default_value() if not self.net_total: self.net_total = self.grand_total @@ -39,8 +41,30 @@ def validate(self): self.valdidate_bank_for_wire_transfer() + def set_default_value(self): + if not self.mode_of_payment: + self.db_set("mode_of_payment", "Wire Transfer") + if not self.payment_type and ( + payment_type := frappe.db.exists( + "Payment Type", + { + "company": self.company, + "is_default": 1, + }, + ) + ): + self.db_set("payment_type", payment_type) + else: + frappe.throw( + _( + f"Set Default Payment Type for company {frappe.bold(self.company)}".format( + self.company + ) + ) + ) + def on_submit(self): - if not self.grand_total: + if not self.grand_total or not self.net_total: frappe.throw(_("Amount cannot be zero")) bank_account = get_party_bank_account(self.party_type, self.party) diff --git a/india_banking/patches/v1_0/update_custom_fields.py b/india_banking/patches/v1_0/update_custom_fields.py index b462919..ffc264a 100644 --- a/india_banking/patches/v1_0/update_custom_fields.py +++ b/india_banking/patches/v1_0/update_custom_fields.py @@ -1,10 +1,11 @@ import frappe -from india_banking.install import make_custom_fields +from india_banking.install import make_custom_fields, toggle_payment_request_creation from india_banking.uninstall import delete_custom_fields def execute(): + toggle_payment_request_creation() delete_custom_fields() remove_custom_section_and_column_break_fields() remove_main_field_order() diff --git a/india_banking/public/js/payment_request.js b/india_banking/public/js/payment_request.js index 7b81736..f022928 100644 --- a/india_banking/public/js/payment_request.js +++ b/india_banking/public/js/payment_request.js @@ -56,6 +56,16 @@ function get_bank_query_conditions(frm) { is_default: 1, disabled: 0, }; + frappe.db + .get_single_value( + "India Banking Settings", + "activate_workflow_on_bank_account" + ) + .then((r) => { + if (r) { + conditions["workflow_state"] = "Approved"; + } + }); if (frm.doc.party_type) { conditions["party_type"] = frm.doc.party_type; } diff --git a/india_banking/public/js/payment_type.js b/india_banking/public/js/payment_type.js deleted file mode 100644 index da68b86..0000000 --- a/india_banking/public/js/payment_type.js +++ /dev/null @@ -1,13 +0,0 @@ -frappe.ui.form.on("Payment Type", { - refresh(frm) { - frm.set_query("account", function () { - return { - filters: { - is_group: 0, - disabled: 0, - account_type: "Payable", - }, - }; - }); - }, -}); diff --git a/india_banking/uninstall.py b/india_banking/uninstall.py index 07c6dfa..2cb756a 100644 --- a/india_banking/uninstall.py +++ b/india_banking/uninstall.py @@ -83,6 +83,7 @@ def delete_custom_fields(): "total", "status", "icici_bank_api_info", + "file_reference_details_section", "payment_summary", "bank_api_info_column_break", "payment_summary_column_break", From 2d84dc0d0ab12a2d9eb1a4f5ea14dbcf00f54169 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 10 Jan 2025 15:33:12 +0530 Subject: [PATCH 76/83] refactor: remove custom field deletetion without checking filter. --- .../patches/v1_0/update_custom_fields.py | 35 +------------------ 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/india_banking/patches/v1_0/update_custom_fields.py b/india_banking/patches/v1_0/update_custom_fields.py index ffc264a..dd852a5 100644 --- a/india_banking/patches/v1_0/update_custom_fields.py +++ b/india_banking/patches/v1_0/update_custom_fields.py @@ -7,37 +7,4 @@ def execute(): toggle_payment_request_creation() delete_custom_fields() - remove_custom_section_and_column_break_fields() - remove_main_field_order() - make_custom_fields() - - -def remove_custom_section_and_column_break_fields(): - if fields := frappe.get_all( - "Custom Field", - filters={ - "dt": [ - "in", - [ - "Journal Entry Account", - "Payment Entry", - "Payment Order", - "Payment Order Reference", - "Payment Request", - ], - ], - "fieldtype": ["in", ["Section Break", "Column Break"]], - }, - pluck="name", - ): - frappe.db.delete("Custom Field", {"name": ["in", fields]}) - - -def remove_main_field_order(): - doctypes = [ - "Payment Order", - "Payment Order Reference", - "Payment Request", - ] - for doctype in doctypes: - frappe.db.delete("Property Setter", {"name": f"{doctype}-main-field_order"}) + make_custom_fields() \ No newline at end of file From 945488176b98f08bdbfb0b0890e383864e57e5d8 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 10 Jan 2025 16:08:30 +0530 Subject: [PATCH 77/83] chore: rename bank_balance key to get_bank_balance --- .../india_banking/doctype/bank_connector/bank_connector.py | 4 ++-- india_banking/public/js/bank_account.js | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/india_banking/india_banking/doctype/bank_connector/bank_connector.py b/india_banking/india_banking/doctype/bank_connector/bank_connector.py index 7a9f7c7..ffbe4da 100644 --- a/india_banking/india_banking/doctype/bank_connector/bank_connector.py +++ b/india_banking/india_banking/doctype/bank_connector/bank_connector.py @@ -746,14 +746,14 @@ def notify_party(self, summary_row): def get_bank_balance(self, bank_account): payload = { "bank_account_number": bank_account.bank_account_no, - "method": "bank_balance", + "method": "get_bank_balance", } response = request.post( self.base_url, headers=self.headers, data=json.dumps(payload) ) # create api request log create_api_log( - response, "Get Payment Status", "Bank Account", bank_account.bank_account_no + response, "Get Bank Balance", "Bank Account", bank_account.bank_account_no ) if response.status_code == 200: diff --git a/india_banking/public/js/bank_account.js b/india_banking/public/js/bank_account.js index 3a920ba..a9500aa 100644 --- a/india_banking/public/js/bank_account.js +++ b/india_banking/public/js/bank_account.js @@ -1,7 +1,6 @@ frappe.ui.form.on("Bank Account", { refresh(frm) { if ( - frm.doc.is_company_account && frm.doc.is_company_account && !frm.doc.disabled ) { @@ -11,7 +10,7 @@ frappe.ui.form.on("Bank Account", { "india_banking.india_banking.doctype.bank_connector.bank_connector.get_bank_balance", freeze: true, args: { - bank_name: frm.doc.name, + bank_account_name: frm.doc.name, }, callback: (res) => { cur_frm.reload_doc(); From 7e7e99c8eade9c976dae0da3f182e5c592661f49 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 10 Jan 2025 18:20:46 +0530 Subject: [PATCH 78/83] fix: change 'or' condition instead of 'and' --- india_banking/overrides/payment_request.py | 5 +---- india_banking/public/js/payment_order.js | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/india_banking/overrides/payment_request.py b/india_banking/overrides/payment_request.py index 9bce27f..c485da6 100644 --- a/india_banking/overrides/payment_request.py +++ b/india_banking/overrides/payment_request.py @@ -8,7 +8,6 @@ ) from erpnext.accounts.party import get_party_bank_account from frappe import _ -from frappe.utils import get_link_to_form class BankPaymentRequest(PaymentRequest): @@ -42,9 +41,7 @@ def validate(self): self.valdidate_bank_for_wire_transfer() def set_default_value(self): - if not self.mode_of_payment: - self.db_set("mode_of_payment", "Wire Transfer") - if not self.payment_type and ( + if not self.payment_type or ( payment_type := frappe.db.exists( "Payment Type", { diff --git a/india_banking/public/js/payment_order.js b/india_banking/public/js/payment_order.js index d433114..f1e467b 100644 --- a/india_banking/public/js/payment_order.js +++ b/india_banking/public/js/payment_order.js @@ -122,6 +122,7 @@ frappe.ui.form.on("Payment Order", { party_type: "", party: "", grand_total: "", + currency: "INR", }, get_query_filters: { docstatus: 1, From a5a5f719a151329ee40e52d1eb69cfd5c8c5b09e Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Mon, 13 Jan 2025 15:39:34 +0530 Subject: [PATCH 79/83] fix: update currency filter for payment cycle --- india_banking/hooks.py | 2 +- .../india_banking/doc_events/bank_account.py | 25 +++- india_banking/install.py | 10 +- india_banking/overrides/journal_entry.py | 42 ++++++- india_banking/overrides/payment_entry.py | 32 ++++- india_banking/overrides/payment_request.py | 119 ++++++++++++++---- india_banking/uninstall.py | 1 + india_banking/utils.py | 5 + 8 files changed, 201 insertions(+), 35 deletions(-) diff --git a/india_banking/hooks.py b/india_banking/hooks.py index 0af0015..b8620d7 100644 --- a/india_banking/hooks.py +++ b/india_banking/hooks.py @@ -32,7 +32,7 @@ "on_trash": "india_banking.india_banking.doc_events.bank.disallow_standard_bank_deletion" }, "Bank Account": { - "validate": "india_banking.india_banking.doc_events.bank_account.validate_ifsc_code" + "validate": "india_banking.india_banking.doc_events.bank_account.validate" }, } diff --git a/india_banking/india_banking/doc_events/bank_account.py b/india_banking/india_banking/doc_events/bank_account.py index b58c79e..ac22aeb 100644 --- a/india_banking/india_banking/doc_events/bank_account.py +++ b/india_banking/india_banking/doc_events/bank_account.py @@ -1,12 +1,29 @@ -import frappe +import re -from frappe.utils import cstr +import frappe from frappe import _ -import re +from frappe.utils import cstr IFSC_PATTERN = re.compile(r"^[A-Z]{4}0[A-Z0-9]{6}$") -def validate_ifsc_code(doc, method=None): +def validate(doc, method=None): + validate_ifsc_code(doc) + update_party_transaction_currency(doc) + + +def validate_ifsc_code(doc): if not IFSC_PATTERN.match(cstr(doc.branch_code)): frappe.throw(_("IFSC/Branch Code is not valid")) + + +def update_party_transaction_currency(doc): + if doc.party_type and doc.party: + currency_field = ( + "salary_currency" if doc.party_type == "Employee" else "default_currency" + ) + doc.currency = frappe.get_value( + doc.party_type, doc.party, currency_field + ) or frappe.db.get_value("Company", doc.company, "default_currency") + elif doc.is_company_account: + doc.currency = frappe.db.get_value("Company", doc.company, "default_currency") diff --git a/india_banking/install.py b/india_banking/install.py index 40dc85f..2081e3b 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -489,9 +489,9 @@ def create_bank_account_custom_fields(): { "label": "Mobile Number", "fieldname": "mobile_number", - "insert_after": "iban", "mandatory_depends_on": "is_company_account", "fieldtype": "Data", + "insert_after": "iban", }, { "label": "Email", @@ -508,6 +508,14 @@ def create_bank_account_custom_fields(): "insert_after": "bank_account_no", "read_only": 1, }, + { + "label": "Currency", + "fieldname": "currency", + "fieldtype": "Link", + "options": "Currency", + "insert_after": "email", + "reqd": 1, + }, ] } diff --git a/india_banking/overrides/journal_entry.py b/india_banking/overrides/journal_entry.py index b885f2d..b3a4fd2 100644 --- a/india_banking/overrides/journal_entry.py +++ b/india_banking/overrides/journal_entry.py @@ -2,8 +2,10 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, ) +from frappe import _ from frappe.query_builder import DocType from frappe.query_builder.functions import Sum +from frappe.utils import get_url_to_form @frappe.whitelist() @@ -20,7 +22,7 @@ def update_bank_entry(source, target): "cost_center", "project", "debit as amount", - "party as employee", + "party", "party_type", "parent as journal", ] @@ -67,6 +69,42 @@ def _update_dimensions(source): } for journal_account in journal_accounts: + bank_account = frappe.get_doc( + "Bank Account", journal_account.party_bank_account + ) + if frappe.db.get_single_value( + "India Banking Settings", "activate_workflow_on_bank_account" + ): + if bank_account.workflow_state != "Approved": + frappe.throw( + title=_("Cannot proceed with un-approved bank account"), + msg=_( + "{}-{}- Bank Account {}".format( + journal_account.party_type, + journal_account.party, + get_url_to_form( + "Bank Account", journal_account.party_bank_account + ), + frappe.bold(journal_account.party_bank_account), + ) + ), + ) + + if bank_account.currency != "INR": + frappe.throw( + title=_("The party bank account currency should be in INR."), + msg=_( + "{}-{}- Bank Account {}".format( + journal_account.party_type, + journal_account.party, + get_url_to_form( + "Bank Account", journal_account.party_bank_account + ), + frappe.bold(journal_account.party_bank_account), + ) + ), + ) + journal_account = frappe._dict(journal_account) details = { "reference_doctype": "Journal Entry", @@ -74,7 +112,7 @@ def _update_dimensions(source): "journal_entry_account": journal_account.name, "amount": journal_account.amount, "party_type": journal_account.party_type, - "party": journal_account.employee, + "party": journal_account.party, "mode_of_payment": "", "bank_account": journal_account.party_bank_account, "account": journal_account.account, diff --git a/india_banking/overrides/payment_entry.py b/india_banking/overrides/payment_entry.py index 61d5685..a98c104 100644 --- a/india_banking/overrides/payment_entry.py +++ b/india_banking/overrides/payment_entry.py @@ -5,6 +5,7 @@ from erpnext.accounts.party import get_party_bank_account from frappe import _ from frappe.model.mapper import get_mapped_doc +from frappe.utils import get_url_to_form @frappe.whitelist() @@ -32,7 +33,36 @@ def _get_default_bank_account(party_type, party): ) ) ) - return party_bank_account + + bank_account = frappe.get_doc("Bank Account", party_bank_account) + if frappe.db.get_single_value( + "India Banking Settings", "activate_workflow_on_bank_account" + ): + if bank_account.workflow_state != "Approved": + frappe.throw( + title=_("Cannot proceed with un-approved bank account"), + msg=_( + "{}-{}- Bank Account {}".format( + party_type, + party, + get_url_to_form("Bank Account", bank_account.name), + frappe.bold(bank_account.name), + ) + ), + ) + + if bank_account.currency != "INR": + frappe.throw( + title=_("The party bank account currency should be in INR."), + msg=_( + "{}-{}- Bank Account {}".format( + party_type, + party, + get_url_to_form("Bank Account", bank_account.name), + frappe.bold(bank_account.name), + ) + ), + ) def _get_reference_data(reference=None): return { diff --git a/india_banking/overrides/payment_request.py b/india_banking/overrides/payment_request.py index c485da6..0cdadb0 100644 --- a/india_banking/overrides/payment_request.py +++ b/india_banking/overrides/payment_request.py @@ -8,6 +8,7 @@ ) from erpnext.accounts.party import get_party_bank_account from frappe import _ +from frappe.utils import get_url_to_form class BankPaymentRequest(PaymentRequest): @@ -41,24 +42,39 @@ def validate(self): self.valdidate_bank_for_wire_transfer() def set_default_value(self): - if not self.payment_type or ( - payment_type := frappe.db.exists( + if not self.payment_type: + if payment_type := frappe.db.exists( "Payment Type", { "company": self.company, "is_default": 1, }, - ) - ): - self.db_set("payment_type", payment_type) - else: - frappe.throw( - _( - f"Set Default Payment Type for company {frappe.bold(self.company)}".format( - self.company + ): + self.payment_type = payment_type + else: + frappe.throw( + _( + f"Set Default Payment Type for company {frappe.bold(self.company)}".format( + self.company + ) ) ) - ) + + filters = { + "party_type": self.party_type, + "party": self.party, + "is_default": 1, + "disabled": 0, + "currency": self.currency, + } + + if frappe.db.get_single_value( + "India Banking Settings", "activate_workflow_on_bank_account" + ): + filters["workflow_state"] = "Approved" + + if not self.bank_account: + self.bank_account = frappe.get_value("Bank Account", filters, "name") def on_submit(self): if not self.grand_total or not self.net_total: @@ -69,11 +85,43 @@ def on_submit(self): frappe.throw( _( "Default Bank Account is missing for {0} - {1}".format( - self.party_type, self.party + self.party_type, frappe.bold(self.party) ) ) ) + bank_account = frappe.get_doc("Bank Account", bank_account) + if frappe.db.get_single_value( + "India Banking Settings", "activate_workflow_on_bank_account" + ): + if bank_account.workflow_state != "Approved": + frappe.throw( + title=_("Cannot proceed with un-approved bank account"), + msg=_( + "{}-{}- Bank Account {}".format( + self.party_type, + self.party, + get_url_to_form("Bank Account", bank_account), + frappe.bold(bank_account), + ) + ), + ) + + if bank_account.currency != self.currency: + frappe.throw( + title=_( + f"The party bank account currency should be in {self.currency}." + ), + msg=_( + "{}-{}- Bank Account {}".format( + self.party_type, + self.party, + get_url_to_form("Bank Account", bank_account.name), + frappe.bold(bank_account.name), + ) + ), + ) + debit_account = frappe.db.get_value( "Payment Type", self.payment_type, "account" ) or frappe.db.get_value( @@ -116,24 +164,43 @@ def calculate_pr_tds(self, amount): return 0 def valdidate_bank_for_wire_transfer(self): - if self.mode_of_payment == "Wire Transfer" and not self.bank_account: - frappe.throw(_("Bank Account is missing for Wire Transfer Payments")) + if self.mode_of_payment == "Wire Transfer": + if not self.bank_account: + frappe.throw(_("Bank Account is missing for Wire Transfer Payments")) - try: - status = frappe.db.get_value( - "Bank Account", self.bank_account, "workflow_state" - ) + bank_account = frappe.get_doc("Bank Account", self.bank_account) if ( - self.mode_of_payment == "Wire Transfer" - and status != "Approved" - and frappe.get_single( - "India Banking Settings" - ).activate_workflow_on_bank_account + frappe.db.get_single_value( + "India Banking Settings", "activate_workflow_on_bank_account" + ) + and bank_account.workflow_state != "Approved" ): - frappe.throw(_("Cannot proceed with un-approved bank account")) - except Exception: - frappe.throw(_("Workflow Not Found for Bank Account")) + frappe.throw( + title=_("Cannot proceed with un-approved bank account"), + msg=_( + "{}-{}- Bank Account {}".format( + self.party_type, + self.party, + get_url_to_form("Bank Account", self.bank_account), + frappe.bold(self.bank_account), + ) + ), + ) + if bank_account.currency != self.currency: + frappe.throw( + title=_( + f"The party bank account currency should be in {self.currency}." + ), + msg=_( + "{}-{}- Bank Account {}".format( + bank_account.party_type, + bank_account.party, + get_url_to_form("Bank Account", self.bank_account), + frappe.bold(self.bank_account), + ) + ), + ) @frappe.whitelist() diff --git a/india_banking/uninstall.py b/india_banking/uninstall.py index 2cb756a..fc74502 100644 --- a/india_banking/uninstall.py +++ b/india_banking/uninstall.py @@ -49,6 +49,7 @@ def delete_custom_fields(): "mobile_number", "email", "bank_balance", + "currency", ], "Payment Entry": [ "source_section", diff --git a/india_banking/utils.py b/india_banking/utils.py index b96b396..1ea752d 100644 --- a/india_banking/utils.py +++ b/india_banking/utils.py @@ -14,20 +14,25 @@ def get_bank_address_details(bank_account): address_line = party_address_.get("address_line1", "").split(",") street_name = party_address_.get("city", "") building_number = address_line[0] if address_line else "" + if len(building_number) > 10: building_number = building_number[:10] + post_code = party_address_.get("pincode", "") + town_name = ( party_address_.get("state", "")[:3].upper() if party_address_.get("state", "") else "" ) + country_sub_division = ( [party_address_.get("country", "")[:2]] if party_address_.get("country", "") else [] ) country = party_address_.get("country", "")[:2] + return { "AddressLine": address_line, "StreetName": street_name, From d91e258272b307a8cd8db6616a2f6e4f8cad6cbc Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 14 Jan 2025 19:59:19 +0530 Subject: [PATCH 80/83] refactor: remove unique id and filereference id fields --- .../doctype/bank_connector/bank_connector.py | 1 - india_banking/install.py | 24 ++++--------------- india_banking/overrides/payment_order.py | 8 ------- india_banking/uninstall.py | 1 + 4 files changed, 6 insertions(+), 28 deletions(-) diff --git a/india_banking/india_banking/doctype/bank_connector/bank_connector.py b/india_banking/india_banking/doctype/bank_connector/bank_connector.py index ffbe4da..9f2e8da 100644 --- a/india_banking/india_banking/doctype/bank_connector/bank_connector.py +++ b/india_banking/india_banking/doctype/bank_connector/bank_connector.py @@ -599,7 +599,6 @@ def verify_otp(self, payment_order, otp): pass def generate_otp(self, payment_order): - payment_order.update_unique_and_file_reference_id(save=True) payment_order.reload() # Generate OTP using POST request diff --git a/india_banking/install.py b/india_banking/install.py index 2081e3b..aef1aaf 100644 --- a/india_banking/install.py +++ b/india_banking/install.py @@ -257,30 +257,16 @@ def create_payment_order_custom_fields(): "insert_after": "account", }, { - "label": "Unique ID", - "fieldname": "unique_id", + "label": "File Sequence Number", + "fieldname": "file_sequence_number", "fieldtype": "Data", - "hidden": 1, + "read_only": 1, "insert_after": "file_reference_details_section", }, - { - "label": "File Reference Id", - "fieldname": "file_reference_id", - "hidden": 1, - "fieldtype": "Data", - "insert_after": "unique_id", - }, { "fieldtype": "Column Break", - "fieldname": "bank_api_info_column_break", - "insert_after": "file_reference_id", - }, - { - "label": "File Sequence Number", - "fieldname": "file_sequence_number", - "fieldtype": "Data", - "read_only": 1, - "insert_after": "bank_api_info_column_break", + "fieldname": "file_reference_details_column", + "insert_after": "file_sequence_number", }, { "label": "Payment Summary", diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index fe1e862..a3625b1 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -1,5 +1,4 @@ import json -import re import frappe from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( @@ -15,7 +14,6 @@ class CustomPaymentOrder(PaymentOrder): def before_submit(self): self.validate_bank_payment_request() - self.update_unique_and_file_reference_id() def validate_bank_payment_request(self): if self.references: @@ -29,12 +27,6 @@ def validate_bank_payment_request(self): message = f"The amount in #Row{ref.idx} does not match the amount of the Payment Request -{link}. The Difference is {ref.amount - payment_request.grand_total}" frappe.throw(title=_("Invalid Amount"), msg=_(message)) - @frappe.whitelist() - def update_unique_and_file_reference_id(self): - unique_id = "".join(re.findall(r"[0-9a-zA-Z]", self.name))[-10:] - self.unique_id = unique_id - self.file_reference_id = unique_id - def validate(self): self.validate_summary() diff --git a/india_banking/uninstall.py b/india_banking/uninstall.py index fc74502..774340c 100644 --- a/india_banking/uninstall.py +++ b/india_banking/uninstall.py @@ -87,6 +87,7 @@ def delete_custom_fields(): "file_reference_details_section", "payment_summary", "bank_api_info_column_break", + "file_reference_details_column", "payment_summary_column_break", "amended_from", ], From 3bcd08aa2594d3e0b75e0ad08fb0850347dfa149 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Wed, 15 Jan 2025 16:45:13 +0530 Subject: [PATCH 81/83] refactor: modify the payment api structure --- .../doctype/bank_connector/bank_connector.py | 791 +++++++----------- .../india_banking_request_log.py | 29 +- india_banking/utils.py | 40 + 3 files changed, 328 insertions(+), 532 deletions(-) diff --git a/india_banking/india_banking/doctype/bank_connector/bank_connector.py b/india_banking/india_banking/doctype/bank_connector/bank_connector.py index 9f2e8da..4b553d1 100644 --- a/india_banking/india_banking/doctype/bank_connector/bank_connector.py +++ b/india_banking/india_banking/doctype/bank_connector/bank_connector.py @@ -8,13 +8,17 @@ import requests as request from frappe import _ from frappe.model.document import Document -from frappe.utils import comma_and, cstr, get_link_to_form, getdate +from frappe.utils import cstr, getdate from frappe.utils.background_jobs import is_job_enqueued from india_banking.india_banking.doctype.india_banking_request_log.india_banking_request_log import ( create_api_log, ) -from india_banking.utils import get_bank_address_details +from india_banking.utils import ( + extract_error_message, + get_bank_address_details, + get_party_field_name, +) OTP_ENABLED_BANK = [ ("ICICI Bank", 1), # ICICI Bank, Bulk Transaction @@ -37,7 +41,7 @@ def headers(self): } @property - def base_url(self): + def connector_url(self): return f"{self.url}/api/method/india_banking_connector.api.connect" def check_otp_enabled(self, otp=None): @@ -46,337 +50,336 @@ def check_otp_enabled(self, otp=None): elif (self.bank, self.bulk_transaction) in OTP_ENABLED_BANK and not otp: frappe.throw(_("OTP is required for this transaction")) - def make_payment(self, payment_order, otp=None): + def verify_otp(self, payment_order, otp): + pass + + def get_payload(self, payment_order, action=None): + bank_account = frappe.get_doc( + "Bank Account", payment_order.company_bank_account + ) + payment_payload = frappe._dict() + payment_payload.doc = payment_order.as_dict(convert_dates_to_str=True) + payment_payload.doc.update( + { + "company_account_number": bank_account.bank_account_no, + "company_bank_account_name": bank_account.account_name, + "company_ifsc": bank_account.branch_code, + "mobile_number": bank_account.mobile_number, + } + ) + payment_payload.method = self.get("action", "") or action + payment_payload.bulk_transaction = self.bulk_transaction + + return payment_payload + + def get_response_details(self, response): + try: + return frappe._dict(response.json().get("message")) + except: + frappe.throw(_("Invalid Response: Check API Log")) + + def make_post_request(self, payment_order, otp=None, action=None): self.check_user_permission() - if self.check_otp_enabled(otp): - return self.generate_otp(payment_order) + if action == "intiate_payment": + if self.check_otp_enabled(otp): + return self.generate_otp(payment_order) + + action = "get_payment_status" + self.make_post_request(payment_order, otp, action) + action = "intiate_payment" + + self.action = action if otp: self.verify_otp(payment_order, otp) - frappe.flags.ignore_message = True - self.get_payment_status(payment_order) - frappe.flags.ignore_message = False - - # Make the payment if self.bulk_transaction: - return self.make_bulk_payment(payment_order, otp) - else: - return self.make_single_payment(payment_order) + url = self.connector_url + headers = self.headers - def get_payment_status(self, payment_order): - self.check_user_permission() - if self.bulk_transaction: - return self.get_bulk_payment_status(payment_order) + payload = self.get_payload(payment_order) + + response = request.post(url, headers=headers, data=json.dumps(payload)) + + # create api request log + create_api_log( + response, self.action, payment_order.doctype, payment_order.name + ) + + self.verify_response(response, payment_order) else: - return self.get_single_payment_status(payment_order) + # add payment in background + if ( + len(payment_order.summary) > 10 + or frappe.get_single( + "India Banking Settings" + ).enable_payment_in_the_background + ): + return self.add_payment_in_the_background(payment_order) - def get_single_payment_status(self, payment_order): - for summary_row in payment_order.summary: - if summary_row.payment_status in ["Initiated", "Pending"]: - self.get_status_response(summary_row, payment_order) + for summary in payment_order.summary: + if ( + not summary.payment_initiated + and summary.payment_status != "Pending" + ): + continue - payment_order.reload() - self.update_payment_status(payment_order) - if not frappe.flags.ignore_message: - frappe.msgprint(_("Payment Status Updated")) + self.make_single_request(payment_order, summary) - def get_bulk_payment_status(self, payment_order): - response = request.post( - self.base_url, - headers=self.headers, - data=json.dumps(self.get_payload(payment_order, "get_payment_status")), - ) + def verify_response(self, response, payment_order): + if self.action == "intiate_payment": + self.verify_payment_response(response, payment_order) + elif self.action == "get_payment_status": + self.verify_status_response(response, payment_order) - # create api request log - create_api_log( - response, "Get Payment Status", payment_order.doctype, payment_order.name - ) + self.update_payment_status(payment_order) + + def verify_payment_response(self, response, payment_order): + payment_response = self.get_response_details(response) if response.ok: - status_response = self.get_response_details(response) - payment_status_details = status_response.payment_status_details + payment_status = payment_response.get("payment_status", "") + message = payment_response.get("message", "") - if status_response.status == "Processed": - frappe.msgprint( - _(cstr(status_response.message)), - _(cstr(status_response.file_status)), - ) - fs = status_response.file_status - if status_response.file_status in ["FAL", "REJ", "REC"]: - for summary_row in payment_order.summary: + file_sequence_number = payment_response.get("file_sequence_number", "") + + summary_details = frappe._dict(payment_response.get("summary_details", {})) + + if payment_status == "ACCEPTED": + if self.bulk_transaction: + frappe.db.set_value( + "Payment Order", + payment_order.name, + { + "status": "Initiated", + "file_sequence_number": file_sequence_number, + }, + ) + + for _name, details in summary_details.items(): + if details.get("payment_status", "") == "Accepted": frappe.db.set_value( "Payment Order Summary", - summary_row.name, - "payment_status", - "Failed" if fs == "FAL" else "Rejected", + _name, + { + "payment_status": "Initiated", + "payment_date": getdate(), + "payment_initiated": 1, + "message": details.get("message", ""), + }, ) - payment_entry_doc = frappe.get_doc( - "Payment Entry", summary_row.payment_entry + elif details.get("payment_status", "") == "Failed": + frappe.db.set_value( + "Payment Order Summary", + _name, + { + "payment_status": "Pending", + "payment_initiated": 1, + "message": details.get("message", ""), + }, ) - if payment_entry_doc.docstatus == 1: - payment_entry_doc.cancel() - self.process_bank_payment_requests(payment_order, summary_row) - - if payment_status_details: - for summary_row in payment_order.summary: - summary_row_payment_status = frappe._dict( - payment_status_details.get(summary_row.name, {}) + elif details.get("payment_status", "") == "Request Failure": + frappe.db.set_value( + "Payment Order Summary", + _name, + { + "payment_status": "Pending", + "message": details.get("message", ""), + }, ) - if ( - summary_row.payment_status == "Initiated" - and summary_row_payment_status - ): - if summary_row_payment_status.transaction_status == "SUC": - frappe.db.set_value( - "Payment Order Summary", - summary_row.name, - { - "reference_number": summary_row_payment_status.host_reference_number, - "payment_status": "Processed", - "message": summary_row_payment_status.host_response_message, - }, - ) + else: + frappe.db.set_value( + "Payment Order Summary", + _name, + { + "payment_status": "Failed", + "payment_initiated": 1, + "message": details.get("message", ""), + }, + ) + + elif payment_status == "FAILED": + frappe.msgprint( + title=_("Failed"), + msg=_(message), + indicator="red", + ) + + else: + extract_error_message(response.json(), show_message=True) + + else: + frappe.throw("Connection Request") + + def verify_status_response(self, response, payment_order): + payment_response = self.get_response_details(response) + + if response.ok: + payment_status = payment_response.get("payment_status", "") + message = payment_response.get("message", "") + + summary_details = frappe._dict(payment_response.get("summary_details", {})) + + if payment_status == "PROCESSED": + for summary in payment_order: + status_details = frappe._dict(summary_details.get(summary.name, "")) + if status_details.status == "Processed": + if status_details.utr_number: + frappe.db.set_value( + "Payment Order Summary", + summary.name, + "reference_number", + status_details.utr_number, + ) + if summary.payment_entry: frappe.db.set_value( "Payment Entry", - summary_row.payment_entry, + summary.payment_entry, "reference_no", - summary_row_payment_status.host_reference_number, + status_details.utr_number, ) - elif summary_row_payment_status.transaction_status == "FAL": + if summary.journal_entry_account: frappe.db.set_value( - "Payment Order Summary", - summary_row.name, - "payment_status", - "Failed", - ) - payment_entry_doc = frappe.get_doc( - "Payment Entry", summary_row.payment_entry - ) - if payment_entry_doc.docstatus == 1: - payment_entry_doc.cancel() - self.process_bank_payment_requests( - payment_order, summary_row - ) - - elif summary_row_payment_status.transaction_status in [ - "RVS", - "REJ", - ]: - frappe.db.set_value( - "Payment Order Summary", - summary_row.name, - "payment_status", - "Rejected", - ) - payment_entry_doc = frappe.get_doc( - "Payment Entry", summary_row.payment_entry - ) - if payment_entry_doc.docstatus == 1: - payment_entry_doc.cancel() - self.process_bank_payment_requests( - payment_order, summary_row + "Journal Entry Account", + summary.journal_entry_account, + { + "payment_status": "Paid", + "reference_number": status_details.utr_number, + }, ) - self.update_payment_status(payment_order) - else: - frappe.throw( - msg=_(cstr(status_response.server_message)), title=_("Failed") - ) - else: - frappe.throw(_("Invalid Request")) + self.notify_party(summary) - def get_status_response(self, summary_row, payment_order): - status_payload = self.get_payload(payment_order, "get_payment_status") - status_payload.update(summary_row.as_dict(convert_dates_to_str=True)) - response = request.post( - self.base_url, - headers=self.headers, - data=json.dumps(status_payload), - ) - - # create api request log - create_api_log( - response, "Get Payment Status", payment_order.doctype, payment_order.name - ) - - if response.ok: - status_response = self.get_response_details(response) - if status_response.status == "Processed": - if status_response.utr_number: - frappe.db.set_value( - "Payment Order Summary", - summary_row.name, - "reference_number", - status_response.utr_number, - ) - if summary_row.payment_entry: frappe.db.set_value( - "Payment Entry", - summary_row.payment_entry, - "reference_no", - status_response.utr_number, + "Payment Order Summary", + summary.name, + "payment_status", + "Processed", + ) + elif status_details.status == "Pending": + frappe.db.set_value( + "Payment Order Summary", + summary.name, + "message", + status_details.message, ) - if summary_row.journal_entry_account: + + elif status_details.status == "Failed": frappe.db.set_value( - "Journal Entry Account", - summary_row.journal_entry_account, + "Payment Order Summary", + summary.name, { - "payment_status": "Paid", - "reference_number": status_response.utr_number, + "payment_status": status_details.status, + "message": status_details.message, }, ) - self.notify_party(summary_row) + if summary.payment_entry: + payment_entry_doc = frappe.get_doc( + "Payment Entry", summary.payment_entry + ) + if payment_entry_doc.docstatus == 1: + payment_entry_doc.cancel() + self.process_bank_payment_requests(payment_order, summary) + + if summary.journal_entry_account: + frappe.db.set_value( + "Journal Entry Account", + summary.journal_entry_account, + "payment_status", + "Failed", + ) - frappe.db.set_value( - "Payment Order Summary", - summary_row.name, - "payment_status", - "Processed", - ) - elif status_response.status == "Pending": - frappe.db.set_value( - "Payment Order Summary", - summary_row.name, - "message", - status_response.message, - ) + elif status_details.status == "Rejected": + frappe.db.set_value( + "Payment Order Summary", + summary.name, + { + "payment_status": status_details.status, + "message": status_details.message, + }, + ) - elif status_response.status == "Failed": - frappe.db.set_value( - "Payment Order Summary", - summary_row.name, - { - "payment_status": status_response.status, - "message": status_response.message, - }, - ) + if summary.payment_entry: + payment_entry_doc = frappe.get_doc( + "Payment Entry", summary.payment_entry + ) + if payment_entry_doc.docstatus == 1: + payment_entry_doc.cancel() - if summary_row.payment_entry: - payment_entry_doc = frappe.get_doc( - "Payment Entry", summary_row.payment_entry - ) - if payment_entry_doc.docstatus == 1: - payment_entry_doc.cancel() - self.process_bank_payment_requests(payment_order, summary_row) + self.process_bank_payment_requests(payment_order, summary) - if summary_row.journal_entry_account: - frappe.db.set_value( - "Journal Entry Account", - summary_row.journal_entry_account, - "payment_status", - "Failed", - ) + if summary.journal_entry_account: + frappe.db.set_value( + "Journal Entry Account", + summary.journal_entry_account, + "payment_status", + "Failed", + ) - elif status_response.status == "Rejected": - frappe.db.set_value( - "Payment Order Summary", - summary_row.name, - { - "payment_status": status_response.status, - "message": status_response.message, - }, + elif payment_status == "FAILED": + frappe.msgprint( + title=_("Failed"), + msg=_(message), + indicator="red", ) - if summary_row.payment_entry: - payment_entry_doc = frappe.get_doc( - "Payment Entry", summary_row.payment_entry - ) - if payment_entry_doc.docstatus == 1: - payment_entry_doc.cancel() - - self.process_bank_payment_requests(payment_order, summary_row) + else: + extract_error_message(response.json()) - if summary_row.journal_entry_account: - frappe.db.set_value( - "Journal Entry Account", - summary_row.journal_entry_account, - "payment_status", - "Failed", - ) + else: + frappe.throw("Invalid Request") - def process_single_payment(self, payment_order, payment_row): - if ( - not payment_row.payment_initiated - and payment_row.payment_status == "Pending" - ): - # handle failed or success response - payment_response = self.process_payment_and_response( - payment_row, payment_order - ) + def make_single_request(self, payment_order, summary): + url = self.connector_url + headers = self.headers - if ( - payment_response - and "payment_status" in payment_response - and payment_response["payment_status"] == "Initiated" - ): - frappe.db.set_value( - "Payment Order Summary", - payment_row.name, - { - "payment_status": "Initiated", - "payment_date": getdate(), - "payment_initiated": 1, - }, - ) + payload = self.get_payload(payment_order) + payload.update(summary.as_dict(convert_dates_to_str=True)) + payload.party_name = frappe.db.get_value( + summary.party_type, summary.party, get_party_field_name(summary.party_type) + ) + payload.address = json.dumps(get_bank_address_details(summary.bank_account)) - elif ( - payment_response - and "payment_status" in payment_response - and payment_response["payment_status"] == "" - ): - if "message" in payment_response: - frappe.db.set_value( - "Payment Order Summary", - payment_row.name, - "message", - payment_response.message, - ) - else: - frappe.db.set_value( - "Payment Order Summary", - payment_row.name, - "payment_status", - "Failed", - ) - payment_entry = frappe.get_doc( - "Payment Entry", payment_row.payment_entry - ) - if payment_entry.docstatus == 1: - payment_entry.cancel() + response = request.post(url, headers=headers, data=json.dumps(payload)) - self.process_bank_payment_requests(payment_order, payment_row) + # create api request log + create_api_log(response, self.action, payment_order.doctype, payment_order.name) - if payment_response and "message" in payment_response: - frappe.db.set_value( - "Payment Order Summary", - payment_row.name, - "message", - payment_response.message, - ) + self.verify_response(response, payment_order) def add_payment_in_the_background(self, payment_order): - def _add_queue(payment_row, job_id): + def _add_queue(summary, job_id): frappe.enqueue( - self.process_single_payment, + self.make_single_request, payment_order=payment_order, - payment_row=payment_row, + summary=summary, job_id=job_id, job_name=f"Make Payment {job_id}", enqueue_after_commit=True, ) enqueue_count = 0 - for payment_row in payment_order.summary: + for summary in payment_order.summary: + if ( + self.action == "initiate_payment" + and not summary.payment_initiated + and summary.payment_status != "Pending" + ): + continue + if self.action == "get_payment_status" and summary.payment_status not in [ + "Pending", + "Initiated", + ]: + continue job_id = ( "".join(re.findall(r"[0-9a-zA-Z]", self.name))[-10:] + "-" - + payment_row.name + + summary.name ) if not frappe.db.exists("RQ Job", job_id): - _add_queue(payment_row=payment_row, job_id=job_id) + _add_queue(summary=summary, job_id=job_id) enqueue_count += 1 elif (rq_job := frappe.db.exists("RQ Job", job_id)) and not is_job_enqueued( @@ -384,226 +387,19 @@ def _add_queue(payment_row, job_id): ): frappe.get_doc("RQ Job", rq_job).delete() frappe.clear_cache(doctype="RQ Job") - _add_queue(payment_row=payment_row, job_id=job_id) + _add_queue(summary=summary, job_id=job_id) enqueue_count += 1 frappe.msgprint(_(f"{enqueue_count} payments added in background")) - def make_single_payment(self, payment_order): - # add payment in background - if ( - len(payment_order.summary) > 10 - or frappe.get_single( - "India Banking Settings" - ).enable_payment_in_the_background - ): - return self.add_payment_in_the_background(payment_order) - - for payment_row in payment_order.summary: - self.process_single_payment( - payment_order=payment_order, payment_row=payment_row - ) - - payment_order.reload() - processed_count = 0 - for row in payment_order.summary: - if row.payment_initiated: - processed_count += 1 - - if processed_count == len(payment_order.summary): - frappe.db.set_value( - "Payment Order", payment_order.name, "status", "Initiated" - ) - - frappe.msgprint(_(f"{processed_count} payments initiated")) - - def process_payment_and_response(self, payment_row, payment_order): - payment_payload = self.get_payload(payment_order, "intiate_payment") - payment_payload.update(payment_row.as_dict(convert_dates_to_str=True)) - party_field_name = ( - "supplier_name" if payment_row.party_type == "Supplier" else "employee_name" - ) - - party_name = frappe.db.get_value( - payment_row.party_type, payment_row.party, party_field_name - ) - - payment_payload.party_name = party_name - payment_payload.desc = ( - f"Payment to {payment_row.party} via {payment_row.parent}" - ) - - party_address = get_bank_address_details(payment_row.bank_account) - bank_link = get_link_to_form("Bank Account", payment_row.bank_account) - if not party_address: - frappe.throw( - _( - f"Address not found for the selected bank account {bank_link} at Row #{payment_row.idx}" - ) - ) - - payment_payload.address = json.dumps(party_address) - - response = request.post( - self.base_url, headers=self.headers, data=json.dumps(payment_payload) - ) - - # create api request log - create_api_log( - response, "Make Payment", payment_order.doctype, payment_order.name - ) - - if response.status_code == 200: - payment_response = self.get_response_details(response) - - if not payment_response.status: - return frappe._dict( - {"payment_status": "", "message": str(response.text)} - ) - - elif payment_response.status == "ACCEPTED": - return frappe._dict( - { - "payment_status": "Initiated", - "message": payment_response.message, - } - ) - - elif payment_response.status == "Request Failure": - return frappe._dict( - { - "payment_status": "", - "message": payment_response.message or "Request Failure", - } - ) - - else: - return frappe._dict( - {"payment_status": "Failed", "message": payment_response.message} - ) - else: - return frappe._dict({"payment_status": "", "message": "Bad Request"}) - - def process_bank_payment_requests(self, payment_order, payment_row): - key = ( - payment_row.party_type, - payment_row.party, - payment_row.bank_account, - payment_row.account, - payment_row.cost_center, - payment_row.project, - payment_row.tax_withholding_category, - payment_row.reference_doctype, - ) - - failed_prs = [] - for ref in payment_order.references: - ref_key = ( - ref.party_type, - ref.party, - ref.bank_account, - ref.account, - ref.cost_center, - ref.project, - ref.tax_withholding_category, - ref.reference_doctype, - ) - if key == ref_key: - failed_prs.append(ref.payment_request) - - for pr in failed_prs: - pr_doc = frappe.get_doc("Payment Request", pr) - if pr_doc.docstatus == 1: - pr_doc.check_if_payment_entry_exists() - pr_doc.set_as_cancelled() - pr_doc.db_set("docstatus", 2) - - def make_bulk_payment(self, payment_order, otp): - payment_payload = self.get_payload(payment_order, "intiate_payment") - payment_payload.doc.update({"otp": otp}) - - payment_account_list = [] - - # Lei number validation - for summary_row in payment_order.summary: - if ( - summary_row.mode_of_transfer == "RTGS" - and summary_row.amount >= 500000000 - ): - lei_number = frappe.db.get_value( - summary_row.party_type, summary_row.party, "lei_number" - ) - payment_account_list.append(summary_row.account_name + "-" + lei_number) - if not lei_number: - frappe.throw(_("LEI Number required for payment > 50 Cr")) - else: - payment_account_list.append( - summary_row.account_name + "-" + summary_row.bank_account_no - ) - - payment_payload.doc.update( - { - "desc": f"Payment to {comma_and(payment_account_list)} via {payment_order.name}" - } - ) - - response = request.post( - self.base_url, headers=self.headers, data=json.dumps(payment_payload) - ) - - # create api request log - create_api_log( - response, "Make Payment", payment_order.doctype, payment_order.name - ) - - # handle failed or success response - return self.process_bulk_payment_response(response, payment_order) - - def process_bulk_payment_response(self, response, payment_order): - payment_response = self.get_response_details(response) - - if payment_response.get("status", "") == "ACCEPTED": - frappe.db.set_value( - "Payment Order", payment_order.name, "status", "Initiated" - ) - frappe.db.set_value( - "Payment Order", - payment_order.name, - "file_sequence_number", - payment_response.get("file_sequence_number"), - ) - - for row in payment_order.summary: - frappe.db.set_value( - "Payment Order Summary", - row.name, - { - "payment_status": "Initiated", - "payment_date": getdate(), - "payment_initiated": 1, - }, - ) - - frappe.msgprint(_("Payment Initiated")) - - elif payment_response.get("status", "") == "Failed": - frappe.msgprint( - title=_("Failed"), - msg=_(cstr(payment_response.get("message"))), - indicator="red", - ) - else: - frappe.throw(_("Invalid Response: Check API Log")) - - def verify_otp(self, payment_order, otp): - pass - def generate_otp(self, payment_order): + return {"otp_required": True} + payment_order.reload() # Generate OTP using POST request response = request.post( - self.base_url, + self.connector_url, headers=self.headers, data=json.dumps(self.get_payload(payment_order, "generate_otp")), ) @@ -615,14 +411,7 @@ def generate_otp(self, payment_order): # handle failed or success response return self.handle_otp_response(response) - def get_response_details(self, response): - try: - return frappe._dict(response.json().get("message")) - except: - frappe.throw(_("Invalid Response: Check API Log")) - def handle_otp_response(self, response): - # return {"otp_required": True} if response.ok: response_details = self.get_response_details(response) if response_details.status == "success": @@ -638,25 +427,9 @@ def handle_otp_response(self, response): msg=_("Invalid Request: Check API Log"), ) - def get_payload(self, payment_order, action): - bank_account = frappe.get_doc( - "Bank Account", payment_order.company_bank_account - ) - payment_payload = frappe._dict() - payment_payload.doc = payment_order.as_dict(convert_dates_to_str=True) - payment_payload.doc.update( - { - "company_account_number": bank_account.bank_account_no, - "company_bank_account_name": bank_account.account_name, - "company_ifsc": bank_account.branch_code, - "mobile_number": bank_account.mobile_number, - } - ) - payment_payload.method = action - payment_payload.bulk_transaction = self.bulk_transaction - return payment_payload - def update_payment_status(self, payment_order): + payment_order.reload() + try: success_count = 0 faild_count = 0 @@ -794,7 +567,9 @@ def make_payment(payment_order, otp=None): bank_connector = get_bank_connector( payment_order.company_bank_account, payment_order.company ) - return bank_connector.make_payment(payment_order, otp) + return bank_connector.make_post_request( + payment_order, otp=otp, action="intiate_payment" + ) @frappe.whitelist() @@ -803,7 +578,7 @@ def get_payment_status(payment_order): bank_connector = get_bank_connector( payment_order.company_bank_account, payment_order.company ) - return bank_connector.get_payment_status(payment_order) + return bank_connector.make_post_request(payment_order, action="get_payment_status") @frappe.whitelist() diff --git a/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py b/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py index b9001a6..b5061e2 100644 --- a/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py +++ b/india_banking/india_banking/doctype/india_banking_request_log/india_banking_request_log.py @@ -9,32 +9,13 @@ from frappe.model.document import Document from requests.models import Response +from india_banking.utils import extract_error_message + class IndiaBankingRequestLog(Document): @frappe.whitelist() def show_failure_message(self): - try: - if ( - self.response - and (response := json.loads(self.response)) - and (server_messages := response.get("_server_messages")) - ): - if ( - server_messages - and (server_messages := json.loads(server_messages)) - and (server_message := server_messages[0]) - ): - server_message = json.loads(server_message) - title = _("Failure Reason") - message = _( - f'{frappe.bold(server_message.get("title", ""))}: {server_message.get("message", "")}' - ) - frappe.msgprint(title=title, msg=message) - except: - frappe.msgprint( - title=_("Error: Could not process the response"), - msg=frappe.get_traceback(with_context=1), - ) + extract_error_message(json.loads(self.response), show_message=True) def format_with_indent(data): @@ -57,8 +38,8 @@ def create_api_log(res, action=None, ref_doctype=None, ref_docname=None): """Can create API log From response Args: - res (response object): It is used to obtain an API response. - request_from (str): It is optional for the purposes of the API... + res (response object): It is used to obtain an API response. + request_from (str): It is optional for the purposes of the API... """ if not isinstance(res, Response): return diff --git a/india_banking/utils.py b/india_banking/utils.py index 1ea752d..fb790f9 100644 --- a/india_banking/utils.py +++ b/india_banking/utils.py @@ -1,4 +1,7 @@ +import json + import frappe +from frappe import _ def get_bank_address_details(bank_account): @@ -42,3 +45,40 @@ def get_bank_address_details(bank_account): "CountySubDivision": country_sub_division, "Country": country, } + + +def get_party_field_name(party_type): + return { + "Supplier": "supplier_name", + "Customer": "customer_name", + "Employee": "employee_name", + }.get(party_type, "name") + + +def extract_error_message(response_json, show_message=False) -> str: + try: + response_json = json.loads(response_json) if isinstance(response_json, str) else response_json + failure_message = "" + + server_message = response_json.get("_server_messages", "[]") + if server_message and (server_message := json.loads(server_message)): + server_message = json.loads(server_message[0]) + failure_message = _( + f'{frappe.bold(server_message.get("title", ""))}: {server_message.get("message", "")}' + ) + + failure_message = failure_message or json.loads( + response_json.get("message", "{}").get("message", "{}") + ).get("errormessage", "") + + if show_message and failure_message: + frappe.msgprint(title=_("Failure Reason"), msg=failure_message) + + elif failure_message: + return failure_message + + except: + frappe.throw( + title=_("Error: Could not process the response"), + msg=frappe.get_traceback(with_context=1), + ) From b06929b42cf801639e39912950fd3561166a08db Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 21 Jan 2025 14:03:16 +0530 Subject: [PATCH 82/83] feat: add settings to reposting entry --- .../doctype/bank_connector/bank_connector.py | 7 +- .../india_banking_settings.json | 30 +++++++- .../india_banking_settings.py | 21 ++++++ india_banking/tasks.py | 69 ++++++++++++++++++- 4 files changed, 120 insertions(+), 7 deletions(-) diff --git a/india_banking/india_banking/doctype/bank_connector/bank_connector.py b/india_banking/india_banking/doctype/bank_connector/bank_connector.py index 4b553d1..cbcb4ea 100644 --- a/india_banking/india_banking/doctype/bank_connector/bank_connector.py +++ b/india_banking/india_banking/doctype/bank_connector/bank_connector.py @@ -8,7 +8,7 @@ import requests as request from frappe import _ from frappe.model.document import Document -from frappe.utils import cstr, getdate +from frappe.utils import cint, cstr, getdate from frappe.utils.background_jobs import is_job_enqueued from india_banking.india_banking.doctype.india_banking_request_log.india_banking_request_log import ( @@ -110,9 +110,8 @@ def make_post_request(self, payment_order, otp=None, action=None): self.verify_response(response, payment_order) else: # add payment in background - if ( - len(payment_order.summary) > 10 - or frappe.get_single( + if len(payment_order.summary) > 10 or cint( + frappe.get_single( "India Banking Settings" ).enable_payment_in_the_background ): diff --git a/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.json b/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.json index f19c729..efc01ce 100644 --- a/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.json +++ b/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.json @@ -10,7 +10,11 @@ "enable_payment_in_the_background", "column_break_wjye", "default_email_format", - "activate_workflow_on_bank_account" + "activate_workflow_on_bank_account", + "automatic_update_configuration_section", + "update_posting_date_as_payment_date", + "column_break_laug", + "update_payment_status" ], "fields": [ { @@ -46,12 +50,34 @@ "fieldname": "enable_payment_in_the_background", "fieldtype": "Check", "label": "Enable Payment in the Background" + }, + { + "default": "0", + "fieldname": "update_posting_date_as_payment_date", + "fieldtype": "Check", + "label": "Update Posting Date as Payment Date" + }, + { + "default": "0", + "fieldname": "update_payment_status", + "fieldtype": "Check", + "label": "Update Payment Status" + }, + { + "collapsible": 1, + "fieldname": "automatic_update_configuration_section", + "fieldtype": "Section Break", + "label": "Automatic Update Configuration" + }, + { + "fieldname": "column_break_laug", + "fieldtype": "Column Break" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2025-01-06 12:03:12.849508", + "modified": "2025-01-21 13:21:56.571226", "modified_by": "Administrator", "module": "India Banking", "name": "India Banking Settings", diff --git a/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.py b/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.py index 18fb72e..f87cd45 100644 --- a/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.py +++ b/india_banking/india_banking/doctype/india_banking_settings/india_banking_settings.py @@ -8,6 +8,7 @@ class IndiaBankingSettings(Document): def validate(self): self.enable_or_disable_workflow_to_bank_account() + self.enable_payment_entry_reposting() def enable_or_disable_workflow_to_bank_account(self): """Enable or disable workflow to bank account based on settings.""" @@ -18,3 +19,23 @@ def enable_or_disable_workflow_to_bank_account(self): "is_active", self.activate_workflow_on_bank_account, ) + + def enable_payment_entry_reposting(self): + if self.update_posting_date_as_payment_date: + if not frappe.db.exists( + "Repost Allowed Types", + { + "parent": "Repost Accounting Ledger Settings", + "document_type": "Payment Entry", + "allowed": 1, + }, + ): + doc = frappe.get_single("Repost Accounting Ledger Settings") + doc.append( + "allowed_types", + { + "document_type": "Payment Entry", + "allowed": 1, + }, + ) + doc.save() diff --git a/india_banking/tasks.py b/india_banking/tasks.py index 80ef4dc..3cf12fc 100644 --- a/india_banking/tasks.py +++ b/india_banking/tasks.py @@ -1,4 +1,5 @@ import frappe +from frappe.query_builder import DocType from india_banking.india_banking.doctype.bank_connector.bank_connector import ( get_payment_status, @@ -6,9 +7,17 @@ def daily(): + update_payment_date_as_posting_date() + update_payment_status() + + +def update_payment_status(): + if not frappe.get_single("India Banking Settings").update_payment_status: + return + orders = frappe.get_all( "Payment Order Summary", - {"docstatus": 1, "payment_status": "Initiated"}, + {"docstatus": 1, "payment_status": ["in", ["Pending", "Initiated"]]}, pluck="parent", distinct="parent", ) @@ -21,3 +30,61 @@ def daily(): title="Error in Payment Order Status Cron", message=frappe.get_traceback(), ) + + +def update_payment_date_as_posting_date(): + """Update Payment Entry posting dates based on Payment date in Payment Order Summary and repost accounting ledgers.""" + try: + if not frappe.get_single( + "India Banking Settings" + ).update_posting_date_as_payment_date: + return + + PaymentEntry = DocType("Payment Entry") + PaymentOrderSummary = DocType("Payment Order Summary") + + reposting_entries = ( + frappe.qb.from_(PaymentOrderSummary) + .join(PaymentEntry) + .on(PaymentOrderSummary.payment_entry == PaymentEntry.name) + .select(PaymentOrderSummary.payment_entry) + .where( + (PaymentEntry.docstatus == 1) + & (PaymentOrderSummary.payment_date.isnotnull()) + & (PaymentOrderSummary.payment_date != PaymentEntry.posting_date) + ) + .groupby(PaymentEntry.name) + ).run(as_dict=True) + + if reposting_entries: + # Update mismatched posting dates + ( + frappe.qb.update(PaymentEntry) + .join(PaymentOrderSummary) + .on(PaymentOrderSummary.payment_entry == PaymentEntry.name) + .set(PaymentEntry.posting_date, PaymentOrderSummary.payment_date) + .where( + (PaymentEntry.docstatus == 1) + & (PaymentOrderSummary.payment_date.isnotnull()) + & (PaymentOrderSummary.payment_date != PaymentEntry.posting_date) + ) + ).run() + frappe.db.commit() + # Repost accounting ledger entries for updated Payment Entries + reposting_doc = frappe.new_doc("Repost Accounting Ledger") + for entry in reposting_entries: + reposting_doc.append( + "vouchers", + { + "voucher_type": "Payment Entry", + "voucher_no": entry["payment_entry"], + }, + ) + + reposting_doc.save() + reposting_doc.submit() + except: + frappe.log_error( + title="Error in Payment Date Update Cron", + message=frappe.get_traceback(), + ) From 9a47597ce965bc1dc4368a61476e994f86ace744 Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Wed, 22 Jan 2025 13:39:22 +0530 Subject: [PATCH 83/83] feat: handle unreconcil payment --- india_banking/default.py | 14 +++++ india_banking/hooks.py | 3 + .../india_banking/doc_events/payment_order.py | 48 +++++---------- .../doc_events/unreconcile_payment.py | 58 +++++++++++++++++++ .../bulk_create_payment_request.json | 36 ++++++++++++ india_banking/overrides/payment_order.py | 17 +----- 6 files changed, 127 insertions(+), 49 deletions(-) create mode 100644 india_banking/india_banking/doc_events/unreconcile_payment.py create mode 100644 india_banking/india_banking/report/bulk_create_payment_request/bulk_create_payment_request.json diff --git a/india_banking/default.py b/india_banking/default.py index 2ea81ca..653e70b 100644 --- a/india_banking/default.py +++ b/india_banking/default.py @@ -1,3 +1,17 @@ +PAYMENT_SUMMARIES_FIELDS = [ + "party_type", + "party", + "bank_account", + "account", + "cost_center", + "project", + "tax_withholding_category", + "reference_doctype", + "reference_name", + "payment_entry", + "journal_entry_account", +] + DEFAULT_MODE_OF_TRANSFERS = [ { "mode": "A2A/FT/Internal", diff --git a/india_banking/hooks.py b/india_banking/hooks.py index b8620d7..ddf5d4d 100644 --- a/india_banking/hooks.py +++ b/india_banking/hooks.py @@ -34,6 +34,9 @@ "Bank Account": { "validate": "india_banking.india_banking.doc_events.bank_account.validate" }, + "Unreconcile Payment": { + "on_submit": "india_banking.india_banking.doc_events.unreconcile_payment.on_submit", + }, } accounting_dimension_doctypes = [ diff --git a/india_banking/india_banking/doc_events/payment_order.py b/india_banking/india_banking/doc_events/payment_order.py index daa8f8d..8422b71 100644 --- a/india_banking/india_banking/doc_events/payment_order.py +++ b/india_banking/india_banking/doc_events/payment_order.py @@ -6,6 +6,8 @@ from frappe import _, parse_json from frappe.utils import nowdate +from india_banking.default import PAYMENT_SUMMARIES_FIELDS + @frappe.whitelist() def cancel_pending_payments(data): @@ -39,19 +41,7 @@ def process_payment_requests(payment_order_summary): pos = frappe.get_doc("Payment Order Summary", payment_order_summary) payment_order_doc = frappe.get_doc("Payment Order", pos.parent) - summarise_field = [ - "party_type", - "party", - "bank_account", - "account", - "cost_center", - "project", - "tax_withholding_category", - "reference_doctype", - "reference_name", - "payment_entry", - "journal_entry_account", - ] + summarise_field = PAYMENT_SUMMARIES_FIELDS if payment_order_doc.summarise_payment_based_on == "Party": summarise_field.remove("reference_name") @@ -108,17 +98,15 @@ def make_payment_entries(docname): if row.tax_withholding_category: net_total = 0 for reference in payment_order_doc.references: - if ( - reference.party_type == row.party_type - and reference.party == row.party - and reference.cost_center == row.cost_center - and reference.project == row.project - and reference.bank_account == row.bank_account - and reference.account == row.account - and reference.tax_withholding_category - == row.tax_withholding_category - and reference.reference_doctype == row.reference_doctype - ): + filter_fields = PAYMENT_SUMMARIES_FIELDS + if payment_order_doc.summarise_payment_based_on == "Party": + filter_fields.remove("reference_name") + filter_fields.extend(get_accounting_dimensions()) + match_condition = [ + (reference.get(field, "") or "") == row.get(field, "") + for field in filter_fields + ] + if match_condition: net_total += frappe.db.get_value( "Payment Request", reference.payment_request, @@ -130,17 +118,7 @@ def make_payment_entries(docname): pe.tax_withholding_category = row.tax_withholding_category for reference in payment_order_doc.references: if not reference.is_adhoc: - filter_fields = [ - "party_type", - "party", - "bank_account", - "account", - "cost_center", - "project", - "tax_withholding_category", - "reference_doctype", - "reference_name", - ] + filter_fields = PAYMENT_SUMMARIES_FIELDS if payment_order_doc.summarise_payment_based_on == "Party": filter_fields.remove("reference_name") filter_fields.extend(get_accounting_dimensions()) diff --git a/india_banking/india_banking/doc_events/unreconcile_payment.py b/india_banking/india_banking/doc_events/unreconcile_payment.py new file mode 100644 index 0000000..08d20fa --- /dev/null +++ b/india_banking/india_banking/doc_events/unreconcile_payment.py @@ -0,0 +1,58 @@ +import frappe +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_accounting_dimensions, +) + +from india_banking.default import PAYMENT_SUMMARIES_FIELDS + + +def on_submit(doc, method=None): + if doc.voucher_type != "Payment Entry": + return + + if ( + frappe.db.get_value("Payment Entry", doc.voucher_no, "source_doctype") + != "Payment Request" + ): + return + + payment_order_summary = get_payment_order_summary(doc.voucher_no) + if not payment_order_summary: + return + + payment_order = frappe.get_doc("Payment Order", payment_order_summary.parent) + + summarise_field = PAYMENT_SUMMARIES_FIELDS + summarise_field.extend(get_accounting_dimensions()) + if payment_order.summarise_payment_based_on == "Party": + summarise_field.remove("reference_name") + + for reference in payment_order.references: + if all( + ( + reference.get(field, "") == payment_order_summary.get(field, "") + for field in summarise_field + ) + ): + frappe.db.set_value( + "Payment Request", + reference.bank_payment_request, + {"reference_doctype": "", "reference_name": ""}, + ) + frappe.db.set_value( + "Payment Order Reference", + reference.name, + {"reference_doctype": "", "reference_name": ""}, + ) + + +def get_payment_order_summary(payment_entry): + is_ammended = frappe.db.get_value("Payment Entry", payment_entry, "amended_from") + payment_entry = ( + "-".join(payment_entry.split("-")[:-1]) if is_ammended else payment_entry + ) + summary = frappe.db.get_value( + "Payment Order Summary", {"payment_entry": payment_entry}, "name" + ) + if summary: + return frappe.get_doc("Payment Order Summary", summary) diff --git a/india_banking/india_banking/report/bulk_create_payment_request/bulk_create_payment_request.json b/india_banking/india_banking/report/bulk_create_payment_request/bulk_create_payment_request.json new file mode 100644 index 0000000..1b30ba3 --- /dev/null +++ b/india_banking/india_banking/report/bulk_create_payment_request/bulk_create_payment_request.json @@ -0,0 +1,36 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2025-01-21 15:35:13.394168", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "letterhead": null, + "modified": "2025-01-21 15:35:39.241012", + "modified_by": "Administrator", + "module": "India Banking", + "name": "Bulk Create Payment Request", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Purchase Invoice", + "report_name": "Bulk Create Payment Request", + "report_type": "Script Report", + "roles": [ + { + "role": "Accounts User" + }, + { + "role": "Purchase User" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Auditor" + } + ], + "timeout": 0 +} \ No newline at end of file diff --git a/india_banking/overrides/payment_order.py b/india_banking/overrides/payment_order.py index a3625b1..b6c886b 100644 --- a/india_banking/overrides/payment_order.py +++ b/india_banking/overrides/payment_order.py @@ -8,6 +8,7 @@ from frappe import _ from frappe.utils import get_link_to_form +from india_banking.default import PAYMENT_SUMMARIES_FIELDS from india_banking.india_banking.doc_events.payment_order import make_payment_entries @@ -185,21 +186,9 @@ def get_party_summary( # Considering the following dimensions to group payments # (party_type, party, bank_account, account, cost_center, project) def _get_unique_key(reference=None, summarise_field_only=False): - summarise_field = [ - "party_type", - "party", - "bank_account", - "account", - "cost_center", - "project", - "tax_withholding_category", - "reference_doctype", - "reference_name", - "payment_entry", - "journal_entry", - "journal_entry_account", - ] + summarise_field = PAYMENT_SUMMARIES_FIELDS summarise_field.extend(get_accounting_dimensions()) + if summarise_payment_based_on == "Party": summarise_field.remove("reference_name")