Skip to content

Commit

Permalink
feat: check payment entries for cancelled or paid invoices before sub…
Browse files Browse the repository at this point in the history
…mitting (#108)
  • Loading branch information
agritheory authored May 31, 2023
1 parent b7adf96 commit adbca4d
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 53 deletions.
84 changes: 55 additions & 29 deletions check_run/check_run/doctype/check_run/check_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."))
Expand All @@ -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)
Expand All @@ -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:
Expand All @@ -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()) + "<br>"
frappe.throw(
frappe._(
f"The following document(s) have been cancelled, please remove them from Check Run to continue:<br>{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. <a href="/app/background_jobs">Click here</a> 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. <a href="/app/background_jobs">Click here</a> for details."""
)
)
return
self.status = "Submitting"
transactions = self.transactions
transactions = json.loads(transactions)
Expand Down Expand Up @@ -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"
):
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
55 changes: 31 additions & 24 deletions check_run/customize.py
Original file line number Diff line number Diff line change
@@ -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
pass

0 comments on commit adbca4d

Please sign in to comment.