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

fix(Sales Invoice): fetch billable items from inpatient occupancy #498

Merged
merged 2 commits into from
Sep 16, 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
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@
from frappe.desk.reportview import get_match_cond
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
from frappe.utils import get_datetime, get_link_to_form, getdate, now, now_datetime, today
from frappe.utils import (
get_datetime,
get_link_to_form,
getdate,
now,
now_datetime,
time_diff_in_hours,
today,
)

from healthcare.healthcare.doctype.healthcare_settings.healthcare_settings import get_account
from healthcare.healthcare.doctype.nursing_task.nursing_task import NursingTask
Expand Down Expand Up @@ -118,55 +126,68 @@ def transfer(self, service_unit, check_in, leave_from=None, txred=0):
@frappe.whitelist()
def add_service_unit_rent_to_billable_items(self):
try:
query = frappe.db.sql(
f"""
SELECT
sum(TIMESTAMPDIFF(minute, io.check_in, now())) as now_difference,
sum(TIMESTAMPDIFF(minute, io.check_in, io.check_out)) as time_difference,
io.`left`,
io = frappe.qb.DocType("Inpatient Occupancy")
su = frappe.qb.DocType("Healthcare Service Unit")
sut = frappe.qb.DocType("Healthcare Service Unit Type")

query = (
frappe.qb.from_(io)
.left_join(su)
.on(io.service_unit == su.name)
.left_join(sut)
.on(su.service_unit_type == sut.name)
.select(
io.check_in,
io.check_out,
io.left,
io.parent,
io.name,
sut.item,
sut.uom,
sut.rate,
sut.no_of_hours,
sut.minimum_billable_qty
FROM
`tabInpatient Occupancy` as io left join
`tabHealthcare Service Unit` as su on io.service_unit=su.name left join
`tabHealthcare Service Unit Type` as sut on su.service_unit_type=sut.name
WHERE
io.parent={frappe.db.escape(self.name)}
GROUP BY
sut.item
""",
as_dict=True,
sut.minimum_billable_qty,
)
.where(io.parent == self.name)
)
for inpatient in query:

inpatient_details = query.run(as_dict=True)
item_hours = {}

# Calculate total hours for each item
for record in inpatient_details:
check_in = get_datetime(record["check_in"])
check_out = get_datetime(record["check_out"]) if record["check_out"] else get_datetime()

hours_diff = time_diff_in_hours(check_out, check_in)
item = record.get("item")

if item not in item_hours:
record["total_hours"] = 0
item_hours[item] = record

item_hours[item]["total_hours"] += hours_diff

ip_records = list(item_hours.values())

for inpatient in ip_records:
item_name, stock_uom = frappe.db.get_value(
"Item", inpatient.get("item"), ["item_name", "stock_uom"]
)
item_row = frappe.db.get_value(
"Inpatient Record Item",
{"item_code": inpatient.get("item"), "parent": self.name},
["name", "quantity", "invoiced"],
order_by="idx DESC",
as_dict=True,
)
uom = 60
if inpatient.get("uom") == "Hour":
uom = 60
elif inpatient.get("uom") == "Day":
uom = 1440

minimum_billable_qty = inpatient.get("minimum_billable_qty")
quantity = 1
if inpatient.get("left") == 1:
quantity = inpatient.get("time_difference") / uom
else:
quantity = inpatient.get("now_difference") / uom
if minimum_billable_qty and quantity < minimum_billable_qty:
quantity = minimum_billable_qty
quantity = max(inpatient.get("total_hours", 0.5), minimum_billable_qty or 0.5)

# Add or update item row based on existing data
if not item_row:
# to add item child first time
# Add new item row if none exists
se_child = self.append("items")
se_child.item_code = inpatient.get("item")
se_child.item_name = item_name
Expand All @@ -176,7 +197,7 @@ def add_service_unit_rent_to_billable_items(self):
se_child.rate = inpatient.rate / inpatient.get("no_of_hours")
else:
if item_row.get("invoiced"):
# if invoiced add another row
# Add new row if invoiced and additional quantity exists
if item_row.get("quantity") and quantity and quantity > item_row.get("quantity"):
se_child = self.append("items")
se_child.item_code = inpatient.get("item")
Expand All @@ -186,17 +207,19 @@ def add_service_unit_rent_to_billable_items(self):
se_child.quantity = quantity - item_row.get("quantity")
se_child.rate = inpatient.rate / inpatient.get("no_of_hours")
else:
# update existing row in item line if not invoiced
# Update existing non-invoiced item row
if quantity != item_row.get("quantity"):
for item in self.items:
if item.name == item_row.get("name"):
item.quantity = quantity

# Update inpatient occupancy billing time
for test in self.inpatient_occupancies:
if test.name == inpatient.get("name"):
test.scheduled_billing_time = now()

self.save()

except Exception as e:
frappe.log_error(message=e, title="Can't bill Service Unit occupancy")

Expand Down
48 changes: 21 additions & 27 deletions healthcare/healthcare/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

import base64
import json
import math

import frappe
from frappe import _
from frappe.utils import cint, cstr, flt, get_link_to_form, rounded, time_diff_in_hours
from frappe.query_builder import DocType
from frappe.utils import cint, cstr, flt, get_link_to_form, time_diff_in_hours
from frappe.utils.formatters import format_value

from erpnext.setup.utils import insert_record
Expand Down Expand Up @@ -257,21 +257,20 @@ def get_clinical_procedures_to_invoice(patient, company):
def get_inpatient_services_to_invoice(patient, company):
services_to_invoice = []
if not frappe.db.get_single_value("Healthcare Settings", "automatically_generate_billable"):
inpatient_services = frappe.db.sql(
"""
SELECT
io.*
FROM
`tabInpatient Record` ip, `tabInpatient Occupancy` io
WHERE
ip.patient=%s
and ip.company=%s
and io.parent=ip.name
and io.left=1
and io.invoiced=0
""",
(patient.name, company),
as_dict=1,
ip_record = DocType("Inpatient Record")
ip_occupancy = DocType("Inpatient Occupancy")

inpatient_services = (
frappe.qb.from_(ip_occupancy)
.join(ip_record)
.on(ip_occupancy.parent == ip_record.name)
.select(ip_occupancy.star)
.where(
(ip_record.patient == patient.name)
& (ip_record.company == company)
& (ip_occupancy.invoiced == 0)
)
.run(as_dict=True)
)

for inpatient_occupancy in inpatient_services:
Expand All @@ -281,19 +280,13 @@ def get_inpatient_services_to_invoice(patient, company):
service_unit_type = frappe.get_cached_doc("Healthcare Service Unit Type", service_unit_type)
if service_unit_type and service_unit_type.is_billable:
hours_occupied = flt(
time_diff_in_hours(inpatient_occupancy.check_out, inpatient_occupancy.check_in), 2
time_diff_in_hours(inpatient_occupancy.check_out, inpatient_occupancy.check_in)
)
qty = 0.5
if hours_occupied > 0:
actual_qty = hours_occupied / service_unit_type.no_of_hours
floor = math.floor(actual_qty)
decimal_part = actual_qty - floor
if decimal_part > 0.5:
qty = rounded(floor + 1, 1)
elif decimal_part < 0.5 and decimal_part > 0:
qty = rounded(floor + 0.5, 1)
if qty <= 0:
qty = 0.5
qty = hours_occupied / service_unit_type.no_of_hours
if qty < service_unit_type.minimum_billable_qty:
qty = service_unit_type.minimum_billable_qty
services_to_invoice.append(
{
"reference_type": "Inpatient Occupancy",
Expand All @@ -313,6 +306,7 @@ def get_inpatient_services_to_invoice(patient, company):
"qty": item.quantity,
}
)
inpatient_record_doc.add_service_unit_rent_to_billable_items()

else:
ip = frappe.qb.DocType("Inpatient Record")
Expand Down
Loading