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 2104e61f..6870d739 100644 --- a/check_run/check_run/doctype/check_run/check_run.py +++ b/check_run/check_run/doctype/check_run/check_run.py @@ -2,6 +2,7 @@ # For license information, please see license.txt from __future__ import unicode_literals +import datetime import json from itertools import groupby, zip_longest from io import StringIO @@ -213,6 +214,9 @@ def create_payment_entries(self, transactions): for reference in group: if not reference: continue + if settings.automatically_release_on_hold_invoices and reference.doctype == 'Purchase Invoice': + if frappe.get_value(reference.doctype, reference.name, 'on_hold'): + frappe.db.set_value(reference.doctype, reference.name, 'on_hold', 0) pe.append('references', { "reference_doctype": reference.doctype, "reference_name": reference.name or reference.ref_number, @@ -363,8 +367,8 @@ def get_entries(doc): .where(purchase_invoices.company == company) .where(purchase_invoices.docstatus == 1) .where(purchase_invoices.credit_to == pay_to_account) - .where(purchase_invoices.status != 'On Hold') .where(purchase_invoices.due_date <= end_date) + .where(Coalesce(purchase_invoices.release_date, datetime.date(1900, 1, 1)) <= end_date) ) # Build expense claims query 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 29b6e04a..ab9308d6 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 @@ -21,6 +21,7 @@ "column_break_9", "number_of_invoices_per_voucher", "split_by_address", + "automatically_release_on_hold_invoices", "ach_settings_section", "ach_file_extension", "ach_service_class_code", @@ -138,29 +139,35 @@ "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" - }, - { - "default": "0", - "fieldname": "omit_destination", - "fieldtype": "Check", - "label": "Omit Destination" - }, - { - "fieldname": "column_break_21", - "fieldtype": "Column Break" - } + "default": "0", + "fieldname": "split_by_address", + "fieldtype": "Check", + "label": "Split Invoices by Address" + }, + { + "fieldname": "ach_settings_section", + "fieldtype": "Section Break", + "label": "ACH Settings" + }, + { + "default": "0", + "fieldname": "omit_destination", + "fieldtype": "Check", + "label": "Omit Destination" + }, + { + "fieldname": "column_break_21", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "automatically_release_on_hold_invoices", + "fieldtype": "Check", + "label": "Automatically Release On Hold Invoices" + } ], "links": [], - "modified": "2022-02-13 15:33:15.269467", + "modified": "2023-02-22 14:14:52.852738", "modified_by": "Administrator", "module": "Check Run", "name": "Check Run Settings", diff --git a/check_run/test_setup.py b/check_run/test_setup.py index c4c2f902..21341501 100644 --- a/check_run/test_setup.py +++ b/check_run/test_setup.py @@ -1,4 +1,5 @@ import datetime +import types import frappe from frappe.desk.page.setup_wizard.setup_wizard import setup_complete @@ -29,51 +30,55 @@ def before_test(): set_defaults_for_tests() frappe.db.commit() create_test_data() + for modu in frappe.get_all('Module Onboarding'): + frappe.db.set_value('Module Onboarding', modu, 'is_complete', 1) + frappe.set_value('Website Settings', 'Website Settings', 'home_page', 'login') + frappe.db.commit() suppliers = [ - ("Exceptional Grid", "Electricity", "ACH/EFT", 150.00, { + ("Exceptional Grid", "Electricity", "ACH/EFT", 150.00, "Net 14", { 'address_line1': '2 Cosmo Point', 'city': 'Summerville', 'state': 'MA', 'country': 'United States', 'pincode': '34791' }), - ("Liu & Loewen Accountants LLP", "Accounting Services", "ACH/EFT", 500.00, { + ("Liu & Loewen Accountants LLP", "Accounting Services", "ACH/EFT", 500.00, "Net 30", { 'address_line1': '138 Wanda Square', 'city': 'Chino', 'state': 'ME', 'country': 'United States', 'pincode': '90953' }), - ("Mare Digitalis", "Cloud Services", "Credit Card", 200.00, { + ("Mare Digitalis", "Cloud Services", "Credit Card", 200.00, "Due on Receipt", { 'address_line1': '1000 Toll Plaza Tunnel Alley', 'city': 'Joplin', 'state': 'CT', 'country': 'United States', 'pincode': '51485' }), - ("AgriTheory", "ERPNext Consulting", "Check", 1000.00, { + ("AgriTheory", "ERPNext Consulting", "Check", 1000.00, "Net 14", { 'address_line1': '1293 Bannan Road', 'city': 'New Brighton', 'state': 'NH', 'country': 'United States', 'pincode': '55932' }), - ("HIJ Telecom, Inc", "Internet Services", "Check", 150.00, { + ("HIJ Telecom, Inc", "Internet Services", "Check", 150.00, "Net 30", { 'address_line1': '955 Winding Highway', 'city': 'Glassboro', 'state': 'NY', 'country': 'United States', 'pincode': '28026' }), - ("Sphere Cellular", "Phone Services", "ACH/EFT", 250.00, { + ("Sphere Cellular", "Phone Services", "ACH/EFT", 250.00, "Net 30", { 'address_line1': '1198 Carpenter Road', 'city': 'Rolla', 'state': 'VT', 'country': 'United States', 'pincode': '94286' }), - ("Cooperative Ag Finance", "Financial Services", "Bank Draft", 5000.00, { + ("Cooperative Ag Finance", "Financial Services", "Bank Draft", 5000.00, "Net 30", { 'address_line1': '629 Loyola Landing', 'city': 'Warner Robins', 'state': 'CT', @@ -83,7 +88,7 @@ def before_test(): ] tax_authority = [ - ("Local Tax Authority", "Payroll Taxes", "Check", 0.00, { + ("Local Tax Authority", "Payroll Taxes", "Check", 0.00, "Due on Receipt", { 'address_line1': '18 Spooner Stravenue', 'city': 'Danbury', 'state': 'RI', @@ -92,6 +97,104 @@ def before_test(): }), ] +employees = [('Wilmer Larson', + 'Male', + '1977-03-06', + '2019-04-12', + '20 Gaven Path', + 'Spokane', + 'NV', + '66308'), + ('Shanel Finley', + 'Female', + '1984-04-23', + '2019-07-04', + '1070 Ulloa Green', + 'DeKalb', + 'PA', + '30474'), + ('Camellia Phelps', + 'Female', + '1980-07-06', + '2019-07-28', + '787 Sotelo Arcade', + 'Stockton', + 'CO', + '14860'), + ('Michale Mitchell', + 'Male', + '1984-06-29', + '2020-01-12', + '773 Icehouse Road', + 'West Sacramento', + 'VT', + '24355'), + ('Sharilyn Romero', + 'Female', + '1998-04-22', + '2020-03-20', + '432 Dudley Ranch', + 'Clovis', + 'WA', + '97159'), + ('Doug Buckley', + 'Male', + '1979-06-18', + '2020-09-08', + '771 Battery Caulfield Motorway', + 'Yonkers', + 'VT', + '38125'), + ('Margarito Wallace', + 'Male', + '1991-08-17', + '2020-11-01', + '639 Brook Park', + 'Terre Haute', + 'OR', + '41704'), + ('Mckenzie Ashley', + 'Female', + '1997-09-13', + '2021-02-22', + '1119 Hunter Glen', + 'Ormond Beach', + 'MD', + '30864'), + ('Merrie Oliver', + 'Other', + '1979-11-08', + '2021-03-11', + '267 Vega Freeway', + 'West Palm Beach', + 'FL', + '24411'), + ('Naoma Blake', + 'Female', + '1987-07-10', + '2021-06-21', + '649 Conrad Road', + 'Thousand Oaks', + 'CT', + '97929'), + ('Donnell Fry', + 'Male', + '1994-07-27', + '2021-06-24', + '504 Starr King Canyon', + 'Norwalk', + 'OR', + '46845'), + ('Shalanda Peterson', + 'Female', + '1999-10-04', + '2021-08-01', + '109 Seventh Parkway', + 'Urbana', + 'DE', + '55975')] + + def create_test_data(): settings = frappe._dict({ 'day': datetime.date(int(frappe.defaults.get_defaults().get('fiscal_year')), 1 ,1), @@ -100,6 +203,8 @@ def create_test_data(): {"account_type": "Bank", "company": frappe.defaults.get_defaults().get('company'), "is_group": 0}), }) create_bank_and_bank_account(settings) + set_up_accounts(settings) + create_payment_terms_templates(settings) create_suppliers(settings) create_items(settings) create_invoices(settings) @@ -153,6 +258,42 @@ def create_bank_and_bank_account(settings): doc.save() doc.submit() +def set_up_accounts(settings): + frappe.rename_doc('Account', '1000 - Application of Funds (Assets) - CFC', '1000 - Assets - CFC', force=True) + frappe.rename_doc('Account', '2000 - Source of Funds (Liabilities) - CFC', '2000 - Liabilities - CFC', force=True) + frappe.rename_doc('Account', '1310 - Debtors - CFC', '1310 - Accounts Payable - CFC', force=True) + frappe.rename_doc('Account', '2110 - Creditors - CFC', '2110 - Accounts Receivable - CFC', force=True) + + +def create_payment_terms_templates(settings): + if not frappe.db.exists("Payment Terms Template", "Net 30"): + doc = frappe.new_doc("Payment Terms Template") + doc.template_name = "Net 30" + doc.append("terms", { + "payment_term": "Net 30", + "invoice_portion": 100, + "due_date_based_on": "Day(s) after invoice date", + "credit_days": 30}) + doc.save() + if not frappe.db.exists("Payment Terms Template", "Due on Receipt"): + doc = frappe.new_doc("Payment Terms Template") + doc.template_name = "Due on Receipt" + doc.append("terms", { + "payment_term": "Due on Receipt", + "invoice_portion": 100, + "due_date_based_on": "Day(s) after invoice date", + "credit_days": 0}) + doc.save() + if not frappe.db.exists("Payment Terms Template", "Net 14"): + doc = frappe.new_doc("Payment Terms Template") + doc.template_name = "Net 14" + doc.append("terms", { + "payment_term": "Net 14", + "invoice_portion": 100, + "due_date_based_on": "Day(s) after invoice date", + "credit_days": 14}) + doc.save() + def create_suppliers(settings): addresses = frappe._dict({}) for supplier in suppliers + tax_authority: @@ -166,16 +307,17 @@ def create_suppliers(settings): biz.bank_account = "123456789" biz.currency = "USD" biz.default_price_list = "Standard Buying" + biz.payment_terms = supplier[4] biz.save() addr = frappe.new_doc('Address') - addr.address_title = f"{supplier[0]} - {supplier[4]['city']}" + addr.address_title = f"{supplier[0]} - {supplier[5]['city']}" addr.address_type = 'Billing' - addr.address_line1 = supplier[4]['address_line1'] - addr.city = supplier[4]['city'] - addr.state = supplier[4]['state'] - addr.country = supplier[4]['country'] - addr.pincode = supplier[4]['pincode'] + 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] @@ -300,6 +442,26 @@ def create_invoices(settings): pi.save() pi.submit() + # test on-hold invoice + pi = frappe.new_doc('Purchase Invoice') + pi.company = settings.company + pi.set_posting_time = 1 + pi.posting_date = settings.day + pi.supplier = suppliers[1][0] + pi.append('items', { + 'item_code': suppliers[1][1], + 'rate': 4000.00, + 'qty': 1, + }) + pi.on_hold =1 + pi.release_date = settings.day + datetime.timedelta(days=60) + pi.hold_comment = 'Testing for on hold invoices' + pi.validate_release_date = types.MethodType(validate_release_date, pi) # allow date to be backdated for testing + pi.save() + pi.submit() + +def validate_release_date(self): + pass def config_expense_claim(settings): try: @@ -324,27 +486,25 @@ def config_expense_claim(settings): pta.parent_account = frappe.get_value('Account', {'account_name': 'Indirect Expenses', 'company': settings.company}) pta.save() - def create_employees(settings): - for employee_number in range(1, 13): + for employee_number, employee in enumerate(employees, start=10): emp = frappe.new_doc('Employee') - emp.first_name = "Test" - emp.last_name = f"Employee {employee_number}" + emp.first_name = employee[0].split(' ')[0] + emp.last_name = employee[0].split(' ')[1] emp.employment_type = "Full-time" emp.company = settings.company emp.status = "Active" - emp.gender = "Other" - emp.date_of_birth = datetime.date(1990, 1, 1) - emp.date_of_joining = datetime.date(2020, 1, 1) + emp.gender = employee[1] + emp.date_of_birth = employee[2] + emp.date_of_joining = employee[3] emp.mode_of_payment = 'Check' if employee_number % 3 == 0 else 'ACH/EFT' emp.mode_of_payment = 'Cash' if employee_number == 10 else emp.mode_of_payment emp.expense_approver = 'Administrator' if emp.mode_of_payment == 'ACH/EFT': emp.bank = 'Local Bank' - emp.bank_account = f'{employee_number}123456' + emp.bank_account = f'{employee_number}12345' emp.save() - def create_expense_claim(settings): cost_center = frappe.get_value('Company', settings.company, 'cost_center') payable_acct = frappe.get_value('Company', settings.company, 'default_payable_account') @@ -402,7 +562,6 @@ def create_expense_claim(settings): ec.save() ec.submit() - def create_payroll_journal_entry(settings): emps = frappe.get_list('Employee', {'company': settings.company}) cost_center = frappe.get_value('Company', settings.company, 'cost_center') @@ -465,4 +624,3 @@ def create_payroll_journal_entry(settings): }) je.save() je.submit() - \ No newline at end of file diff --git a/docs/settings.md b/docs/settings.md index 720ed588..99100596 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -31,6 +31,11 @@ If the system doesn't find settings for the account combination you're using in - Default value shows 0, which tells the system this setting is unmodified and it will use 5 invoices per voucher - This setting is an upper limit for the number of invoices per party to group into each voucher to that party - The screen shot below shows the output of a submitted Check Run where the Number of Invoices per Voucher setting was set to 2. Out of the four invoices paid to Exceptional Grid, they are grouped so two are paid under one voucher, then the other two are paid under a different voucher +- **Split Invoices By Address:** + - If checked, this will validate if the same vendor is being paid to different addresses and split the payments entries appropriately +- **Automatically Release On Hold Invoices:** + - By default, on hold invoices will not show if their 'release date' is not within the Check Run period. The checkbox allows invoices that _are_ on hold to be automatically released and paid in the Check Run. + ![Check Run output table showing a row for eight invoices paid (two for AgriTheory, two for Cooperative Ag Finance, and four for Exception Grid). The first two Exceptional Grid invoices have Check Reference Number ACC-PAY-2022-00003 and the next set of two invoices have Check Reference Number ACC-PAY-2022-00004. They were split into different vouchers because the setting limited two invoices per voucher.](./assets/VoucherGroup.png)