diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 88213762..0e97e47d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,10 @@ exclude: 'node_modules|.git' -default_stages: [commit] +default_stages: [pre-commit] fail_fast: false repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v5.0.0 hooks: - id: trailing-whitespace files: 'check_run.*' @@ -20,58 +20,56 @@ repos: - id: debug-statements - repo: https://github.com/asottile/pyupgrade - rev: v2.34.0 + rev: v3.19.1 hooks: - id: pyupgrade args: ['--py310-plus'] - - repo: https://github.com/frappe/black + - repo: https://github.com/agritheory/black rev: 951ccf4d5bb0d692b457a5ebc4215d755618eb68 hooks: - id: black - - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.7.1 + - repo: https://github.com/PyCQA/autoflake + rev: v2.3.1 hooks: - - id: prettier - types_or: [javascript] - # Ignore any files that might contain jinja / bundles - exclude: | - (?x)^( - check_run/public/dist/.*| - .*node_modules.*| - .*boilerplate.*| - check_run/www/website_script.js| - check_run/templates/includes/.*| - check_run/public/js/lib/.* - )$ + - id: autoflake + args: [--remove-all-unused-imports, --in-place] - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 + rev: 7.1.1 hooks: - id: flake8 additional_dependencies: ['flake8-bugbear'] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.5.1 + rev: v1.14.1 hooks: - id: mypy exclude: ^tests/ args: [--ignore-missing-imports] - - repo: local + - repo: https://github.com/agritheory/test_utils + rev: v0.17.0 hooks: + - id: update_pre_commit_config + - id: validate_copyright + files: '\.(js|ts|py|md)$' + args: ['--app', 'check_run'] - id: validate_customizations - always_run: true - name: .github/validate_customizations.py - entry: python .github/validate_customizations.py - language: system - types: [python] + # - id: validate_python_dependencies + + - repo: local + hooks: + - id: prettier + name: prettier + entry: npx prettier . --write --ignore-path .prettierignore + language: node ci: autoupdate_schedule: weekly 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 65f7781c..2801338e 100644 --- a/check_run/check_run/doctype/check_run/check_run.js +++ b/check_run/check_run/doctype/check_run/check_run.js @@ -45,10 +45,16 @@ frappe.ui.form.on('Check Run', { e.stopPropagation() }) } + change_amount_label(frm) settings_button(frm) 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() @@ -448,3 +454,42 @@ 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 => { + downloadXML('payments.xml', r) + cur_frm.refresh() + }) + }) +} + +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) +} + +function change_amount_label(frm) { + frappe + .xcall('check_run.check_run.doctype.check_run.check_run.get_default_currency', { company: frm.doc.company }) + .then(r => { + if (r) { + if (r != frm.pay_to_account_currency) { + cur_frm.fields_dict.amount_check_run.set_label('Estimated Amount in Check Run') + } + } + }) +} 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 cb6671e2..30e984a9 100644 --- a/check_run/check_run/doctype/check_run/check_run.json +++ b/check_run/check_run/doctype/check_run/check_run.json @@ -26,7 +26,8 @@ "transactions", "print_count", "status", - "ach_file_generated" + "ach_file_generated", + "sepa_file_generated" ], "fields": [ { @@ -165,6 +166,13 @@ "fieldtype": "Check", "hidden": 1, "label": "Ach File Generated" + }, + { + "allow_on_submit": 1, + "default": "0", + "fieldname": "sepa_file_generated", + "fieldtype": "Check", + "hidden": 1 } ], "is_submittable": 1, 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 b9c25d2d..6df2a4a2 100644 --- a/check_run/check_run/doctype/check_run/check_run.py +++ b/check_run/check_run/doctype/check_run/check_run.py @@ -924,3 +924,19 @@ def get_authorized_role_for_ach(doc): "role_allowed_to_download_ach_file_multiple_times", ) return role + + +@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 + + +@frappe.whitelist() +def get_default_currency(company): + return frappe.db.get_value("Company", company, "default_currency") 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..702d60ef --- /dev/null +++ b/check_run/check_run/doctype/check_run/gen_sepa_xml.py @@ -0,0 +1,273 @@ +import time + +import frappe +from frappe.utils import get_link_to_form, get_url, getdate + + +@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) + if doc.sepa_file_generated == 0: + frappe.db.set_value("Check Run", doc.name, "sepa_file_generated", 1) + 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) + if addr: + 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: + return None + 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 9d39e6d0..7dd21d50 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 @@ -39,7 +39,13 @@ "immediate_origin", "company_discretionary_data", "custom_post_processing_hook", - "role_allowed_to_download_ach_file_multiple_times" + "role_allowed_to_download_ach_file_multiple_times", + "sepa_payment_settings_section", + "initiating_party_org_id", + "debtor_org_id", + "column_break_oqnv", + "debtors_address", + "sepa_authorized_role" ], "fields": [ { @@ -54,6 +60,22 @@ "label": "Bank Account", "options": "Bank Account" }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "pay_to_account", + "fieldtype": "Link", + "label": "Payable Account", + "options": "Account" + }, + { + "fieldname": "print_format", + "fieldtype": "Link", + "label": "Print Format", + "options": "Print Format" + }, { "fieldname": "section_break_4", "fieldtype": "Section Break" @@ -76,6 +98,13 @@ "fieldtype": "Check", "label": "Include Expense Claims" }, + { + "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": "Payment Entries will be unlinked when Check Run is cancelled", @@ -83,47 +112,94 @@ "fieldtype": "Check", "label": "Allow Cancellation" }, + { + "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" + }, + { + "default": "0", + "fieldname": "validate_unique_check_number", + "fieldtype": "Check", + "label": "Validate Unique Check Number" + }, + { + "default": "No", + "fieldname": "allow_stand_alone_debit_notes", + "fieldtype": "Select", + "label": "Allow stand-alone debit notes?", + "options": "Yes\nNo" + }, { "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" + "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 }, { "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", + "fieldname": "split_by_address", "fieldtype": "Check", - "label": "Pre-Check Overdue Items" + "label": "Split Invoices by Address" }, { "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", + "fieldname": "automatically_release_on_hold_invoices", "fieldtype": "Check", - "label": "Cascade Cancellation" + "label": "Automatically Release On Hold Invoices" }, { - "description": "Defaults to 5 if no value is provided", - "fieldname": "number_of_invoices_per_voucher", + "default": "1000", + "description": "File preview is enabled up to this number of transactions", + "fieldname": "file_preview_threshold", "fieldtype": "Int", - "label": "Number of Invoices per Voucher", - "non_negative": 1 + "label": "File Preview Threshold" }, { - "fieldname": "column_break_3", + "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": "column_break_21", "fieldtype": "Column Break" }, { - "fieldname": "pay_to_account", + "fieldname": "expense_claim", "fieldtype": "Link", - "label": "Payable Account", - "options": "Account" + "label": "Expense Claim", + "options": "Mode of Payment" + }, + { + "fieldname": "ach_settings_section", + "fieldtype": "Section Break", + "label": "ACH Settings" + }, + { + "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" }, { "fieldname": "ach_service_class_code", @@ -145,32 +221,9 @@ "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", + "fieldname": "column_break_27", "fieldtype": "Column Break" }, - { - "default": "0", - "fieldname": "automatically_release_on_hold_invoices", - "fieldtype": "Check", - "label": "Automatically Release On Hold Invoices" - }, { "fieldname": "immediate_origin", "fieldtype": "Data", @@ -189,62 +242,47 @@ "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", + "description": "Users with this role are allowed to download ACH file multiple times. Users without this role are allowed to download it only once.", + "fieldname": "role_allowed_to_download_ach_file_multiple_times", "fieldtype": "Link", - "label": "Journal Entry", - "options": "Mode of Payment" + "label": "Role Allowed to Download ACH File Multiple Times", + "options": "Role" }, { - "fieldname": "expense_claim", - "fieldtype": "Link", - "label": "Expense Claim", - "options": "Mode of Payment" + "fieldname": "sepa_payment_settings_section", + "fieldtype": "Section Break", + "label": "SEPA Payment Settings" }, { - "fieldname": "column_break_27", - "fieldtype": "Column Break" + "fieldname": "initiating_party_org_id", + "fieldtype": "Data", + "label": "Initiating Party Org Id" }, { - "default": "1000", - "description": "File preview is enabled up to this number of transactions", - "fieldname": "file_preview_threshold", - "fieldtype": "Int", - "label": "File Preview Threshold" + "fieldname": "debtor_org_id", + "fieldtype": "Data", + "label": "Debtor Org Id" }, { - "default": "0", - "fieldname": "validate_unique_check_number", - "fieldtype": "Check", - "label": "Validate Unique Check Number" + "fieldname": "column_break_oqnv", + "fieldtype": "Column Break" }, { - "default": "No", - "fieldname": "allow_stand_alone_debit_notes", - "fieldtype": "Select", - "label": "Allow stand-alone debit notes?", - "options": "Yes\nNo" + "fieldname": "debtors_address", + "fieldtype": "Link", + "label": "Debtor's Address", + "options": "Address" }, { - "description": "Users with this role are allowed to download ACH file multiple times. Users without this role are allowed to download it only once.", - "fieldname": "role_allowed_to_download_ach_file_multiple_times", + "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 ACH File Multiple Times", + "label": "Role Allowed to Download SEPA XML File Multiple Times", "options": "Role" } ], "links": [], - "modified": "2024-04-29 08:51:46.252826", + "modified": "2025-01-21 04:33:25.766823", "modified_by": "Administrator", "module": "Check Run", "name": "Check Run Settings", @@ -281,4 +319,4 @@ "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 b8d9707f..28fd688f 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 95cb6695..be9379db 100644 --- a/check_run/tests/setup.py +++ b/check_run/tests/setup.py @@ -7,8 +7,7 @@ from erpnext.setup.utils import enable_all_roles_and_domains, set_defaults_for_tests from frappe.desk.page.setup_wizard.setup_wizard import setup_complete from frappe.utils.data import add_days, flt - -from check_run.tests.fixtures import employees, suppliers, tax_authority +from check_run.tests.fixtures import employees, sepa_supplier, suppliers, tax_authority def before_test(): @@ -60,6 +59,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) @@ -150,6 +150,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 @@ -288,9 +331,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" @@ -473,6 +550,38 @@ def create_invoices(settings): rpi.save() rpi.submit() + 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"}, + order_by="posting_date DESC", + ) + rpi = make_debit_note(spi) + rpi.return_against = ( + None # this approach isn't best practice but it allows us to see a negative PI in the check run + ) + rpi.items[0].rate = 500 + rpi.save() + rpi.submit() + def validate_release_date(self): pass