diff --git a/check_run/check_run/doctype/check_run/check_run.js b/check_run/check_run/doctype/check_run/check_run.js index d912994b..6cd8c069 100644 --- a/check_run/check_run/doctype/check_run/check_run.js +++ b/check_run/check_run/doctype/check_run/check_run.js @@ -49,6 +49,11 @@ frappe.ui.form.on('Check Run', { permit_first_user(frm) get_defaults(frm) set_queries(frm) + if (frm.doc.docstatus == 1 && frm.doc.sepa_file_generated == 0 && frm.pay_to_account_currency == 'EUR') { + downloadsepa(frm) + } else if (frm.doc.sepa_file_generated == 1 && frm.doc.docstatus == 1 && frm.pay_to_account_currency == 'EUR') { + gen_sepa_xml(frm) + } frappe.realtime.off('reload') frappe.realtime.on('reload', message => { frm.reload_doc() @@ -428,3 +433,33 @@ function check_settings(frm) { }) } } + +function gen_sepa_xml(frm) { + frappe.xcall('check_run.check_run.doctype.check_run.check_run.get_authorized_role', { doc: frm.doc }).then(r => { + if (frappe.user.has_role(r)) { + downloadsepa(frm) + } + }) +} + +function downloadsepa(frm) { + frm.add_custom_button('Download SEPA', () => { + frappe.xcall('check_run.check_run.doctype.check_run.gen_sepa_xml.gen_sepa_xml_file', { doc: frm.doc }).then(r => { + console.log(r) + downloadXML('payments.xml', r) + if(frm.doc.sepa_file_generated == 0){ + frappe.db.set_value('Check Run', frm.doc.name, 'sepa_file_generated', 1) + } + }) + }) +} + +function downloadXML(filename, content) { + var element = document.createElement('a') + element.setAttribute('href', 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(content)) + element.setAttribute('download', filename) + element.style.display = 'none' + document.body.appendChild(element) + element.click() + document.body.removeChild(element) +} diff --git a/check_run/check_run/doctype/check_run/check_run.json b/check_run/check_run/doctype/check_run/check_run.json index 3181fabc..b4815231 100644 --- a/check_run/check_run/doctype/check_run/check_run.json +++ b/check_run/check_run/doctype/check_run/check_run.json @@ -1,196 +1,206 @@ { - "actions": [], - "allow_copy": 1, - "allow_events_in_timeline": 1, - "autoname": "ACC-CR-.YYYY.-.#####", - "creation": "2018-05-22 14:01:11.307993", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "end_date", - "posting_date", - "beg_balance", - "company_discretionary_data", - "column_break_3", - "initial_check_number", - "final_check_number", - "amount_check_run", - "column_break_6", - "company", - "bank_account", - "pay_to_account", - "amended_from", - "section_break_9", - "check_run_table", - "transactions", - "print_count", - "status" - ], - "fields": [ - { - "fieldname": "end_date", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Check Run End Date" - }, - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, - { - "allow_on_submit": 1, - "fieldname": "initial_check_number", - "fieldtype": "Int", - "in_list_view": 1, - "label": "Initial Check Number" - }, - { - "allow_on_submit": 1, - "fieldname": "final_check_number", - "fieldtype": "Int", - "in_list_view": 1, - "label": "Final Check Number", - "read_only": 1 - }, - { - "default": "0.00", - "fieldname": "amount_check_run", - "fieldtype": "Currency", - "in_list_view": 1, - "label": "Amount in Check Run", - "read_only": 1 - }, - { - "fieldname": "column_break_6", - "fieldtype": "Column Break" - }, - { - "allow_in_quick_entry": 1, - "fieldname": "company", - "fieldtype": "Link", - "in_standard_filter": 1, - "label": "Company", - "options": "Company", - "remember_last_selected_value": 1, - "reqd": 1 - }, - { - "allow_in_quick_entry": 1, - "fetch_from": "company.default_bank_account", - "fetch_if_empty": 1, - "fieldname": "bank_account", - "fieldtype": "Link", - "label": "Paid From (Bank Account)", - "options": "Bank Account", - "remember_last_selected_value": 1, - "reqd": 1 - }, - { - "default": "company.default_payable_account", - "fieldname": "pay_to_account", - "fieldtype": "Link", - "in_standard_filter": 1, - "label": "Accounts Payable", - "options": "Account", - "remember_last_selected_value": 1, - "reqd": 1 - }, - { - "allow_on_submit": 1, - "depends_on": "eval:doc.docstatus==1", - "fieldname": "company_discretionary_data", - "fieldtype": "Data", - "label": "Company Discretionary Data", - "length": 20 - }, - { - "fieldname": "section_break_9", - "fieldtype": "Section Break" - }, - { - "default": "0.00", - "fieldname": "beg_balance", - "fieldtype": "Currency", - "label": "Beginning Bank Account Balance", - "read_only": 1 - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 1, - "label": "Amended From", - "no_copy": 1, - "options": "Check Run", - "print_hide": 1, - "read_only": 1 - }, - { - "allow_on_submit": 1, - "fieldname": "transactions", - "fieldtype": "Long Text", - "hidden": 1, - "ignore_xss_filter": 1 - }, - { - "allow_on_submit": 1, - "fieldname": "print_count", - "fieldtype": "Int", - "hidden": 1 - }, - { - "allow_on_submit": 1, - "default": "Draft", - "fieldname": "status", - "fieldtype": "Select", - "hidden": 1, - "options": "Draft\nSubmitting\nSubmitted\nReady to Print\nConfirm Print\nPrinted" - }, - { - "fieldname": "check_run_table", - "fieldtype": "HTML" - }, - { - "fieldname": "posting_date", - "fieldtype": "Date", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Posting Date" - } - ], - "is_submittable": 1, - "links": [], - "modified": "2023-03-10 12:28:36.910004", - "modified_by": "Administrator", - "module": "Check Run", - "name": "Check Run", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "read": 1, - "report": 1, - "role": "Accounts User", - "select": 1, - "write": 1 - }, - { - "create": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "select": 1, - "share": 1, - "submit": 1, - "write": 1 - } - ], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 1, - "track_views": 1 -} + "actions": [], + "allow_copy": 1, + "allow_events_in_timeline": 1, + "autoname": "ACC-CR-.YYYY.-.#####", + "creation": "2018-05-22 14:01:11.307993", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "end_date", + "posting_date", + "beg_balance", + "company_discretionary_data", + "column_break_3", + "initial_check_number", + "final_check_number", + "amount_check_run", + "column_break_6", + "company", + "bank_account", + "pay_to_account", + "amended_from", + "section_break_9", + "check_run_table", + "transactions", + "print_count", + "status", + "sepa_file_generated" + ], + "fields": [ + { + "fieldname": "end_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Check Run End Date" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "fieldname": "initial_check_number", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Initial Check Number" + }, + { + "allow_on_submit": 1, + "fieldname": "final_check_number", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Final Check Number", + "read_only": 1 + }, + { + "default": "0.00", + "fieldname": "amount_check_run", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount in Check Run", + "read_only": 1 + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "allow_in_quick_entry": 1, + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "options": "Company", + "remember_last_selected_value": 1, + "reqd": 1 + }, + { + "allow_in_quick_entry": 1, + "fetch_from": "company.default_bank_account", + "fetch_if_empty": 1, + "fieldname": "bank_account", + "fieldtype": "Link", + "label": "Paid From (Bank Account)", + "options": "Bank Account", + "remember_last_selected_value": 1, + "reqd": 1 + }, + { + "default": "company.default_payable_account", + "fieldname": "pay_to_account", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Accounts Payable", + "options": "Account", + "remember_last_selected_value": 1, + "reqd": 1 + }, + { + "allow_on_submit": 1, + "depends_on": "eval:doc.docstatus==1", + "fieldname": "company_discretionary_data", + "fieldtype": "Data", + "label": "Company Discretionary Data", + "length": 20 + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break" + }, + { + "default": "0.00", + "fieldname": "beg_balance", + "fieldtype": "Currency", + "label": "Beginning Bank Account Balance", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "hidden": 1, + "label": "Amended From", + "no_copy": 1, + "options": "Check Run", + "print_hide": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "transactions", + "fieldtype": "Long Text", + "hidden": 1, + "ignore_xss_filter": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "print_count", + "fieldtype": "Int", + "hidden": 1 + }, + { + "allow_on_submit": 1, + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "hidden": 1, + "options": "Draft\nSubmitting\nSubmitted\nReady to Print\nConfirm Print\nPrinted" + }, + { + "fieldname": "check_run_table", + "fieldtype": "HTML" + }, + { + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Posting Date" + }, + { + "allow_on_submit": 1, + "default": "0", + "fieldname": "sepa_file_generated", + "fieldtype": "Check", + "hidden": 1 + } + ], + "is_submittable": 1, + "links": [], + "modified": "2024-04-22 07:22:02.167863", + "modified_by": "Administrator", + "module": "Check Run", + "name": "Check Run", + "naming_rule": "Expression (old style)", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "select": 1, + "write": 1 + }, + { + "create": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "select": 1, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1, + "track_seen": 1, + "track_views": 1 +} \ No newline at end of file diff --git a/check_run/check_run/doctype/check_run/check_run.py b/check_run/check_run/doctype/check_run/check_run.py index f1a9ad28..0928f0a1 100644 --- a/check_run/check_run/doctype/check_run/check_run.py +++ b/check_run/check_run/doctype/check_run/check_run.py @@ -912,3 +912,14 @@ def ach_only(docname: str) -> dict: def process_check_run(docname: str) -> None: doc = frappe.get_doc("Check Run", docname) doc.process_check_run() + + +@frappe.whitelist() +def get_authorized_role(doc): + doc = frappe._dict(json.loads(doc)) if isinstance(doc, str) else doc + role = frappe.db.get_value( + "Check Run Settings", + {"pay_to_account": doc.pay_to_account, "bank_account": doc.bank_account}, + "sepa_authorized_role", + ) + return role diff --git a/check_run/check_run/doctype/check_run/gen_sepa_xml.py b/check_run/check_run/doctype/check_run/gen_sepa_xml.py new file mode 100644 index 00000000..c13412d1 --- /dev/null +++ b/check_run/check_run/doctype/check_run/gen_sepa_xml.py @@ -0,0 +1,275 @@ +import frappe +from frappe.utils import getdate, get_link_to_form, get_url +import time + + +@frappe.whitelist() +def gen_sepa_xml_file(doc): + doc = frappe.parse_json(doc) + payments = frappe.parse_json(doc.transactions) + posting_date = getdate() + content = genrate_file_for_sepa(payments, doc, posting_date) + return content + + +def genrate_file_for_sepa(payments, doc, posting_date): + # Message Root + content = make_line("") + content += make_line( + "" + ) + content += make_line(" ") + + # Group Header + content += make_line(" ") + content += make_line(" {}".format(time.strftime("%Y%m%d%H%M%S"))) + content += make_line(" {}".format(time.strftime("%Y-%m-%dT%H:%M:%S"))) + transaction_count = 0 + transaction_count_identifier = "" + content += make_line(f" {transaction_count_identifier}") + control_sum = 0.0 + control_sum_identifier = "" + content += make_line(f" {control_sum_identifier}") + content += make_line(" ") + content += make_line(f" {doc.company}") + content += make_line(" ") + + orgid = get_party_orgid(doc.company, doc.bank_account, doc.pay_to_account) + + initiating_party_org_id = orgid.get("initiating_party_org_id", None) + if not initiating_party_org_id: + frappe.throw( + frappe._( + "Please specify 'Initiating Party OrgID' in {}".format( + f"Check Run Settings" + ) + ) + ) + content += make_line(" ") + content += make_line(" ") + content += make_line(f" {initiating_party_org_id}") + content += make_line(" ") + content += make_line(" BANK") + content += make_line(" ") + content += make_line(" ") + content += make_line(" ") + content += make_line(" ") + content += make_line(" ") + content += make_line(" ") + + # Payment Information Elements + content += make_line(" ") + content += make_line(f" {doc.name}") + content += make_line(" TRF") + content += make_line(" false") + content += make_line(f" {transaction_count_identifier}") + + content += make_line(f" {control_sum_identifier}") + content += make_line(" ") + content += make_line(" ") + content += make_line(" SEPA") + content += make_line(" ") + content += make_line(" ") + required_execution_date = posting_date + content += make_line(f" {required_execution_date}") + content += make_line(" ") + content += make_line(f" {doc.company}") + # Address + addr = debtors_address(doc.company, doc.bank_account, doc.pay_to_account) + content += make_line(" ") + content += make_line( + " {}".format(addr.pincode if addr.pincode else "") + ) + content += make_line( + " {}".format(addr.address_line1 if addr.address_line1 else "") + ) + content += make_line(" {}".format(addr.city if addr.city else "")) + content += make_line( + " {}".format(addr.address_line2 if addr.address_line2 else "") + ) + content += make_line(" ") + content += make_line(" ") + content += make_line(" ") + content += make_line(" ") + debtor_org_id = orgid.get("debtor_org_id", None) + if not debtor_org_id: + frappe.throw( + frappe._( + "Please specify 'Debtor Org Id' in {}".format( + f"Check Run Settings" + ) + ) + ) + content += make_line(f" {debtor_org_id}") + content += make_line(" ") + content += make_line(" BANK") + content += make_line(" ") + content += make_line(" ") + content += make_line(" ") + content += make_line(" ") + content += make_line(" SE") + content += make_line(" ") + content += make_line(" ") + content += make_line(" ") + iban = get_iban_number(doc.company, doc.bank_account, doc.pay_to_account) + content += make_line(f" {iban}") + content += make_line(" ") + content += make_line(" EUR") + content += make_line(" ") + content += make_line(" ") + content += make_line( + " " + ) + content += make_line(" ") + bank_bic = frappe.db.get_value("Bank Account", doc.bank_account, "branch_code") # optional + if bank_bic: + content += make_line(f" {bank_bic}") + else: + content += make_line(" ") + content += make_line(" NOTPROVIDED") + content += make_line(" ") + content += make_line(" ") + content += make_line(" ") + content += make_line(" SLEV") + for payment in payments: + payment_record = frappe.get_doc("Payment Entry", payment.get("payment_entry")) + content += make_line(" ") + content += make_line(" ") + content += make_line( + " {}".format(payment.get("payment_entry")) + ) + content += make_line( + " {}".format( + payment.get("payment_entry").replace("-", "") + ) + ) + content += make_line(" ") + content += make_line(" ") + content += make_line( + ' {:.2f}'.format( + payment_record.paid_from_account_currency, payment_record.paid_amount + ) + ) + content += make_line(" ") + content += make_line( + " " + ) + content += make_line(" ") + if payment_record.party_type == "Supplier": + name = frappe.db.get_value("Supplier", payment_record.party, "supplier_name") + if "&" in name: + new_name = name.replace("& ", "") + if new_name == name: + new_name = name.replace("&", " ") + name = new_name + content += make_line(f" {name}") + content += make_line(" ") + content += make_line(" ") + content += make_line(" ") + + iban_code = get_party_iban_code(payment_record.party_type, payment_record.party) + + content += make_line( + " {}".format(iban_code.strip() if iban_code else "") + ) + content += make_line(" ") + content += make_line(" ") + content += make_line(" ") + sup_invoice_no = [ + frappe.db.get_value("Purchase Invoice", row.reference_name, "bill_no") + for row in payment_record.references + ] + + content += make_line( + " {}".format( + ", ".join(sup_invoice_no) if sup_invoice_no[0] else "" + ) + ) + content += make_line(" ") + content += make_line(" ") + transaction_count += 1 + control_sum += payment_record.paid_amount + content += make_line(" ") + + # Finished tags + content += make_line(" ") + content += make_line("") + content = content.replace(transaction_count_identifier, f"{transaction_count}") + content = content.replace(control_sum_identifier, f"{control_sum:.2f}") + return content + + +def get_company_name(payment_entry): + return frappe.get_value("Payment Entry", payment_entry, "company") + + +def make_line(line): + return line + "\r\n" + + +def get_iban_number(company, bank_account, pay_to_account): + bank_iban = frappe.db.get_value("Bank Account", bank_account, "iban") + + if bank_iban: + return bank_iban + else: + frappe.throw( + frappe._(f"Iban no is missing in bank account {get_link_to_form('Bank Account', bank_account)}") + ) + + +def get_party_iban_code(party_type, party): + party_iban = frappe.db.sql( + f""" + Select iban + From `tabBank Account` + Where party_type = '{party_type}' and party = '{party}' + """, + as_dict=1, + ) + + if party_iban: + return party_iban[0].iban + else: + frappe.throw( + frappe._(f"Iban Code is not available for {party_type} {get_link_to_form(party_type, party)}") + ) + + +def get_party_orgid(company, bank_account, pay_to_account): + orgid = frappe.db.sql( + f""" + Select initiating_party_org_id, debtor_org_id + From `tabCheck Run Settings` + Where company = '{company}' and pay_to_account = '{pay_to_account}' and bank_account = '{bank_account}' + """, + as_dict=1, + ) + return orgid[0] + + +def debtors_address(company, bank_account, pay_to_account): + if crs_name := frappe.db.exists( + "Check Run Settings", {"bank_account": bank_account, "pay_to_account": pay_to_account} + ): + address = frappe.db.get_value("Check Run Settings", crs_name, "debtors_address") + if not address: + frappe.throw( + frappe._( + "Please enter a Debtor's Address in check run settings {}".format( + get_link_to_form("Check Run Settings", crs_name) + ) + ) + ) + address_doc = frappe.get_doc("Address", address) + return address_doc + + +def get_check_run_settings_link(doc): + url = get_url() + check_run_settings_path = "/app/check-run-settings/" + check_run_settings = frappe.db.exists( + "Check Run Settings", {"bank_account": doc.bank_account, "pay_to_account": doc.pay_to_account} + ) + check_run_settings_url = url + check_run_settings_path + check_run_settings + return str(check_run_settings_url) diff --git a/check_run/check_run/doctype/check_run_settings/check_run_settings.js b/check_run/check_run/doctype/check_run_settings/check_run_settings.js index 9ba74675..74ca71fa 100644 --- a/check_run/check_run/doctype/check_run_settings/check_run_settings.js +++ b/check_run/check_run/doctype/check_run_settings/check_run_settings.js @@ -27,5 +27,11 @@ frappe.ui.form.on('Check Run Settings', { }, } }) + frm.set_query('debtors_address', function () { + return { + query: 'frappe.contacts.doctype.address.address.address_query', + filters: { link_doctype: 'Company', link_name: frm.doc.company }, + } + }) }, }) diff --git a/check_run/check_run/doctype/check_run_settings/check_run_settings.json b/check_run/check_run/doctype/check_run_settings/check_run_settings.json index d1243c27..cd92a9d5 100644 --- a/check_run/check_run/doctype/check_run_settings/check_run_settings.json +++ b/check_run/check_run/doctype/check_run_settings/check_run_settings.json @@ -1,268 +1,306 @@ { - "actions": [], - "autoname": "format:ACC-CRS-{bank_account}-{pay_to_account}", - "creation": "2022-08-22 14:43:43.533105", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "company", - "bank_account", - "column_break_3", - "pay_to_account", - "print_format", - "section_break_4", - "include_purchase_invoices", - "include_journal_entries", - "include_expense_claims", - "pre_check_overdue_items", - "allow_cancellation", - "cascade_cancellation", - "validate_unique_check_number", - "column_break_9", - "number_of_invoices_per_voucher", - "split_by_address", - "automatically_release_on_hold_invoices", - "file_preview_threshold", - "default_modes_of_payment_section_section", - "purchase_invoice", - "journal_entry", - "column_break_21", - "expense_claim", - "ach_settings_section", - "ach_file_extension", - "ach_service_class_code", - "ach_standard_class_code", - "ach_description", - "column_break_27", - "immediate_origin", - "company_discretionary_data", - "custom_post_processing_hook" - ], - "fields": [ - { - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "options": "Company" - }, - { - "fieldname": "bank_account", - "fieldtype": "Link", - "label": "Bank Account", - "options": "Bank Account" - }, - { - "fieldname": "section_break_4", - "fieldtype": "Section Break" - }, - { - "default": "1", - "fieldname": "include_purchase_invoices", - "fieldtype": "Check", - "label": "Include Purchase Invoices" - }, - { - "default": "1", - "fieldname": "include_journal_entries", - "fieldtype": "Check", - "label": "Include Journal Entries " - }, - { - "default": "1", - "fieldname": "include_expense_claims", - "fieldtype": "Check", - "label": "Include Expense Claims" - }, - { - "default": "0", - "description": "Payment Entries will be unlinked when Check Run is cancelled", - "fieldname": "allow_cancellation", - "fieldtype": "Check", - "label": "Allow Cancellation" - }, - { - "fieldname": "column_break_9", - "fieldtype": "Column Break" - }, - { - "default": "ach", - "description": "Common file extensions are 'ach', 'txt' and 'dat'. Your bank may require one of these.", - "fieldname": "ach_file_extension", - "fieldtype": "Data", - "label": "ACH File Extension" - }, - { - "default": "0", - "description": "Pre-Check all payables that have a due date greater than the Check Run's posting date", - "fieldname": "pre_check_overdue_items", - "fieldtype": "Check", - "label": "Pre-Check Overdue Items" - }, - { - "default": "0", - "description": "When a Check Run is cancelled, all Payment Entries linked to it will also be cancelled. This is not recommended.", - "fieldname": "cascade_cancellation", - "fieldtype": "Check", - "label": "Cascade Cancellation" - }, - { - "description": "Defaults to 5 if no value is provided", - "fieldname": "number_of_invoices_per_voucher", - "fieldtype": "Int", - "label": "Number of Invoices per Voucher", - "non_negative": 1 - }, - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, - { - "fieldname": "pay_to_account", - "fieldtype": "Link", - "label": "Payable Account", - "options": "Account" - }, - { - "fieldname": "ach_service_class_code", - "fieldtype": "Select", - "label": "ACH Service Class Code", - "options": "200\n220\n225" - }, - { - "description": "PPD is only supported Entry format at this time", - "fieldname": "ach_standard_class_code", - "fieldtype": "Select", - "label": "ACH Standard Class Code", - "options": "PPD" - }, - { - "fieldname": "ach_description", - "fieldtype": "Data", - "label": "ACH Description", - "length": 10 - }, - { - "fieldname": "print_format", - "fieldtype": "Link", - "label": "Print Format", - "options": "Print Format" - }, - { - "default": "0", - "fieldname": "split_by_address", - "fieldtype": "Check", - "label": "Split Invoices by Address" - }, - { - "fieldname": "ach_settings_section", - "fieldtype": "Section Break", - "label": "ACH Settings" - }, - { - "fieldname": "column_break_21", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "automatically_release_on_hold_invoices", - "fieldtype": "Check", - "label": "Automatically Release On Hold Invoices" - }, - { - "fieldname": "immediate_origin", - "fieldtype": "Data", - "label": "Immediate Origin" - }, - { - "fieldname": "company_discretionary_data", - "fieldtype": "Data", - "label": "Company Discretionary Data", - "length": 20 - }, - { - "fieldname": "custom_post_processing_hook", - "fieldtype": "Data", - "label": "Custom Post Processing Hook", - "read_only": 1 - }, - { - "fieldname": "default_modes_of_payment_section_section", - "fieldtype": "Section Break", - "label": "Default Modes of Payment Section" - }, - { - "fieldname": "purchase_invoice", - "fieldtype": "Link", - "label": "Purchase Invoice", - "options": "Mode of Payment" - }, - { - "fieldname": "journal_entry", - "fieldtype": "Link", - "label": "Journal Entry", - "options": "Mode of Payment" - }, - { - "fieldname": "expense_claim", - "fieldtype": "Link", - "label": "Expense Claim", - "options": "Mode of Payment" - }, - { - "fieldname": "column_break_27", - "fieldtype": "Column Break" - }, - { - "default": "1000", - "description": "File preview is enabled up to this number of transactions", - "fieldname": "file_preview_threshold", - "fieldtype": "Int", - "label": "File Preview Threshold" - }, - { - "default": "0", - "fieldname": "validate_unique_check_number", - "fieldtype": "Check", - "label": "Validate Unique Check Number" - } - ], - "links": [], - "modified": "2024-01-11 10:49:46.062226", - "modified_by": "Administrator", - "module": "Check Run", - "name": "Check Run Settings", - "naming_rule": "Expression", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "states": [], - "track_changes": 1 -} + "actions": [], + "autoname": "format:ACC-CRS-{bank_account}-{pay_to_account}", + "creation": "2022-08-22 14:43:43.533105", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "bank_account", + "column_break_3", + "pay_to_account", + "print_format", + "section_break_4", + "include_purchase_invoices", + "include_journal_entries", + "include_expense_claims", + "pre_check_overdue_items", + "allow_cancellation", + "cascade_cancellation", + "validate_unique_check_number", + "column_break_9", + "number_of_invoices_per_voucher", + "split_by_address", + "automatically_release_on_hold_invoices", + "file_preview_threshold", + "default_modes_of_payment_section_section", + "purchase_invoice", + "journal_entry", + "column_break_21", + "expense_claim", + "ach_settings_section", + "ach_file_extension", + "ach_service_class_code", + "ach_standard_class_code", + "ach_description", + "column_break_27", + "immediate_origin", + "company_discretionary_data", + "custom_post_processing_hook", + "sepa_payment_settings_section", + "initiating_party_org_id", + "debtor_org_id", + "column_break_lzfmw", + "debtors_address", + "sepa_authorized_role" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company" + }, + { + "fieldname": "bank_account", + "fieldtype": "Link", + "label": "Bank Account", + "options": "Bank Account" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "default": "1", + "fieldname": "include_purchase_invoices", + "fieldtype": "Check", + "label": "Include Purchase Invoices" + }, + { + "default": "1", + "fieldname": "include_journal_entries", + "fieldtype": "Check", + "label": "Include Journal Entries " + }, + { + "default": "1", + "fieldname": "include_expense_claims", + "fieldtype": "Check", + "label": "Include Expense Claims" + }, + { + "default": "0", + "description": "Payment Entries will be unlinked when Check Run is cancelled", + "fieldname": "allow_cancellation", + "fieldtype": "Check", + "label": "Allow Cancellation" + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "default": "ach", + "description": "Common file extensions are 'ach', 'txt' and 'dat'. Your bank may require one of these.", + "fieldname": "ach_file_extension", + "fieldtype": "Data", + "label": "ACH File Extension" + }, + { + "default": "0", + "description": "Pre-Check all payables that have a due date greater than the Check Run's posting date", + "fieldname": "pre_check_overdue_items", + "fieldtype": "Check", + "label": "Pre-Check Overdue Items" + }, + { + "default": "0", + "description": "When a Check Run is cancelled, all Payment Entries linked to it will also be cancelled. This is not recommended.", + "fieldname": "cascade_cancellation", + "fieldtype": "Check", + "label": "Cascade Cancellation" + }, + { + "description": "Defaults to 5 if no value is provided", + "fieldname": "number_of_invoices_per_voucher", + "fieldtype": "Int", + "label": "Number of Invoices per Voucher", + "non_negative": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "pay_to_account", + "fieldtype": "Link", + "label": "Payable Account", + "options": "Account" + }, + { + "fieldname": "ach_service_class_code", + "fieldtype": "Select", + "label": "ACH Service Class Code", + "options": "200\n220\n225" + }, + { + "description": "PPD is only supported Entry format at this time", + "fieldname": "ach_standard_class_code", + "fieldtype": "Select", + "label": "ACH Standard Class Code", + "options": "PPD" + }, + { + "fieldname": "ach_description", + "fieldtype": "Data", + "label": "ACH Description", + "length": 10 + }, + { + "fieldname": "print_format", + "fieldtype": "Link", + "label": "Print Format", + "options": "Print Format" + }, + { + "default": "0", + "fieldname": "split_by_address", + "fieldtype": "Check", + "label": "Split Invoices by Address" + }, + { + "fieldname": "ach_settings_section", + "fieldtype": "Section Break", + "label": "ACH Settings" + }, + { + "fieldname": "column_break_21", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "automatically_release_on_hold_invoices", + "fieldtype": "Check", + "label": "Automatically Release On Hold Invoices" + }, + { + "fieldname": "immediate_origin", + "fieldtype": "Data", + "label": "Immediate Origin" + }, + { + "fieldname": "company_discretionary_data", + "fieldtype": "Data", + "label": "Company Discretionary Data", + "length": 20 + }, + { + "fieldname": "custom_post_processing_hook", + "fieldtype": "Data", + "label": "Custom Post Processing Hook", + "read_only": 1 + }, + { + "fieldname": "default_modes_of_payment_section_section", + "fieldtype": "Section Break", + "label": "Default Modes of Payment Section" + }, + { + "fieldname": "purchase_invoice", + "fieldtype": "Link", + "label": "Purchase Invoice", + "options": "Mode of Payment" + }, + { + "fieldname": "journal_entry", + "fieldtype": "Link", + "label": "Journal Entry", + "options": "Mode of Payment" + }, + { + "fieldname": "expense_claim", + "fieldtype": "Link", + "label": "Expense Claim", + "options": "Mode of Payment" + }, + { + "fieldname": "column_break_27", + "fieldtype": "Column Break" + }, + { + "default": "1000", + "description": "File preview is enabled up to this number of transactions", + "fieldname": "file_preview_threshold", + "fieldtype": "Int", + "label": "File Preview Threshold" + }, + { + "default": "0", + "fieldname": "validate_unique_check_number", + "fieldtype": "Check", + "label": "Validate Unique Check Number" + }, + { + "fieldname": "sepa_payment_settings_section", + "fieldtype": "Section Break", + "label": "SEPA Payment Settings" + }, + { + "fieldname": "initiating_party_org_id", + "fieldtype": "Data", + "label": "Initiating Party Org Id" + }, + { + "fieldname": "debtor_org_id", + "fieldtype": "Data", + "label": "Debtor Org Id" + }, + { + "fieldname": "column_break_lzfmw", + "fieldtype": "Column Break" + }, + { + "fieldname": "debtors_address", + "fieldtype": "Link", + "label": "Debtor's Address", + "options": "Address" + }, + { + "description": "Users with this role are allowed to download SEPA XML file multiple times. Users without this role are allowed to download it only once.", + "fieldname": "sepa_authorized_role", + "fieldtype": "Link", + "label": "Role Allowed to Download SEPA XML File Multiple Times", + "options": "Role" + } + ], + "links": [], + "modified": "2024-04-26 04:21:24.517639", + "modified_by": "Administrator", + "module": "Check Run", + "name": "Check Run Settings", + "naming_rule": "Expression", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/check_run/tests/fixtures.py b/check_run/tests/fixtures.py index 2129869c..de3c47fe 100644 --- a/check_run/tests/fixtures.py +++ b/check_run/tests/fixtures.py @@ -113,6 +113,23 @@ ), ] +sepa_supplier = [ + ( + "NRW Global Business", + "Export Service", + "SEPA", + 20000.00, + "Net 30", + { + "address_line1": "Volklinger Str. 4", + "city": "Dusseldorf", + "state": "Berlin", + "country": "Germany", + "pincode": "40219", + }, + ) +] + tax_authority = [ ( "Local Tax Authority", diff --git a/check_run/tests/setup.py b/check_run/tests/setup.py index 5b4afc7c..c8d1ec06 100644 --- a/check_run/tests/setup.py +++ b/check_run/tests/setup.py @@ -9,7 +9,7 @@ from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_debit_note -from check_run.tests.fixtures import employees, suppliers, tax_authority +from check_run.tests.fixtures import employees, suppliers, tax_authority, sepa_supplier def before_test(): @@ -62,6 +62,7 @@ def create_test_data(): } ) create_bank_and_bank_account(settings) + create_eur_bank_and_bank_account(settings) create_payment_terms_templates(settings) create_suppliers(settings) create_items(settings) @@ -152,6 +153,49 @@ def create_bank_and_bank_account(settings): doc.submit() +def create_eur_bank_and_bank_account(settings): + if not frappe.db.exists("Mode of Payment", "SEPA"): + mop = frappe.new_doc("Mode of Payment") + mop.mode_of_payment = "SEPA" + mop.enabled = 1 + mop.type = "Electronic" + mop.append( + "accounts", {"company": settings.company, "default_account": settings.company_account} + ) + mop.save() + if not frappe.db.exists("Bank", "EUR Bank"): + bank = frappe.new_doc("Bank") + bank.bank_name = "EUR Bank" + bank.save() + if not frappe.db.exists("Account", "1202 - Primary EUR Account - CFC"): + acc = frappe.new_doc("Account") + acc.account_currency = "EUR" + acc.account_name = "1202 - Primary EUR Account" + acc.account_type = "Bank" + acc.company = "Chelsea Fruit Co" + acc.parent_account = "1200 - Bank Accounts - CFC" + acc.save() + if not frappe.db.exists("Bank Account", "Primary Checking - EUR Bank"): + ba = frappe.new_doc("Bank Account") + ba.account_name = "Primary Checking" + ba.bank = "EUR Bank" + ba.is_company_account = 1 + ba.account = "1202 - Primary EUR Account - CFC" + ba.branch_code = "CXF6754" + ba.iban = "GB82 WEST 1234 5698 7654 32" + ba.save() + + # New chart of account + account = frappe.new_doc("Account") + account.account_name = "EUR Account Payable" + account.account_number = "2130" + account.account_type = "Payable" + account.parent_account = "2100 - Accounts Payable - CFC" + account.account_currency = "EUR" + account.root_type = "Liability" + account.save() + + def setup_accounts(): frappe.rename_doc( "Account", "1000 - Application of Funds (Assets) - CFC", "1000 - Assets - CFC", force=True @@ -290,9 +334,43 @@ def create_suppliers(settings): addr.append("links", {"link_doctype": "Supplier", "link_name": "HIJ Telecom, Inc"}) addr.save() + for supplier in sepa_supplier: + su = frappe.new_doc("Supplier") + su.supplier_name = supplier[0] + su.supplier_group = "Services" + su.country = "Germany" + su.supplier_default_mode_of_payment = supplier[2] + if su.supplier_default_mode_of_payment == "SEPA": + su.bank = "EUR Bank" + su.iban = "DE89370400440532013000" + su.currency = "EUR" + su.default_price_list = "Standard Buying" + su.payment_terms = supplier[4] + su.save() + + ba = frappe.new_doc("Bank Account") + ba.account_name = "NRW Global Business" + ba.bank = "EUR Bank" + ba.party_type = "Supplier" + ba.party = "NRW Global Business" + ba.iban = "DE89370400440532013000" + ba.branch_code = "BGYH7876" + ba.save() + + addr = frappe.new_doc("Address") + addr.address_title = f"{supplier[0]} - {supplier[5]['city']}" + addr.address_type = "Billing" + addr.address_line1 = supplier[5]["address_line1"] + addr.city = supplier[5]["city"] + addr.state = supplier[5]["state"] + addr.country = supplier[5]["country"] + addr.pincode = supplier[5]["pincode"] + addr.append("links", {"link_doctype": "Supplier", "link_name": supplier[0]}) + addr.save() + def create_items(settings): - for supplier in suppliers + tax_authority: + for supplier in suppliers + tax_authority + sepa_supplier: item = frappe.new_doc("Item") item.item_code = item.item_name = supplier[1] item.item_group = "Services" @@ -460,6 +538,26 @@ def create_invoices(settings): pi.save() pi.submit() + # EUR Purchase Invoice + pi = frappe.new_doc("Purchase Invoice") + pi.company = settings.company + pi.set_posting_time = 1 + pi.posting_date = settings.day + pi.supplier = sepa_supplier[0][0] + pi.currency = "EUR" + pi.credit_to = "2130 - EUR Account Payable - CFC" + pi.append( + "items", + { + "item_code": sepa_supplier[0][1], + "rate": 25000.00, + "qty": 1, + }, + ) + pi.supplier_address = "NRW Global Business - Dusseldorf-Billing" + pi.save() + pi.submit() + spi = frappe.get_value( "Purchase Invoice", {"supplier": "Cooperative Ag Finance"},