-
Notifications
You must be signed in to change notification settings - Fork 429
Feat: Combine Registration Fee with Consultation Fee for New Patient Appointment Billing #947
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
base: develop
Are you sure you want to change the base?
Changes from all commits
8b22480
2d183be
2366d6a
2437c84
c058200
3392135
0b0ba16
3465745
837bd0f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ | |
| from frappe.model.mapper import get_mapped_doc | ||
| from frappe.utils import ( | ||
| add_to_date, | ||
| cint, | ||
| flt, | ||
| format_date, | ||
| get_datetime, | ||
|
|
@@ -544,6 +545,16 @@ def invoice_appointment(appointment_name, discount_percentage=0, discount_amount | |
| update_fee_validity(appointment_doc) | ||
|
|
||
|
|
||
| @frappe.whitelist() | ||
| def get_registration_fee_details(appointment_name: str) -> dict: | ||
| appointment_doc = frappe.get_doc("Patient Appointment", appointment_name) | ||
| registration_context = get_registration_fee_context(appointment_doc) | ||
| return { | ||
| "apply_registration_fee": registration_context.get("apply_registration_fee"), | ||
| "registration_fee": registration_context.get("registration_fee"), | ||
| } | ||
|
|
||
|
|
||
| def create_sales_invoice(appointment_doc, discount_percentage=0, discount_amount=0): | ||
| sales_invoice = frappe.new_doc("Sales Invoice") | ||
| sales_invoice.patient = appointment_doc.patient | ||
|
|
@@ -556,19 +567,25 @@ def create_sales_invoice(appointment_doc, discount_percentage=0, discount_amount | |
| item = sales_invoice.append("items", {}) | ||
| item = get_appointment_item(appointment_doc, item) | ||
|
|
||
| paid_amount = flt(appointment_doc.paid_amount) | ||
| registration_context = get_registration_fee_context(appointment_doc) | ||
| registration_fee = flt(registration_context.get("registration_fee")) | ||
| paid_amount = flt(appointment_doc.paid_amount) + registration_fee | ||
|
|
||
| if registration_context.get("apply_registration_fee"): | ||
| reg_item = sales_invoice.append("items", {}) | ||
| reg_item = get_registration_item(appointment_doc, reg_item, registration_context) | ||
| # Set discount amount and percentage if entered in payment popup | ||
| if flt(discount_percentage): | ||
| sales_invoice.additional_discount_percentage = flt(discount_percentage) | ||
| paid_amount = flt(appointment_doc.paid_amount) - ( | ||
| flt(appointment_doc.paid_amount) * (flt(discount_percentage) / 100) | ||
| ) | ||
| paid_amount = paid_amount - (paid_amount * (flt(discount_percentage) / 100)) | ||
| paid_amount = max(0, paid_amount) | ||
| if flt(discount_amount): | ||
| sales_invoice.discount_amount = flt(discount_amount) | ||
| paid_amount = flt(appointment_doc.paid_amount) - flt(discount_amount) | ||
| paid_amount = paid_amount - flt(discount_amount) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| paid_amount = max(0, paid_amount) | ||
|
|
||
| # Add payments if payment details are supplied else proceed to create invoice as Unpaid | ||
| if appointment_doc.mode_of_payment and appointment_doc.paid_amount: | ||
| if appointment_doc.mode_of_payment and paid_amount: | ||
| sales_invoice.is_pos = 1 | ||
| payment = sales_invoice.append("payments", {}) | ||
| payment.mode_of_payment = appointment_doc.mode_of_payment | ||
|
|
@@ -631,6 +648,66 @@ def get_appointment_item(appointment_doc, item): | |
| return item | ||
|
|
||
|
|
||
| def get_registration_item(appointment_doc, item, registration_context): | ||
| item.item_code = registration_context.get("registration_item") | ||
| item.description = _("Registration Charges") | ||
| item.income_account = get_income_account(appointment_doc.practitioner, appointment_doc.company) | ||
| item.cost_center = frappe.get_cached_value("Company", appointment_doc.company, "cost_center") | ||
| item.rate = flt(registration_context.get("registration_fee")) | ||
| item.amount = flt(registration_context.get("registration_fee")) | ||
| item.qty = 1 | ||
| item.reference_dt = "Patient" | ||
| item.reference_dn = appointment_doc.patient | ||
| return item | ||
|
Comment on lines
+651
to
+661
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use an appointment-scoped reference before auto-canceling the invoice. The registration row is linked as Suggested fix def get_registration_item(appointment_doc, item, registration_context):
item.item_code = registration_context.get("registration_item")
item.description = _("Registration Charges")
item.income_account = get_income_account(appointment_doc.practitioner, appointment_doc.company)
item.cost_center = frappe.get_cached_value("Company", appointment_doc.company, "cost_center")
item.rate = flt(registration_context.get("registration_fee"))
item.amount = flt(registration_context.get("registration_fee"))
item.qty = 1
- item.reference_dt = "Patient"
- item.reference_dn = appointment_doc.patient
+ item.reference_dt = "Patient Appointment"
+ item.reference_dn = appointment_doc.name
return item def cancel_sales_invoice(sales_invoice):
if frappe.db.get_single_value("Healthcare Settings", "show_payment_popup"):
has_appointment_reference_item = any(
item.reference_dt == "Patient Appointment" and item.reference_dn == sales_invoice.appointment
for item in sales_invoice.items
)
- has_other_references = any(
- (item.reference_dt or item.reference_dn)
- and not (
- (item.reference_dt == "Patient Appointment" and item.reference_dn == sales_invoice.appointment)
- or (item.reference_dt == "Patient" and item.reference_dn == sales_invoice.patient)
- )
- for item in sales_invoice.items
- )
+ has_other_items = any(
+ not (
+ item.reference_dt == "Patient Appointment"
+ and item.reference_dn == sales_invoice.appointment
+ )
+ for item in sales_invoice.items
+ )
- if has_appointment_reference_item and not has_other_references:
+ if has_appointment_reference_item and not has_other_items:
if sales_invoice.docstatus.is_submitted():
sales_invoice.cancel()
return True
return FalseAlso applies to: 749-763 🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The registration fee item usually has reference to Patient Doctype and patient name instead of patient appointment in Sales Invoice. This is also in the case of Collect Registration Fee Option |
||
|
|
||
|
|
||
| def get_registration_fee_context(appointment_doc): | ||
| settings = get_registration_fee_settings() | ||
| if not settings.get("include_registration_fee"): | ||
| return {"apply_registration_fee": False, "registration_fee": 0, "registration_item": None} | ||
|
|
||
| reg_fee = flt(settings.get("reg_fee")) | ||
| if not settings.get("reg_item") or reg_fee <= 0: | ||
| return {"apply_registration_fee": False, "registration_fee": 0, "registration_item": None} | ||
|
|
||
| is_new_patient = check_is_new_patient_by_invoice(appointment_doc.patient) | ||
| if not is_new_patient: | ||
| return {"apply_registration_fee": False, "registration_fee": 0, "registration_item": None} | ||
|
|
||
| return { | ||
| "apply_registration_fee": True, | ||
| "registration_fee": reg_fee, | ||
| "registration_item": settings.get("reg_item"), | ||
| } | ||
|
|
||
|
|
||
| def get_registration_fee_settings(): | ||
| settings = {"include_registration_fee": 0, "reg_item": None, "reg_fee": 0} | ||
| meta = frappe.get_meta("Healthcare Settings") | ||
| required_fields = ("include_registration_fee", "reg_item", "reg_fee") | ||
| if not all(meta.has_field(fieldname) for fieldname in required_fields): | ||
| # Custom fields may not exist in all sites. | ||
| return settings | ||
|
|
||
| settings["include_registration_fee"] = cint( | ||
| frappe.db.get_single_value("Healthcare Settings", "include_registration_fee") | ||
| ) | ||
| settings["reg_item"] = frappe.db.get_single_value("Healthcare Settings", "reg_item") | ||
| settings["reg_fee"] = flt(frappe.db.get_single_value("Healthcare Settings", "reg_fee")) | ||
| return settings | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
|
|
||
| def check_is_new_patient_by_invoice(patient): | ||
| has_submitted_invoice = frappe.db.exists( | ||
| "Sales Invoice", | ||
| { | ||
| "docstatus": 1, | ||
| "patient": patient, | ||
| }, | ||
| ) | ||
| return not bool(has_submitted_invoice) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
|
|
||
| def cancel_appointment(appointment_id): | ||
| appointment = frappe.get_doc("Patient Appointment", appointment_id) | ||
|
|
||
|
|
@@ -646,7 +723,7 @@ def cancel_appointment(appointment_id): | |
|
|
||
| if appointment.invoiced: | ||
| sales_invoice = check_sales_invoice_exists(appointment) | ||
| if sales_invoice and cancel_sales_invoice(sales_invoice): | ||
| if sales_invoice and cancel_sales_invoice(sales_invoice, appointment.name): | ||
| msg = _("Appointment {0} and Sales Invoice {1} cancelled").format( | ||
| appointment.name, sales_invoice.name | ||
| ) | ||
|
|
@@ -672,12 +749,39 @@ def cancel_appointment(appointment_id): | |
| frappe.msgprint(msg) | ||
|
|
||
|
|
||
| def cancel_sales_invoice(sales_invoice): | ||
| if frappe.db.get_single_value("Healthcare Settings", "show_payment_popup"): | ||
| if len(sales_invoice.items) == 1: | ||
| if sales_invoice.docstatus.is_submitted(): | ||
| sales_invoice.cancel() | ||
| return True | ||
| def cancel_sales_invoice(sales_invoice, appointment_name=None): | ||
| if not frappe.db.get_single_value("Healthcare Settings", "show_payment_popup"): | ||
| return False | ||
|
|
||
| if not appointment_name: | ||
| appointment_name = next( | ||
| ( | ||
| item.reference_dn | ||
| for item in sales_invoice.items | ||
| if item.reference_dt == "Patient Appointment" and item.reference_dn | ||
| ), | ||
| None, | ||
| ) | ||
|
|
||
| if not appointment_name: | ||
| return False | ||
|
|
||
| has_appointment_reference_item = any( | ||
| item.reference_dt == "Patient Appointment" and item.reference_dn == appointment_name | ||
| for item in sales_invoice.items | ||
| ) | ||
| has_other_references = any( | ||
| not ( | ||
| (item.reference_dt == "Patient Appointment" and item.reference_dn == appointment_name) | ||
| or (item.reference_dt == "Patient" and item.reference_dn == sales_invoice.patient) | ||
| ) | ||
| for item in sales_invoice.items | ||
| ) | ||
|
|
||
| if has_appointment_reference_item and not has_other_references: | ||
| if sales_invoice.docstatus.is_submitted(): | ||
| sales_invoice.cancel() | ||
| return True | ||
| return False | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.