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() diff --git a/india_banking/default.py b/india_banking/default.py new file mode 100644 index 0000000..653e70b --- /dev/null +++ b/india_banking/default.py @@ -0,0 +1,118 @@ +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", + "minimum_limit": 1, + "maximum_limit": 500000000, + "start_time": "0:00:00", + "end_time": "23:59:59", + "disabled": 0, + "priority": "1", + }, + { + "mode": "IMPS", + "minimum_limit": 1, + "maximum_limit": 200000, + "start_time": "0:00:00", + "end_time": "23:59:59", + "disabled": 0, + "priority": "2", + }, + { + "mode": "RTGS", + "minimum_limit": 200000, + "maximum_limit": 500000000, + "start_time": "0:00:00", + "end_time": "23:59:59", + "disabled": 0, + "priority": "3", + }, + { + "mode": "NEFT", + "minimum_limit": 1, + "maximum_limit": 500000000, + "start_time": "0:00:00", + "end_time": "23:59:59", + "disabled": 0, + "priority": "4", + }, +] + +STD_BANK_LIST = [ + "Axis Bank", + "HDFC Bank", + "ICICI 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 9d8b6bb..ddf5d4d 100644 --- a/india_banking/hooks.py +++ b/india_banking/hooks.py @@ -4,295 +4,44 @@ app_description = "Indian Banking Integration with ERPNext" app_email = "support@aerele.in" app_license = "gpl-3.0" -# 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" +after_install = "india_banking.install.after_install" -# 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"} - -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" - ] - ] - ]} -] +before_uninstall = "india_banking.uninstall.before_uninstall" doctype_js = { - "Payment Order" : "public/js/payment_order.js", - "Purchase Order" : "public/js/purchase_order.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" - + "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"} -# 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" -after_install = "india_banking.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 +doctype_list_js = { + "Payment Order": "public/js/payment_order_list.js", +} -# override_doctype_class = { -# "ToDo": "custom_app.overrides.CustomToDo" -# } 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 Order": "india_banking.overrides.payment_order.CustomPaymentOrder", + "Payment Request": "india_banking.overrides.payment_request.BankPaymentRequest", } - -# Document Events -# --------------- -# Hook on document methods and events - -# doc_events = { -# "*": { -# "on_update": "method", -# "on_cancel": "method", -# "on_trash": "method" -# } -# } - doc_events = { + "Bank": { + "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", - } -} - -# accounting_dimension_doctypes = ['Bank Payment Request', 'Payment Order', 'Payment Order Reference', 'Payment Order Summary'] -accounting_dimension_doctypes = ['Bank Payment Request'] - -# 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" -# ], -# } - -scheduler_events = { - "daily": [ - "india_banking.tasks.daily" - ] + "validate": "india_banking.india_banking.doc_events.bank_account.validate" + }, + "Unreconcile Payment": { + "on_submit": "india_banking.india_banking.doc_events.unreconcile_payment.on_submit", + }, } -# 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" -# } - -override_whitelisted_methods = { - "erpnext.accounts.doctype.payment_request.payment_request.make_payment_request": "india_banking.india_banking.override.payment_request.make_payment_request" -} - -# -# 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 -# } +accounting_dimension_doctypes = [ + "Payment Order Reference", + "Payment Order Summary", +] +scheduler_events = {"daily": ["india_banking.tasks.daily"]} 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/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/doc_events/bank.py b/india_banking/india_banking/doc_events/bank.py new file mode 100644 index 0000000..58c8b75 --- /dev/null +++ b/india_banking/india_banking/doc_events/bank.py @@ -0,0 +1,9 @@ +import frappe +from frappe import _ + + +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") + ) diff --git a/india_banking/india_banking/doc_events/bank_account.py b/india_banking/india_banking/doc_events/bank_account.py index d0ff132..ac22aeb 100644 --- a/india_banking/india_banking/doc_events/bank_account.py +++ b/india_banking/india_banking/doc_events/bank_account.py @@ -1,7 +1,29 @@ -import frappe, re -from frappe import _ , cstr +import re -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 +import frappe +from frappe import _ +from frappe.utils import cstr + +IFSC_PATTERN = re.compile(r"^[A-Z]{4}0[A-Z0-9]{6}$") + + +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/india_banking/doc_events/payment_order.py b/india_banking/india_banking/doc_events/payment_order.py index 14dbddb..8422b71 100644 --- a/india_banking/india_banking/doc_events/payment_order.py +++ b/india_banking/india_banking/doc_events/payment_order.py @@ -1,417 +1,73 @@ -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.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.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() -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") +from india_banking.default import PAYMENT_SUMMARIES_FIELDS - 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 +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 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 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_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}" + process_payment_requests(d.row_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' - ) + if success_count: + frappe.msgprint(_(f"{success_count} payment(s) updated")) - 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) +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) - # 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 - } - ) + summarise_field = PAYMENT_SUMMARIES_FIELDS + if payment_order_doc.summarise_payment_based_on == "Party": + summarise_field.remove("reference_name") - if not bank_connector_exists: - frappe.throw("Bank Connector is not initialized") + summarise_field.extend(get_accounting_dimensions()) + key = tuple([pos.get(field, "") for field in summarise_field]) - bank_connector = frappe.get_doc("Bank Connector", bank_connector_exists) + failed_prs = [] + for ref in payment_order_doc.references: + ref_key = tuple([(ref.get(field, "") or "") for field in summarise_field]) - if payment_order_doc.company_bank == 'ICICI Bank' and bank_connector.bulk_transaction: - get_bulk_payment_status(payment_order_doc) + if key == ref_key and ref.payment_request: + failed_prs.append(ref.payment_request) - 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) + 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) - 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" @@ -428,7 +84,6 @@ def make_payment_entries(docname): 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 @@ -438,79 +93,114 @@ def make_payment_entries(docname): 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, '')}) - + 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") + 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, + "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") + 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 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("Bank Payment Request", reference.bank_payment_request, "payment_term") - + 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 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") + 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: pe.append( "references", @@ -519,10 +209,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 +222,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 +232,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,17 +243,18 @@ 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( { "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 ), } @@ -569,11 +262,16 @@ def make_payment_entries(docname): 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: @@ -583,232 +281,4 @@ def group_by_invoices(self): 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.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) - -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 \ No newline at end of file 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/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 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/doctype/bank_connector/bank_connector.js b/india_banking/india_banking/doctype/bank_connector/bank_connector.js index b75c182..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,15 +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 - }, - }; - }); - }, + 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_connector/bank_connector.py b/india_banking/india_banking/doctype/bank_connector/bank_connector.py index 0554d91..cbcb4ea 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,587 @@ # Copyright (c) 2024, Aerele Technologies Private Limited and contributors # For license information, please see license.txt -# import frappe +import json +import re + +import frappe +import requests as request +from frappe import _ from frappe.model.document import Document +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 ( + create_api_log, +) +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 +] class BankConnector(Document): - pass + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def check_user_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 connector_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 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 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) + + if self.bulk_transaction: + url = self.connector_url + headers = self.headers + + 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: + # add payment in background + if len(payment_order.summary) > 10 or cint( + frappe.get_single( + "India Banking Settings" + ).enable_payment_in_the_background + ): + return self.add_payment_in_the_background(payment_order) + + for summary in payment_order.summary: + if ( + not summary.payment_initiated + and summary.payment_status != "Pending" + ): + continue + + self.make_single_request(payment_order, summary) + + 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) + + self.update_payment_status(payment_order) + + def verify_payment_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", "") + + 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", + _name, + { + "payment_status": "Initiated", + "payment_date": getdate(), + "payment_initiated": 1, + "message": details.get("message", ""), + }, + ) + elif details.get("payment_status", "") == "Failed": + frappe.db.set_value( + "Payment Order Summary", + _name, + { + "payment_status": "Pending", + "payment_initiated": 1, + "message": details.get("message", ""), + }, + ) + elif details.get("payment_status", "") == "Request Failure": + frappe.db.set_value( + "Payment Order Summary", + _name, + { + "payment_status": "Pending", + "message": details.get("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.payment_entry, + "reference_no", + status_details.utr_number, + ) + if summary.journal_entry_account: + frappe.db.set_value( + "Journal Entry Account", + summary.journal_entry_account, + { + "payment_status": "Paid", + "reference_number": status_details.utr_number, + }, + ) + + self.notify_party(summary) + + frappe.db.set_value( + "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, + ) + + elif status_details.status == "Failed": + frappe.db.set_value( + "Payment Order Summary", + summary.name, + { + "payment_status": status_details.status, + "message": status_details.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() + 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", + ) + + elif status_details.status == "Rejected": + frappe.db.set_value( + "Payment Order Summary", + summary.name, + { + "payment_status": status_details.status, + "message": status_details.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() + + 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", + ) + + elif payment_status == "FAILED": + frappe.msgprint( + title=_("Failed"), + msg=_(message), + indicator="red", + ) + + else: + extract_error_message(response.json()) + + else: + frappe.throw("Invalid Request") + + def make_single_request(self, payment_order, summary): + url = self.connector_url + headers = self.headers + + 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)) + + 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) + + def add_payment_in_the_background(self, payment_order): + def _add_queue(summary, job_id): + frappe.enqueue( + self.make_single_request, + payment_order=payment_order, + summary=summary, + job_id=job_id, + job_name=f"Make Payment {job_id}", + enqueue_after_commit=True, + ) + + enqueue_count = 0 + 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:] + + "-" + + summary.name + ) + if not frappe.db.exists("RQ Job", 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( + job_id + ): + frappe.get_doc("RQ Job", rq_job).delete() + frappe.clear_cache(doctype="RQ Job") + _add_queue(summary=summary, job_id=job_id) + enqueue_count += 1 + + frappe.msgprint(_(f"{enqueue_count} payments added in background")) + + def generate_otp(self, payment_order): + return {"otp_required": True} + + payment_order.reload() + + # Generate OTP using POST request + response = request.post( + self.connector_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 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( + title=_("Invalid Request"), + msg=_("OTP Initiation Failed: Check API Log"), + ) + else: + frappe.throw( + title=cstr(response.status_code), + msg=_("Invalid Request: Check API Log"), + ) + + def update_payment_status(self, payment_order): + payment_order.reload() + + try: + success_count = 0 + faild_count = 0 + rejected_count = 0 + for summary in payment_order.summary: + status = frappe.db.get_value( + "Payment Order Summary", summary.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 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_balance(self, bank_account): + payload = { + "bank_account_number": bank_account.bank_account_no, + "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 Bank Balance", "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 + bank_connector = frappe.db.exists( + "Bank Connector", + { + "company": company, + "bank_account": bank_account, + }, + ) + if not bank_connector: + frappe.throw(_("Bank Connector is not initialized")) + + 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.company_bank_account, payment_order.company + ) + return bank_connector.make_post_request( + payment_order, otp=otp, action="intiate_payment" + ) + + +@frappe.whitelist() +def get_payment_status(payment_order): + payment_order = frappe.get_doc("Payment Order", payment_order) + bank_connector = get_bank_connector( + payment_order.company_bank_account, payment_order.company + ) + return bank_connector.make_post_request(payment_order, action="get_payment_status") + + +@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/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..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 @@ -1,6 +1,15 @@ // 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) { + 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 () { @@ -104,4 +113,5 @@ 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.json b/india_banking/india_banking/doctype/bank_payment_request/bank_payment_request.json index bf02909..8e52631 100644 --- 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 @@ -27,7 +27,6 @@ "currency", "apply_tax_withholding_amount", "tax_withholding_category", - "party_account_currency", "bank_account_details", "bank_account", "bank", @@ -412,19 +411,13 @@ "label": "Payment Term", "options": "Payment Term", "read_only": 1 - }, - { - "fieldname": "party_account_currency", - "fieldtype": "Link", - "label": "Party Account Currency", - "options": "Currency", - "read_only": 1 } ], + "in_create": 1, "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-11-25 13:10:22.989664", + "modified": "2024-12-27 15:23:52.373306", "modified_by": "Administrator", "module": "India Banking", "name": "Bank Payment Request", @@ -459,7 +452,9 @@ "write": 1 } ], + "read_only": 1, "sort_field": "modified", "sort_order": "DESC", - "states": [] + "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 index 5275bf9..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 @@ -2,19 +2,21 @@ # 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 import _ +from frappe.model.document import Document +from frappe.utils.data import cstr, flt, today class BankPaymentRequest(PaymentRequest): @@ -23,7 +25,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 +38,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: @@ -41,13 +51,15 @@ 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() 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 +67,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 +95,42 @@ 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 - ) + _( + "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 +142,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 @@ -125,73 +163,41 @@ 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("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") + 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() 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', + "Bank Payment Request", { - 'party_type': 'Employee', - 'disabled': 0, - 'is_default': 1 - }, - ['name','party', 'party_type', 'bank', 'branch_code', 'bank_account_no', 'mobile_number', 'email', 'workflow_state'] + "reference_doctype": args.get("ref_doctype"), + "reference_name": args.get("ref_name"), + "docstatus": 1, + }, + "sum(grand_total) as grand_total", ) - employee_bank_account_details = {detail.get('party'): detail for detail in employee_bank_account_details} - - payment_details = [] + 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" - 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) - ) + return "" - 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): @@ -205,39 +211,45 @@ 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( + _( + "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 - 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, { + "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: @@ -245,7 +257,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" @@ -259,33 +273,37 @@ def make_bank_payment_request(**args): "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), + "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", + ), } ) @@ -302,51 +320,25 @@ def make_bank_payment_request(**args): 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): +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", @@ -363,7 +355,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" @@ -371,14 +363,13 @@ def set_missing_values(source, target): 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, '')}) + target.update({dimension: source.get(dimension, "")}) if source.references: target.append( @@ -390,12 +381,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( @@ -407,16 +402,16 @@ def update_missing_values(source, target): "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": source.party_bank_account, "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') not in ["Payment Entry", "Payroll Entry", "Journal Entry"]: + if args.get("ref_doctype") != "Payment Entry": doclist = get_mapped_doc( "Bank Payment Request", source_name, @@ -428,7 +423,7 @@ def update_missing_values(source, target): target_doc, set_missing_values, ) - elif args.get('ref_doctype') == "Payment Entry": + else: doclist = get_mapped_doc( "Payment Entry", source_name, @@ -440,35 +435,13 @@ def update_missing_values(source, target): 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): + +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. @@ -476,7 +449,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( """ @@ -488,9 +465,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""" @@ -506,9 +488,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: @@ -525,58 +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.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 + frappe.throw(_("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 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.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 7cfa7b6..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 @@ -1,38 +1,65 @@ # 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 import _ from frappe.model.document import Document from requests.models import Response +from india_banking.utils import extract_error_message + + class IndiaBankingRequestLog(Document): - pass + @frappe.whitelist() + def show_failure_message(self): + extract_error_message(json.loads(self.response), show_message=True) + + +def format_with_indent(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") 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 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() + return log_doc.name 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/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..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 @@ -7,8 +7,14 @@ "field_order": [ "summarise_payment_based_on", "notify_party", + "enable_payment_in_the_background", "column_break_wjye", - "default_email_format" + "default_email_format", + "activate_workflow_on_bank_account", + "automatic_update_configuration_section", + "update_posting_date_as_payment_date", + "column_break_laug", + "update_payment_status" ], "fields": [ { @@ -32,12 +38,46 @@ "fieldname": "notify_party", "fieldtype": "Check", "label": "Notify Party" + }, + { + "default": "1", + "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" + }, + { + "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": "2024-12-09 16:07:39.909616", + "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 c7f4809..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 @@ -1,9 +1,41 @@ # 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() + self.enable_payment_entry_reposting() + + 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, + ) + + 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/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..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", @@ -15,11 +16,15 @@ "message", "reference_number", "bank_account", + "email", + "branch_code", + "account_name", + "bank_account_no", + "bank", "accounting_section", "account", "tax_withholding_category", "payment_entry", - "payroll_entry", "journal_entry", "journal_entry_account", "reference_doctype", @@ -169,17 +174,11 @@ }, { "fieldname": "reference_name", - "fieldtype": "Data", + "fieldtype": "Dynamic Link", "label": "Reference Name", "options": "reference_doctype", "read_only": 1 }, - { - "fieldname": "payroll_entry", - "fieldtype": "Data", - "label": "Payroll Entry", - "read_only": 1 - }, { "fieldname": "journal_entry", "fieldtype": "Data", @@ -191,12 +190,54 @@ "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" + }, + { + "fieldname": "party_name", + "fieldtype": "Data", + "label": "Party Name", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-09-26 11:13:44.056821", + "modified": "2025-01-03 12:50:25.880199", "modified_by": "Administrator", "module": "India Banking", "name": "Payment Order Summary", 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/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/india_banking/override/bank.py b/india_banking/india_banking/override/bank.py deleted file mode 100644 index 0c98d01..0000000 --- a/india_banking/india_banking/override/bank.py +++ /dev/null @@ -1,9 +0,0 @@ -import frappe - -from erpnext.accounts.doctype.bank.bank import Bank -from frappe import _ - -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 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/india_banking/override/payment_order.py b/india_banking/india_banking/override/payment_order.py deleted file mode 100644 index 57fcd95..0000000 --- a/india_banking/india_banking/override/payment_order.py +++ /dev/null @@ -1,259 +0,0 @@ -import frappe, json -from erpnext.accounts.doctype.payment_order.payment_order import PaymentOrder -from india_banking.india_banking.doc_events.payment_order import make_payment_entries - -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) - 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}" - 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:] - 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 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}") - - def validate_summary(self): - if len(self.summary) <= 0: - frappe.throw("Please validate the summary") - - 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: - 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) - - references_total += ref.amount - - for sum in self.summary: - summary_total += sum.amount - - if summary_total != references_total: - 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' - 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"]: - 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 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) - - 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 - - - def before_cancel(self): - 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") - 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)) - - 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" - - if self.payment_order_type == "Bank 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"]: - for d in self.references: - frappe.db.set_value(self.payment_order_type, d.get(ref_doc_field), ref_field, status) - - -@frappe.whitelist() -def get_party_summary(references, company_bank_account): - 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) - - summary = {} - for ref in references: - ref = frappe._dict(ref) - key = _get_unique_key(ref) - - if key in summary: - summary[key] += ref.amount - else: - summary[key] = ref.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) } - 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 - - result.append(summary_line_item) - - 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 - - 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 - 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 '' \ No newline at end of file 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/install.py b/india_banking/install.py new file mode 100644 index 0000000..aef1aaf --- /dev/null +++ b/india_banking/install.py @@ -0,0 +1,614 @@ +import click +import frappe +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, + DEFAULT_WORKFLOW_ACTIONS, + DEFAULT_WORKFLOW_LIST, + DEFAULT_WORKFLOW_STATE, + STD_BANK_LIST, +) + + +def after_install(): + click.secho("* Updating India Banking Customisations") + toggle_payment_request_creation(True) + make_custom_fields() + create_property_setter() + toggle_reqd_for_reference_in_payment_order(False) + create_default_mode_of_transfers() + create_default_payment_type() + create_default_workflow() + create_default_bank() + + +def make_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() + + +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} + ) + + +def create_bank_doc_custom_fields(): + click.secho(" -> Installing Custom Fields in a Bank...") + fields = { + "Bank": [ + { + "label": "Standard", + "fieldname": "is_standard", + "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\nPayment Ordered\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", + }, + ] + } + + create_custom_fields(fields) + + +def create_supplier_custom_fields(): + click.secho(" -> Installing Custom Fields Supplier...") + 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 Custom Fields in a 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) + + +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, + }, + ], + "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_property_setter(): + 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_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": "File Reference Details", + "fieldname": "file_reference_details_section", + "fieldtype": "Section Break", + "insert_after": "account", + }, + { + "label": "File Sequence Number", + "fieldname": "file_sequence_number", + "fieldtype": "Data", + "read_only": 1, + "insert_after": "file_reference_details_section", + }, + { + "fieldtype": "Column Break", + "fieldname": "file_reference_details_column", + "insert_after": "file_sequence_number", + }, + { + "label": "Payment Summary", + "fieldname": "payment_summary", + "fieldtype": "Section Break", + "insert_after": "references", + }, + { + "label": "Summarise Payment Based On", + "fieldname": "summarise_payment_based_on", + "fieldtype": "Select", + "options": "Party\nVoucher", + "no_copy": 1, + "insert_after": "payment_summary", + }, + { + "label": "Get Summary", + "fieldname": "get_summary", + "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": "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": "payment_summary2", + "no_copy": 1, + }, + { + "label": "Total", + "fieldname": "total", + "fieldtype": "Currency", + "insert_after": "summary", + }, + ], + "Payment Order Reference": [ + { + "label": "Party Type", + "fieldname": "party_type", + "fieldtype": "Link", + "options": "DocType", + "in_list_view": 1, + "reqd": 1, + "insert_after": "column_break_4", + }, + { + "label": "Party", + "fieldname": "party", + "fieldtype": "Dynamic Link", + "options": "party_type", + "in_list_view": 1, + "reqd": 1, + "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", + }, + { + "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": "Bank", + "fieldname": "bank", + "fieldtype": "Data", + "read_only": 1, + "insert_after": "journal_entry_account", + "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", + }, + ], + } + + create_custom_fields(fields) + + +def create_payment_entry_custom_fields(): + click.secho(" -> Installing Custom 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 Fields in a Bank Account...") + fields = { + "Bank Account": [ + { + "label": "Mobile Number", + "fieldname": "mobile_number", + "mandatory_depends_on": "is_company_account", + "fieldtype": "Data", + "insert_after": "iban", + }, + { + "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, + }, + { + "label": "Currency", + "fieldname": "currency", + "fieldtype": "Link", + "options": "Currency", + "insert_after": "email", + "reqd": 1, + }, + ] + } + + 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}, + ) + frappe.db.set_value( + "DocField", + {"parent": "Payment Order", "fieldname": "payment_order_type"}, + "options", + "\nPayment Request\nPayment Entry\nJournal Entry", + ) + + +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") + bank_doc.bank_name = bank + bank_doc.is_standard = 1 + 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) + + +def create_default_payment_type(): + 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(): + 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/patches/migrate/__init__.py b/india_banking/overrides/__init__.py similarity index 100% rename from india_banking/patches/migrate/__init__.py rename to india_banking/overrides/__init__.py diff --git a/india_banking/overrides/journal_entry.py b/india_banking/overrides/journal_entry.py new file mode 100644 index 0000000..b3a4fd2 --- /dev/null +++ b/india_banking/overrides/journal_entry.py @@ -0,0 +1,183 @@ +import frappe +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() +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", + "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.payment_status.notin( + ["Paid", "Ordered", "Payment Ordered"] + ) + ) + & (BankAccount.disabled == 0) + & (BankAccount.is_default == 1) + ) + ) + + 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: + 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", + "reference_name": journal_account.journal, + "journal_entry_account": journal_account.name, + "amount": journal_account.amount, + "party_type": journal_account.party_type, + "party": journal_account.party, + "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( + "Journal Entry", + source_name, + { + "Journal Entry": { + "doctype": "Payment Order", + } + }, + target_doc, + update_bank_entry, + ) + + return doclist + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_bank_entry(doctype, txt, searchfield, start, page_len, filters, as_dict): + 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", "Payment Ordered"] + ) + ) + & (JournalEntry.voucher_type == "Bank Entry") + & (JournalEntryAccount.against_account == filters.company_account) + ) + .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 diff --git a/india_banking/overrides/payment_entry.py b/india_banking/overrides/payment_entry.py new file mode 100644 index 0000000..a98c104 --- /dev/null +++ b/india_banking/overrides/payment_entry.py @@ -0,0 +1,113 @@ +import frappe +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_accounting_dimensions, +) +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() +def make_payment_order(source_name, target_doc=None): + def set_missing_values(source, target): + target.payment_order_type = "Payment Entry" + target.company_bank_account = source.bank_account + target.party = "" + + account = source.paid_to if source.paid_to else "" + + def _update_dimensions(source): + return { + dimension: source.get(dimension, "") + for dimension in get_accounting_dimensions() + } + + 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 + ) + ) + ) + + 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 { + "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 + if reference + else "Wire Transfer", + "bank_account": _get_default_bank_account( + source.party_type, source.party + ), + "account": account if reference else source.paid_to, + "cost_center": source.cost_center, + "project": source.project, + "payment_entry": source.name, + **_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.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/overrides/payment_order.py b/india_banking/overrides/payment_order.py new file mode 100644 index 0000000..b6c886b --- /dev/null +++ b/india_banking/overrides/payment_order.py @@ -0,0 +1,249 @@ +import json + +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 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 + + +class CustomPaymentOrder(PaymentOrder): + def before_submit(self): + self.validate_bank_payment_request() + + def validate_bank_payment_request(self): + if self.references: + for ref in self.references: + if ref.payment_request: + payment_request = frappe.get_doc( + "Payment Request", ref.payment_request + ) + 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)) + + def validate(self): + self.validate_summary() + + 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 in ["NEFT", "RTGS"] + and payment.amount >= 500000000 + ): + lei_number = frappe.db.get_value( + payment.party_type, payment.party, "lei_number" + ) + if not lei_number: + frappe.throw( + _( + 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}" + ) + ) + + if not mode_of_transfer: + frappe.throw(_("Define a specific mode of transfer or a default one")) + + 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}" + ) + ) + + payment.mode_of_transfer = mode_of_transfer.mode + summary_total += payment.amount + + references_total = 0 + for reference in self.references: + reference.party_name = frappe.get_value( + reference.party_type, + reference.party, + self.get_party_field_name(reference), + ) + references_total += reference.amount + + if summary_total != references_total: + 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" + elif party.party_type == "Shareholder": + return "name" + elif party.party_type == "Customer": + return "customer_name" + else: + return "name" + + def on_submit(self): + if self.payment_order_type in [ + "Payment Request", + "Payment Entry", + "Journal Entry", + ]: + if self.payment_order_type == "Payment Request": + make_payment_entries(self.name) + + self.update_payment_status() + + def on_update_after_submit(self): + 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" + ) + ) + super().on_cancel() + + def on_trash(self): + if self.docstatus == 1: + frappe.throw(_("You cannot delete a payment order")) + + def update_payment_status(self, cancel=False): + self.db_set("status", "Pending") + + 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: + 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), + ref_field, + status, + ) + + +@frappe.whitelist() +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 + + # 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 = PAYMENT_SUMMARIES_FIELDS + summarise_field.extend(get_accounting_dimensions()) + + if summarise_payment_based_on == "Party": + 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 reference in references: + reference = frappe._dict(reference) + key = _get_unique_key(reference) + + if key in summary: + summary[key] += reference.amount + else: + 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_only=True), key) + } + 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") + + summary_line_item.update( + { + "amount": val, + "mode_of_transfer": get_mode_of_transfer(val, party_bank, company_bank), + } + ) + + 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 diff --git a/india_banking/overrides/payment_request.py b/india_banking/overrides/payment_request.py new file mode 100644 index 0000000..0cdadb0 --- /dev/null +++ b/india_banking/overrides/payment_request.py @@ -0,0 +1,263 @@ +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, +) +from erpnext.accounts.party import get_party_bank_account +from frappe import _ +from frappe.utils import get_url_to_form + + +class BankPaymentRequest(PaymentRequest): + def validate(self): + self.set_default_value() + 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: + 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")) + + if self.remarks: + self.remarks = self.remarks[:48] + + self.valdidate_bank_for_wire_transfer() + + def set_default_value(self): + if not self.payment_type: + if payment_type := frappe.db.exists( + "Payment Type", + { + "company": self.company, + "is_default": 1, + }, + ): + 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: + 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, 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( + 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") + + 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 "" + ) + + 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": + if not self.bank_account: + frappe.throw(_("Bank Account is missing for Wire Transfer Payments")) + + bank_account = frappe.get_doc("Bank Account", self.bank_account) + + if ( + frappe.db.get_single_value( + "India Banking Settings", "activate_workflow_on_bank_account" + ) + and 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", 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() +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 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" + ) + + 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, + ) + 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/patches.txt b/india_banking/patches.txt index 069db8d..76bdf41 100644 --- a/india_banking/patches.txt +++ b/india_banking/patches.txt @@ -2,7 +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.india_banking.install #2 +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/migrate/after_migrate.py b/india_banking/patches/migrate/after_migrate.py deleted file mode 100644 index 5ed0044..0000000 --- a/india_banking/patches/migrate/after_migrate.py +++ /dev/null @@ -1,5 +0,0 @@ -import frappe -from india_banking.india_banking.install import create_default_bank - -def execute(): - create_default_bank() \ 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..dd852a5 --- /dev/null +++ b/india_banking/patches/v1_0/update_custom_fields.py @@ -0,0 +1,10 @@ +import frappe + +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() + make_custom_fields() \ No newline at end of file diff --git a/india_banking/public/js/bank_account.js b/india_banking/public/js/bank_account.js index cd308b6..a9500aa 100644 --- a/india_banking/public/js/bank_account.js +++ b/india_banking/public/js/bank_account.js @@ -1,32 +1,36 @@ -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.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_account_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(); + }, +}); diff --git a/india_banking/public/js/payment_order.js b/india_banking/public/js/payment_order.js index 12ec942..f1e467b 100644 --- a/india_banking/public/js/payment_order.js +++ b/india_banking/public/js/payment_order.js @@ -1,363 +1,513 @@ -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') - } - - 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.remove_custom_button("Payment Entry", "Get Payments from"); - frm.remove_custom_button("Payment Request", "Get Payments from"); - - frm.set_df_property("payment_order_type", "options", [""].concat(["Bank Payment Request", "Payment Entry", "Purchase Invoice"])); - frm.refresh_field("payment_order_type"); - - 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}) - - 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")); - - frm.add_custom_button(__('Payment Entry'), function() { - 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: "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(); - } - }); - }); - } - }) - } - } - } - - 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 - } - } - - 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 - } - }; - }); - }, - - remove_button: function(frm) { - // remove custom button of order type that is not imported - let label = ["Payment Request", "Purchase Invoice"]; - - 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; - - } - 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 - } - - if (!frm.doc.approval_status) { - frappe.msgprint("Updating status is not allowed without value"); - return - } - - var selected_rows = frm.get_selected() - if (!Object.keys(selected_rows).length || !"summary" in selected_rows){ - frappe.msgprint("No rows are selected"); - return - } - - 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(); - } - }); - } +frappe.ui.form.on("Payment Order", { + onload(frm) { + // Set summary based on party or voucher + 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()) { + if (frm.doc.references) { + cur_frm.clear_table("references"); + } + } + + // Set query for the company_bank_account field + frm.set_query("company_bank_account", () => { + return { + filters: { + company: frm.doc.company, + is_company_account: 1, + }, + }; + }); + + // Set query for the mode_of_transfer field in the summary child table + frm.set_query("mode_of_transfer", "summary", () => { + return { + filters: { + disabled: 0, + }, + }; + }); + frm.set_query("default_mode_of_transfer", () => { + return { + filters: { + disabled: 0, + }, + }; + }); + + // Set properties for the summary table + 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) { + 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("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 + 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"); + + // Collect existing payment requests from references table, if any + const existing_payment_requests = (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", + target: frm, + setters: { + party_type: "", + party: "", + grand_total: "", + currency: "INR", + }, + get_query_filters: { + docstatus: 1, + status: ["=", "Initiated"], + bank: frm.doc.bank, + name: ["not in", existing_payment_requests], + company: frm.doc.company, + }, + }); + }, + + get_payments_from_payment_entry(frm) { + // Ensure references table is clean before processing + frm.trigger("remove_row_if_empty"); + + // Collect existing payment entries from the references table, if any + const existing_payment_entries = (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", + target: frm, + setters: { + party: "", + paid_amount: "", + }, + get_query_filters: { + docstatus: 1, + 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, + }, + }); + }, + + get_payments_from_journal_entry(frm) { + 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: true, + }, + { + fieldtype: "Currency", + label: "Amount", + fieldname: "total", + hidden: true, + }, + ], + get_query: function () { + // Extract unique reference names from the references table + const existing_journal_entries = [ + ...new Set( + (frm.doc.references || []).map( + (reference) => reference.reference_name + ) + ), + ]; + return { + query: "india_banking.overrides.journal_entry.get_bank_entry", + filters: { + docs: existing_journal_entries, + company_account: frm.doc.account, + }, + }; + }, + }); + }, + + 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 has_pending_payments = frm.doc.summary.some( + (item) => item.payment_status === "Pending" + ); + + if (has_pending_payments) { + // 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.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) { + frappe.call({ + method: + "india_banking.india_banking.doctype.bank_connector.bank_connector.make_payment", + freeze: true, + freeze_message: __("Initiating Payment..."), + args: { + payment_order: frm.doc.name, + }, + callback: (res) => { + 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(); + }, + }); + }, + + verify_otp(frm) { + frappe.prompt( + { + 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: true, + freeze_message: __("Verifying OTP and processing payment..."), + args: { + payment_order: frm.doc.name, + otp: otp, + }, + callback: function (r) { + if (!r.exc) { + frm.reload_doc(); // Reload form to reflect changes + } + }, + }); + }, + __("Sent an OTP to your registered mobile number"), + __("Proceed") + ); + }, + + remove_button: function (frm) { + // 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 + ) { + // Define the mapping of payment_order_type to buttons + 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 buttons_to_remove = + button_mapping[frm.doc.payment_order_type] || []; + + // Iterate over the buttons and remove them + buttons_to_remove.forEach((button) => { + frm.remove_custom_button(button, "Get Payments 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.overrides.payment_order.get_party_summary", + args: { + references: frm.doc.references, + company_bank_account: frm.doc.company_bank_account, + summarise_payment_based_on: frm.doc.summarise_payment_based_on, + }, + freeze: true, + callback: function (r) { + if (r.message && !r.exc) { + frm.clear_table("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(); + } + }, + }); + }, }); -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 +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/public/js/payment_order_list.js b/india_banking/public/js/payment_order_list.js index b9930ed..4c06eb5 100644 --- a/india_banking/public/js/payment_order_list.js +++ b/india_banking/public/js/payment_order_list.js @@ -1,17 +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 (doc.status == "Completed") { - 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"]; - } - }, + 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_request.js b/india_banking/public/js/payment_request.js index 723bd0a..f022928 100644 --- a/india_banking/public/js/payment_request.js +++ b/india_banking/public/js/payment_request.js @@ -1,71 +1,76 @@ -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 - } - }; - }); - }, - 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"); + } + + set_payment_type_query(frm); + set_bank_account_query(frm); + }, + company(frm) { + set_payment_type_query(frm); + }, + mode_of_payment(frm) { + set_bank_account_query(frm); + }, + party_type(frm) { + set_bank_account_query(frm); + }, + party(frm) { + set_bank_account_query(frm); + }, }); -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 +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 { + filters: conditions, + }; + }); + } +} + +function get_bank_query_conditions(frm) { + let conditions = { + company: frm.doc.company, + 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; + } + if (frm.doc.party) { + conditions["party"] = frm.doc.party; + } + return conditions; +} diff --git a/india_banking/public/js/payment_type.js b/india_banking/public/js/payment_type.js deleted file mode 100644 index b952e39..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" - } - }; - }); - } -}) \ No newline at end of file diff --git a/india_banking/public/js/purchase_invoice.js b/india_banking/public/js/purchase_invoice.js deleted file mode 100644 index 672367f..0000000 --- a/india_banking/public/js/purchase_invoice.js +++ /dev/null @@ -1,40 +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 Request", "Create") - 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 cfa216f..0000000 --- a/india_banking/public/js/purchase_order.js +++ /dev/null @@ -1,58 +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 Request", "Create") - 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 diff --git a/india_banking/tasks.py b/india_banking/tasks.py index 87b61f4..3cf12fc 100644 --- a/india_banking/tasks.py +++ b/india_banking/tasks.py @@ -1,17 +1,90 @@ import frappe -from india_banking.india_banking.doc_events.payment_order import get_payment_status +from frappe.query_builder import DocType + +from india_banking.india_banking.doctype.bank_connector.bank_connector import ( + get_payment_status, +) + + def daily(): - orders = frappe.get_all("Payment Order Summary", - { - 'docstatus': 1, - 'payment_status': 'Initiated' - }, - pluck= 'parent', distinct='parent') + 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": ["in", ["Pending", "Initiated"]]}, + pluck="parent", + distinct="parent", + ) 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", message=frappe.get_traceback()) \ No newline at end of file + frappe.log_error( + 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(), + ) diff --git a/india_banking/uninstall.py b/india_banking/uninstall.py new file mode 100644 index 0000000..774340c --- /dev/null +++ b/india_banking/uninstall.py @@ -0,0 +1,148 @@ +import click +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, + 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) + 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(): + click.secho("* Removing India Banking Customizations") + fieldnames = { + "Journal Entry Account": [ + "payment_details", + "payment_status", + "payment_details_column_break", + "reference_number", + ], + "Bank Account": [ + "mobile_number", + "email", + "bank_balance", + "currency", + ], + "Payment Entry": [ + "source_section", + "source_doctype", + "source_column", + "source_name", + ], + "Payment Request": [ + "payment_type", + "is_adhoc", + "net_total", + "taxes_deducted", + "apply_tax_withholding_amount", + "tax_withholding_category", + "payment_term", + "remarks", + "remark_section", + ], + "Payment Order": [ + "company_account_number", + "company_bank_account_name", + "company_ifsc", + "mobile_number", + "is_party_wise", + "unique_id", + "file_sequence_number", + "file_reference_id", + "summarise_payment_based_on", + "get_summary", + "default_mode_of_transfer", + "summary", + "total", + "status", + "icici_bank_api_info", + "file_reference_details_section", + "payment_summary", + "bank_api_info_column_break", + "file_reference_details_column", + "payment_summary_column_break", + "amended_from", + ], + "Payment Order Reference": [ + "bank_payment_request", + "party_name", + "party_type", + "party", + "tax_withholding_category", + "is_adhoc", + "payment_term", + "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"], + } + + for doctype, fieldnames in fieldnames.items(): + click.secho(f" -> Uninstalling Custom Fields from {doctype}") + for fieldname in fieldnames: + frappe.db.delete("Custom Field", {"dt": doctype, "fieldname": fieldname}) + + frappe.clear_cache(doctype=doctype) + + +def delete_propert_setters(): + delete_payment_request_property_setter() + + +def delete_payment_request_property_setter(): + 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) diff --git a/india_banking/utils.py b/india_banking/utils.py index 7955249..fb790f9 100644 --- a/india_banking/utils.py +++ b/india_banking/utils.py @@ -1,65 +1,84 @@ -import frappe, json -from datetime import datetime +import json + +import frappe from frappe import _ -import requests -from frappe.utils import get_link_to_form +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: + 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 "" -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), + if len(building_number) > 10: + building_number = building_number[:10] - }).insert(ignore_permissions=True) - frappe.db.commit() - return log.name + post_code = party_address_.get("pincode", "") + town_name = ( + party_address_.get("state", "")[:3].upper() + if party_address_.get("state", "") + else "" + ) -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), + 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, + } - if response.ok: - return response.text - else: - return response.text +def get_party_field_name(party_type): + return { + "Supplier": "supplier_name", + "Customer": "customer_name", + "Employee": "employee_name", + }.get(party_type, "name") -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: - 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 '' - 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, - "BuildingNumber": building_number, - "PostCode": post_code, - "TownName": town_name, - "CountySubDivision": country_sub_division, - "Country": country - } +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), + )