Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: fetch and reconcile invoices and mpesa draft payments #6

Merged
merged 11 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions .idea/frappe_mpsa_payments.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/profiles_settings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 29 additions & 23 deletions frappe_mpsa_payments/frappe_mpsa_payments/api/m_pesa_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,16 @@ def get_mpesa_mode_of_payment(company):
return modes_of_payment

@frappe.whitelist(allow_guest=True)
def get_mpesa_draft_c2b_payments(search_term):
def get_mpesa_draft_c2b_payments(
company,
full_name=None,
mode_of_payment=None,
from_date=None,
to_date=None,
):
fields = [
"name",
"transid",
"company",
"msisdn",
"full_name",
Expand All @@ -74,30 +81,26 @@ def get_mpesa_draft_c2b_payments(search_term):
"transamount",
]

filters = {"docstatus": 0}
filters = {"company": company, "docstatus": 0}
order_by="posting_date desc, posting_time desc"

if search_term:
payments_by_msisdn = frappe.get_all(
"Mpesa C2B Payment Register",
filters={"msisdn": ["like", f"%{search_term}%"], "docstatus": 0},
fields=fields,
order_by=order_by
)
payments_by_full_name = frappe.get_all(
"Mpesa C2B Payment Register",
filters={"full_name": ["like", f"%{search_term}%"], "docstatus": 0},
fields=fields,
order_by=order_by
)
if mode_of_payment:
filters["mode_of_payment"] = mode_of_payment

# Merge results from both queries
payments = payments_by_full_name + payments_by_msisdn
else:
# If search_term or status is not provided, return all payments with the given status
payments = frappe.get_all(
"Mpesa C2B Payment Register", filters=filters, fields=fields,order_by=order_by
)
if full_name:
filters["full_name"] = ["like", f"%{full_name}%"]

if from_date and to_date:
filters["posting_date"] = ["between", [from_date, to_date]]
elif from_date:
filters["posting_date"] = [">=", from_date]
elif to_date:
filters["posting_date"] = ["<=", to_date]

payments = frappe.get_all(
"Mpesa C2B Payment Register",
filters=filters, fields=fields,order_by=order_by
)

return payments

Expand Down Expand Up @@ -156,6 +159,7 @@ def submit_instant_mpesa_payment():
def process_mpesa_payment(mpesa_payment, customer, submit_payment=False):
try:
doc = frappe.get_doc("Mpesa C2B Payment Register", mpesa_payment)
print(f"Mpesa Payment: {doc}")
doc.customer = customer
# doc.mode_of_payment = mode_of_payment
#TODO: after testing, mode of payment
Expand All @@ -181,6 +185,8 @@ def get_payment_method(pos_profile):

def get_mode_of_payment(mpesa_doc):
business_short_code=mpesa_doc.businessshortcode
mode_of_payment = frappe.get_value("Mpesa C2B Payment Register URL", business_short_code, "mode_of_payment")
mode_of_payment = frappe.get_value("Mpesa C2B Payment Register URL", {"business_shortcode": business_short_code, "register_status": "Success"}, "mode_of_payment")
if mode_of_payment is None:
mode_of_payment = frappe.get_value("Mpesa C2B Payment Register URL", {"till_number": business_short_code, "register_status": "Success"}, "mode_of_payment")
return mode_of_payment

67 changes: 41 additions & 26 deletions frappe_mpsa_payments/frappe_mpsa_payments/api/payment_entry.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json

import frappe, erpnext
from frappe import _
Expand Down Expand Up @@ -285,13 +286,17 @@ def get_outstanding_invoices(
invoice_type=None,
common_filter=None,
posting_date=None,
from_date=None,
to_date=None,
min_outstanding=None,
max_outstanding=None,
accounting_dimensions=None,
vouchers=None,
limit=None,
voucher_no=None,
):
if invoice_type is None:
invoice_type = "Sales Invoice"

account=get_party_account("Customer", customer, company),
ple = qb.DocType("Payment Ledger Entry")
Expand All @@ -314,6 +319,12 @@ def get_outstanding_invoices(
common_filter.append(ple.account.isin(account))
common_filter.append(ple.party_type == "Customer")
common_filter.append(ple.party == customer)
if from_date and to_date:
common_filter.append(ple.posting_date.between(from_date, to_date))
elif from_date:
common_filter.append(ple.posting_date >= from_date)
elif to_date:
common_filter.append(ple.posting_date <= to_date)

ple_query = QueryPaymentLedger()
invoice_list = ple_query.get_voucher_outstandings(
Expand Down Expand Up @@ -482,10 +493,7 @@ def get_available_pos_profiles(company, currency):
)
return pos_profiles_list

def create_and_reconcile_payment_reconciliation(invoice_name, customer, company, payment_entries):
invoice = frappe.get_doc("Sales Invoice", invoice_name)
currency = invoice.get("currency")

def create_and_reconcile_payment_reconciliation(outstanding_invoices, customer, company, payment_entries):
reconcile_doc = frappe.new_doc("Payment Reconciliation")
reconcile_doc.party_type = "Customer"
reconcile_doc.party = customer
Expand All @@ -498,17 +506,20 @@ def create_and_reconcile_payment_reconciliation(invoice_name, customer, company,
"payments": [],
}

args["invoices"].append(
{
"invoice_type": "Sales Invoice",
"invoice_number": invoice.get("name"),
"invoice_date": invoice.get("posting_date"),
"amount": invoice.get("grand_total"),
"outstanding_amount": invoice.get("outstanding_amount"),
"currency": invoice.get("currency"),
"exchange_rate": 0,
}
)
for invoice in outstanding_invoices:
invoice_doc = frappe.get_doc("Sales Invoice", invoice)
args["invoices"].append(
{
"invoice_type": "Sales Invoice",
"invoice_number": invoice_doc.get("name"),
"invoice_date": invoice_doc.get("posting_date"),
"amount": invoice_doc.get("grand_total"),
"outstanding_amount": invoice_doc.get("outstanding_amount"),
"currency": invoice_doc.get("currency"),
"exchange_rate": 0,
}
)


for payment_entry in payment_entries:
payment_entry_doc = frappe.get_doc("Payment Entry", payment_entry)
Expand All @@ -530,20 +541,24 @@ def create_and_reconcile_payment_reconciliation(invoice_name, customer, company,
frappe.db.commit()

@frappe.whitelist()
def process_mpesa_c2b_reconciliation():
mpesa_transaction = frappe.form_dict.get("mpesa_name")
invoice_name = frappe.form_dict.get("invoice_name")
invoice = frappe.get_doc("Sales Invoice", invoice_name)
customer = invoice.get("customer")
company = invoice.get("company")
def process_mpesa_c2b_reconciliation(mpesa_names, invoice_names):
if isinstance(mpesa_names, str):
mpesa_names = json.loads(mpesa_names)
if isinstance(invoice_names, str):
invoice_names = json.loads(invoice_names)

# TODO: after testing, withdraw this static method of payment
mode_of_payment = "Mpesa-Test"
if not invoice_names:
frappe.throw(_("No invoices provided."))

payment_entry = submit_mpesa_payment(mpesa_transaction, customer)
payment_entries = [payment_entry.get("name")]
first_invoice_name = invoice_names[0]
first_invoice = frappe.get_doc("Sales Invoice", first_invoice_name)
customer = first_invoice.get("customer")
company = first_invoice.get("company")

create_and_reconcile_payment_reconciliation(invoice_name, customer, company, payment_entries)
payment_entries = [submit_mpesa_payment(mpesa_name, customer).get("name") for mpesa_name in
mpesa_names]

create_and_reconcile_payment_reconciliation(invoice_names, customer, company, payment_entries)


@frappe.whitelist()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2024-10-17 07:33:08.666932",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"payment_id",
"full_name",
"column_break_fnjs",
"date",
"amount"
],
"fields": [
{
"fieldname": "payment_id",
"fieldtype": "Link",
"in_list_view": 1,
"in_preview": 1,
"in_standard_filter": 1,
"label": "Payment ID",
"options": "Mpesa C2B Payment Register"
},
{
"fieldname": "full_name",
"fieldtype": "Data",
"in_list_view": 1,
"in_preview": 1,
"in_standard_filter": 1,
"label": "Full Name"
},
{
"fieldname": "date",
"fieldtype": "Date",
"in_list_view": 1,
"in_preview": 1,
"in_standard_filter": 1,
"label": "Date"
},
{
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
"in_preview": 1,
"in_standard_filter": 1,
"label": "Amount"
},
{
"fieldname": "column_break_fnjs",
"fieldtype": "Column Break"
}
],
"index_web_pages_for_search": 1,
"is_virtual": 1,
"istable": 1,
"links": [],
"modified": "2024-10-17 07:35:16.568396",
"modified_by": "Administrator",
"module": "Frappe Mpsa Payments",
"name": "Mpesa Draft Payments",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Copyright (c) 2024, Navari Limited and contributors
# For license information, please see license.txt

# import frappe
from frappe.model.document import Document


class MpesaDraftPayments(Document):

def db_insert(self, *args, **kwargs):
pass

def load_from_db(self):
pass

def db_update(self):
pass
def delete(self):
pass

@staticmethod
def get_list(args):
pass

@staticmethod
def get_count(args):
pass

@staticmethod
def get_stats(args):
pass

Loading
Loading