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 a41678e0..007069ae 100644 --- a/check_run/check_run/doctype/check_run/check_run.py +++ b/check_run/check_run/doctype/check_run/check_run.py @@ -43,7 +43,6 @@ def onload(self): self.set_onload("check_run_submitting", False) def validate(self): - self.set_status() gl_account = frappe.get_value("Bank Account", self.bank_account, "account") if not gl_account: frappe.throw(frappe._("This Bank Account is not associated with a General Ledger Account.")) @@ -54,7 +53,8 @@ def validate(self): self.set_default_payable_account() self.set_default_dates() else: - self.validate_transactions() + if self.status == "Draft": + self.filter_transactions() def on_cancel(self): settings = get_check_run_settings(self) @@ -73,7 +73,8 @@ def on_cancel(self): def set_status(self, status=None): if status: - self.status = status + self.db_set("status", status) + return elif self.status == "Confirm Print": pass elif self.docstatus == 0: @@ -99,40 +100,61 @@ def set_default_dates(self): if not self.end_date: self.end_date = getdate() - def validate_transactions(self): + def filter_transactions(self): if not self.get("transactions"): return - selected = [txn for txn in json.loads(self.get("transactions")) if txn["pay"]] - wrong_status = [] + transactions = json.loads(self.get("transactions")) + for t in transactions: + if self.not_outstanding_or_cancelled(t): + transactions.remove(t) + self.transactions = json.dumps(transactions) + selected = [txn for txn in transactions if txn["pay"]] for t in selected: if not t["mode_of_payment"]: frappe.throw(frappe._(f"Mode of Payment Required: {t['party_name']} {t['ref_number']}")) - filters = {"name": t["name"] if t["doctype"] != "Journal Entry" else t["ref_number"]} - if frappe.get_value(t["doctype"], filters, "docstatus") != 1: - wrong_status.append( - {"party_name": t["party_name"], "ref_number": t["ref_number"] or "", "name": t["name"]} - ) - if len(wrong_status) < 1: - return - invalid_records = "" - for invalid_record in wrong_status: - invalid_records += " ".join(invalid_record.values()) + "
" - frappe.throw( - frappe._( - f"The following document(s) have been cancelled, please remove them from Check Run to continue:
{invalid_records}" + + def not_outstanding_or_cancelled(self, transaction): + filters = { + "name": transaction["name"] + if transaction["doctype"] != "Journal Entry" + else transaction["ref_number"] + } + if frappe.get_value(transaction["doctype"], filters, "docstatus") != 1: + return True + if transaction["doctype"] == "Journal Entry": + outstanding_based_on_gle = frappe.db.sql( + """ + SELECT SUM(`tabGL Entry`.debit) - SUM(`tabGL Entry`.credit) AS outstanding_amount + FROM `tabGL Entry` + WHERE party_type = %(party_type)s + AND account = %(account)s + AND party = %(party)s + AND voucher_no = %(ref)s + """, + { + "account": self.pay_to_account, + "party": transaction["party"], + "party_type": transaction["party_type"], + "ref": filters["name"], + }, + as_dict=True, ) - ) + if outstanding_based_on_gle and not outstanding_based_on_gle[0].outstanding_amount: + return True + else: + if frappe.get_value(transaction["doctype"], filters, "outstanding_amount") == 0.0: + return True @frappe.whitelist() def process_check_run(self): - # check_run_submitting = frappe.defaults.get_global_default("check_run_submitting") - # if check_run_submitting: - # frappe.throw( - # frappe._( - # f"""Check run {check_run_submitting} is in process. No other check runs can be submitted until it completes. Click here for details.""" - # ) - # ) - # return + check_run_submitting = frappe.defaults.get_global_default("check_run_submitting") + if check_run_submitting: + frappe.throw( + frappe._( + f"""Check run {check_run_submitting} is in process. No other check runs can be submitted until it completes. Click here for details.""" + ) + ) + return self.status = "Submitting" transactions = self.transactions transactions = json.loads(transactions) @@ -267,6 +289,8 @@ def create_payment_entries(self, transactions): for reference in group: if not reference: continue + if self.not_outstanding_or_cancelled(reference): + continue if ( settings.automatically_release_on_hold_invoices and reference.doctype == "Purchase Invoice" ): @@ -295,6 +319,8 @@ def create_payment_entries(self, transactions): pe.paid_amount = total_amount pe.base_paid_amount = total_amount pe.base_grand_total = total_amount + if not pe.get("references"): # already paid or cancelled + continue try: pe.save() pe.submit() @@ -545,7 +571,7 @@ def get_entries(doc): else: query = query.union(qb) if query: - query = query.orderby("due_date", "name").get_sql() + query = query.orderby("due_date", "ref_number").get_sql() transactions = frappe.db.sql( query, {"company": company, "pay_to_account": pay_to_account, "end_date": end_date}, as_dict=True diff --git a/check_run/customize.py b/check_run/customize.py index 2d7e47d9..83d14e93 100644 --- a/check_run/customize.py +++ b/check_run/customize.py @@ -1,43 +1,50 @@ import json from pathlib import Path -import frappe -from frappe.custom.doctype.custom_field.custom_field import create_custom_fields -from frappe.custom.doctype.property_setter.property_setter import make_property_setter +import frappe + def load_customizations(): - customizations_directory = Path().cwd().parent / 'apps' / 'check_run' / 'check_run' / 'check_run' / 'custom' - files = list(customizations_directory.glob('**/*.json')) + customizations_directory = ( + Path().cwd().parent / "apps" / "check_run" / "check_run" / "check_run" / "custom" + ) + files = list(customizations_directory.glob("**/*.json")) for file in files: customizations = json.loads(Path(file).read_text()) - for field in customizations.get('custom_fields'): - existing_field = frappe.get_value('Custom Field', field.get('name')) - custom_field = frappe.get_doc("Custom Field", field.get('name')) if existing_field else frappe.new_doc('Custom Field') - field.pop('modified') + for field in customizations.get("custom_fields"): + existing_field = frappe.get_value("Custom Field", field.get("name")) + custom_field = ( + frappe.get_doc("Custom Field", field.get("name")) + if existing_field + else frappe.new_doc("Custom Field") + ) + field.pop("modified") {custom_field.set(key, value) for key, value in field.items()} custom_field.flags.ignore_permissions = True custom_field.flags.ignore_version = True custom_field.save() - for prop in customizations.get('property_setters'): - property_setter = frappe.get_doc({ - "name": prop.get('name'), - "doctype": "Property Setter", - "doctype_or_field": prop.get('doctype_or_field'), - "doc_type": prop.get('doc_type'), - "field_name": prop.get('field_name'), - "property": prop.get('property'), - "value": prop.get('value'), - "property_type": prop.get('property_type') - }) + for prop in customizations.get("property_setters"): + property_setter = frappe.get_doc( + { + "name": prop.get("name"), + "doctype": "Property Setter", + "doctype_or_field": prop.get("doctype_or_field"), + "doc_type": prop.get("doc_type"), + "field_name": prop.get("field_name"), + "property": prop.get("property"), + "value": prop.get("value"), + "property_type": prop.get("property_type"), + } + ) property_setter.flags.ignore_permissions = True property_setter.insert() def after_install(): - if not frappe.db.exists('File', 'Home/Check Run'): - try: + if not frappe.db.exists("File", "Home/Check Run"): + try: cr_folder = frappe.new_doc("File") - cr_folder.update({"file_name":"Check Run", "is_folder": True, "folder":"Home"}) + cr_folder.update({"file_name": "Check Run", "is_folder": True, "folder": "Home"}) cr_folder.save() except Exception as e: - pass \ No newline at end of file + pass