mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-23 15:09:20 +00:00
Merge branch 'develop' into payroll_fixes2.0
This commit is contained in:
@@ -17,6 +17,8 @@
|
|||||||
"enable_free_follow_ups",
|
"enable_free_follow_ups",
|
||||||
"max_visits",
|
"max_visits",
|
||||||
"valid_days",
|
"valid_days",
|
||||||
|
"inpatient_settings_section",
|
||||||
|
"allow_discharge_despite_unbilled_services",
|
||||||
"healthcare_service_items",
|
"healthcare_service_items",
|
||||||
"inpatient_visit_charge_item",
|
"inpatient_visit_charge_item",
|
||||||
"op_consulting_charge_item",
|
"op_consulting_charge_item",
|
||||||
@@ -302,11 +304,22 @@
|
|||||||
"fieldname": "enable_free_follow_ups",
|
"fieldname": "enable_free_follow_ups",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Enable Free Follow-ups"
|
"label": "Enable Free Follow-ups"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "inpatient_settings_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Inpatient Settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "allow_discharge_despite_unbilled_services",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Allow Discharge Despite Unbilled Healthcare Services"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-07-08 15:17:21.543218",
|
"modified": "2021-01-04 10:19:22.329272",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Healthcare",
|
"module": "Healthcare",
|
||||||
"name": "Healthcare Settings",
|
"name": "Healthcare Settings",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, json
|
import frappe, json
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import today, now_datetime, getdate, get_datetime
|
from frappe.utils import today, now_datetime, getdate, get_datetime, get_link_to_form
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.desk.reportview import get_match_cond
|
from frappe.desk.reportview import get_match_cond
|
||||||
|
|
||||||
@@ -113,6 +113,7 @@ def schedule_inpatient(args):
|
|||||||
inpatient_record.status = 'Admission Scheduled'
|
inpatient_record.status = 'Admission Scheduled'
|
||||||
inpatient_record.save(ignore_permissions = True)
|
inpatient_record.save(ignore_permissions = True)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def schedule_discharge(args):
|
def schedule_discharge(args):
|
||||||
discharge_order = json.loads(args)
|
discharge_order = json.loads(args)
|
||||||
@@ -126,16 +127,19 @@ def schedule_discharge(args):
|
|||||||
frappe.db.set_value('Patient', discharge_order['patient'], 'inpatient_status', inpatient_record.status)
|
frappe.db.set_value('Patient', discharge_order['patient'], 'inpatient_status', inpatient_record.status)
|
||||||
frappe.db.set_value('Patient Encounter', inpatient_record.discharge_encounter, 'inpatient_status', inpatient_record.status)
|
frappe.db.set_value('Patient Encounter', inpatient_record.discharge_encounter, 'inpatient_status', inpatient_record.status)
|
||||||
|
|
||||||
|
|
||||||
def set_details_from_ip_order(inpatient_record, ip_order):
|
def set_details_from_ip_order(inpatient_record, ip_order):
|
||||||
for key in ip_order:
|
for key in ip_order:
|
||||||
inpatient_record.set(key, ip_order[key])
|
inpatient_record.set(key, ip_order[key])
|
||||||
|
|
||||||
|
|
||||||
def set_ip_child_records(inpatient_record, inpatient_record_child, encounter_child):
|
def set_ip_child_records(inpatient_record, inpatient_record_child, encounter_child):
|
||||||
for item in encounter_child:
|
for item in encounter_child:
|
||||||
table = inpatient_record.append(inpatient_record_child)
|
table = inpatient_record.append(inpatient_record_child)
|
||||||
for df in table.meta.get('fields'):
|
for df in table.meta.get('fields'):
|
||||||
table.set(df.fieldname, item.get(df.fieldname))
|
table.set(df.fieldname, item.get(df.fieldname))
|
||||||
|
|
||||||
|
|
||||||
def check_out_inpatient(inpatient_record):
|
def check_out_inpatient(inpatient_record):
|
||||||
if inpatient_record.inpatient_occupancies:
|
if inpatient_record.inpatient_occupancies:
|
||||||
for inpatient_occupancy in inpatient_record.inpatient_occupancies:
|
for inpatient_occupancy in inpatient_record.inpatient_occupancies:
|
||||||
@@ -144,54 +148,88 @@ def check_out_inpatient(inpatient_record):
|
|||||||
inpatient_occupancy.check_out = now_datetime()
|
inpatient_occupancy.check_out = now_datetime()
|
||||||
frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant")
|
frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant")
|
||||||
|
|
||||||
|
|
||||||
def discharge_patient(inpatient_record):
|
def discharge_patient(inpatient_record):
|
||||||
validate_invoiced_inpatient(inpatient_record)
|
validate_inpatient_invoicing(inpatient_record)
|
||||||
inpatient_record.discharge_date = today()
|
inpatient_record.discharge_date = today()
|
||||||
inpatient_record.status = "Discharged"
|
inpatient_record.status = "Discharged"
|
||||||
|
|
||||||
inpatient_record.save(ignore_permissions = True)
|
inpatient_record.save(ignore_permissions = True)
|
||||||
|
|
||||||
def validate_invoiced_inpatient(inpatient_record):
|
|
||||||
pending_invoices = []
|
def validate_inpatient_invoicing(inpatient_record):
|
||||||
|
if frappe.db.get_single_value("Healthcare Settings", "allow_discharge_despite_unbilled_services"):
|
||||||
|
return
|
||||||
|
|
||||||
|
pending_invoices = get_pending_invoices(inpatient_record)
|
||||||
|
|
||||||
|
if pending_invoices:
|
||||||
|
message = _("Cannot mark Inpatient Record as Discharged since there are unbilled services. ")
|
||||||
|
|
||||||
|
formatted_doc_rows = ''
|
||||||
|
|
||||||
|
for doctype, docnames in pending_invoices.items():
|
||||||
|
formatted_doc_rows += """
|
||||||
|
<td>{0}</td>
|
||||||
|
<td>{1}</td>
|
||||||
|
</tr>""".format(doctype, docnames)
|
||||||
|
|
||||||
|
message += """
|
||||||
|
<table class='table'>
|
||||||
|
<thead>
|
||||||
|
<th>{0}</th>
|
||||||
|
<th>{1}</th>
|
||||||
|
</thead>
|
||||||
|
{2}
|
||||||
|
</table>
|
||||||
|
""".format(_("Healthcare Service"), _("Documents"), formatted_doc_rows)
|
||||||
|
|
||||||
|
frappe.throw(message, title=_("Unbilled Services"), is_minimizable=True, wide=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_pending_invoices(inpatient_record):
|
||||||
|
pending_invoices = {}
|
||||||
if inpatient_record.inpatient_occupancies:
|
if inpatient_record.inpatient_occupancies:
|
||||||
service_unit_names = False
|
service_unit_names = False
|
||||||
for inpatient_occupancy in inpatient_record.inpatient_occupancies:
|
for inpatient_occupancy in inpatient_record.inpatient_occupancies:
|
||||||
if inpatient_occupancy.invoiced != 1:
|
if not inpatient_occupancy.invoiced:
|
||||||
if service_unit_names:
|
if service_unit_names:
|
||||||
service_unit_names += ", " + inpatient_occupancy.service_unit
|
service_unit_names += ", " + inpatient_occupancy.service_unit
|
||||||
else:
|
else:
|
||||||
service_unit_names = inpatient_occupancy.service_unit
|
service_unit_names = inpatient_occupancy.service_unit
|
||||||
if service_unit_names:
|
if service_unit_names:
|
||||||
pending_invoices.append("Inpatient Occupancy (" + service_unit_names + ")")
|
pending_invoices["Inpatient Occupancy"] = service_unit_names
|
||||||
|
|
||||||
docs = ["Patient Appointment", "Patient Encounter", "Lab Test", "Clinical Procedure"]
|
docs = ["Patient Appointment", "Patient Encounter", "Lab Test", "Clinical Procedure"]
|
||||||
|
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
doc_name_list = get_inpatient_docs_not_invoiced(doc, inpatient_record)
|
doc_name_list = get_unbilled_inpatient_docs(doc, inpatient_record)
|
||||||
if doc_name_list:
|
if doc_name_list:
|
||||||
pending_invoices = get_pending_doc(doc, doc_name_list, pending_invoices)
|
pending_invoices = get_pending_doc(doc, doc_name_list, pending_invoices)
|
||||||
|
|
||||||
if pending_invoices:
|
return pending_invoices
|
||||||
frappe.throw(_("Can not mark Inpatient Record Discharged, there are Unbilled Invoices {0}").format(", "
|
|
||||||
.join(pending_invoices)), title=_('Unbilled Invoices'))
|
|
||||||
|
|
||||||
def get_pending_doc(doc, doc_name_list, pending_invoices):
|
def get_pending_doc(doc, doc_name_list, pending_invoices):
|
||||||
if doc_name_list:
|
if doc_name_list:
|
||||||
doc_ids = False
|
doc_ids = False
|
||||||
for doc_name in doc_name_list:
|
for doc_name in doc_name_list:
|
||||||
|
doc_link = get_link_to_form(doc, doc_name.name)
|
||||||
if doc_ids:
|
if doc_ids:
|
||||||
doc_ids += ", "+doc_name.name
|
doc_ids += ", " + doc_link
|
||||||
else:
|
else:
|
||||||
doc_ids = doc_name.name
|
doc_ids = doc_link
|
||||||
if doc_ids:
|
if doc_ids:
|
||||||
pending_invoices.append(doc + " (" + doc_ids + ")")
|
pending_invoices[doc] = doc_ids
|
||||||
|
|
||||||
return pending_invoices
|
return pending_invoices
|
||||||
|
|
||||||
def get_inpatient_docs_not_invoiced(doc, inpatient_record):
|
|
||||||
|
def get_unbilled_inpatient_docs(doc, inpatient_record):
|
||||||
return frappe.db.get_list(doc, filters = {'patient': inpatient_record.patient,
|
return frappe.db.get_list(doc, filters = {'patient': inpatient_record.patient,
|
||||||
'inpatient_record': inpatient_record.name, 'docstatus': 1, 'invoiced': 0})
|
'inpatient_record': inpatient_record.name, 'docstatus': 1, 'invoiced': 0})
|
||||||
|
|
||||||
|
|
||||||
def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=None):
|
def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=None):
|
||||||
inpatient_record.admitted_datetime = check_in
|
inpatient_record.admitted_datetime = check_in
|
||||||
inpatient_record.status = 'Admitted'
|
inpatient_record.status = 'Admitted'
|
||||||
@@ -203,6 +241,7 @@ def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=N
|
|||||||
frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_status', 'Admitted')
|
frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_status', 'Admitted')
|
||||||
frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_record', inpatient_record.name)
|
frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_record', inpatient_record.name)
|
||||||
|
|
||||||
|
|
||||||
def transfer_patient(inpatient_record, service_unit, check_in):
|
def transfer_patient(inpatient_record, service_unit, check_in):
|
||||||
item_line = inpatient_record.append('inpatient_occupancies', {})
|
item_line = inpatient_record.append('inpatient_occupancies', {})
|
||||||
item_line.service_unit = service_unit
|
item_line.service_unit = service_unit
|
||||||
@@ -212,6 +251,7 @@ def transfer_patient(inpatient_record, service_unit, check_in):
|
|||||||
|
|
||||||
frappe.db.set_value("Healthcare Service Unit", service_unit, "occupancy_status", "Occupied")
|
frappe.db.set_value("Healthcare Service Unit", service_unit, "occupancy_status", "Occupied")
|
||||||
|
|
||||||
|
|
||||||
def patient_leave_service_unit(inpatient_record, check_out, leave_from):
|
def patient_leave_service_unit(inpatient_record, check_out, leave_from):
|
||||||
if inpatient_record.inpatient_occupancies:
|
if inpatient_record.inpatient_occupancies:
|
||||||
for inpatient_occupancy in inpatient_record.inpatient_occupancies:
|
for inpatient_occupancy in inpatient_record.inpatient_occupancies:
|
||||||
@@ -221,6 +261,7 @@ def patient_leave_service_unit(inpatient_record, check_out, leave_from):
|
|||||||
frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant")
|
frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant")
|
||||||
inpatient_record.save(ignore_permissions = True)
|
inpatient_record.save(ignore_permissions = True)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
def get_leave_from(doctype, txt, searchfield, start, page_len, filters):
|
def get_leave_from(doctype, txt, searchfield, start, page_len, filters):
|
||||||
|
|||||||
@@ -40,6 +40,31 @@ class TestInpatientRecord(unittest.TestCase):
|
|||||||
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record"))
|
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record"))
|
||||||
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status"))
|
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status"))
|
||||||
|
|
||||||
|
def test_allow_discharge_despite_unbilled_services(self):
|
||||||
|
frappe.db.sql("""delete from `tabInpatient Record`""")
|
||||||
|
setup_inpatient_settings()
|
||||||
|
patient = create_patient()
|
||||||
|
# Schedule Admission
|
||||||
|
ip_record = create_inpatient(patient)
|
||||||
|
ip_record.expected_length_of_stay = 0
|
||||||
|
ip_record.save(ignore_permissions = True)
|
||||||
|
|
||||||
|
# Admit
|
||||||
|
service_unit = get_healthcare_service_unit()
|
||||||
|
admit_patient(ip_record, service_unit, now_datetime())
|
||||||
|
|
||||||
|
# Discharge
|
||||||
|
schedule_discharge(frappe.as_json({"patient": patient}))
|
||||||
|
self.assertEqual("Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
|
||||||
|
|
||||||
|
ip_record = frappe.get_doc("Inpatient Record", ip_record.name)
|
||||||
|
# Should not validate Pending Invoices
|
||||||
|
ip_record.discharge()
|
||||||
|
|
||||||
|
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record"))
|
||||||
|
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status"))
|
||||||
|
|
||||||
|
|
||||||
def test_validate_overlap_admission(self):
|
def test_validate_overlap_admission(self):
|
||||||
frappe.db.sql("""delete from `tabInpatient Record`""")
|
frappe.db.sql("""delete from `tabInpatient Record`""")
|
||||||
patient = create_patient()
|
patient = create_patient()
|
||||||
@@ -63,6 +88,13 @@ def mark_invoiced_inpatient_occupancy(ip_record):
|
|||||||
inpatient_occupancy.invoiced = 1
|
inpatient_occupancy.invoiced = 1
|
||||||
ip_record.save(ignore_permissions = True)
|
ip_record.save(ignore_permissions = True)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_inpatient_settings():
|
||||||
|
settings = frappe.get_single("Healthcare Settings")
|
||||||
|
settings.allow_discharge_despite_unbilled_services = 1
|
||||||
|
settings.save()
|
||||||
|
|
||||||
|
|
||||||
def create_inpatient(patient):
|
def create_inpatient(patient):
|
||||||
patient_obj = frappe.get_doc('Patient', patient)
|
patient_obj = frappe.get_doc('Patient', patient)
|
||||||
inpatient_record = frappe.new_doc('Inpatient Record')
|
inpatient_record = frappe.new_doc('Inpatient Record')
|
||||||
@@ -78,6 +110,7 @@ def create_inpatient(patient):
|
|||||||
inpatient_record.scheduled_date = today()
|
inpatient_record.scheduled_date = today()
|
||||||
return inpatient_record
|
return inpatient_record
|
||||||
|
|
||||||
|
|
||||||
def get_healthcare_service_unit():
|
def get_healthcare_service_unit():
|
||||||
service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1})
|
service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1})
|
||||||
if not service_unit:
|
if not service_unit:
|
||||||
@@ -105,6 +138,7 @@ def get_healthcare_service_unit():
|
|||||||
return service_unit.name
|
return service_unit.name
|
||||||
return service_unit
|
return service_unit
|
||||||
|
|
||||||
|
|
||||||
def get_service_unit_type():
|
def get_service_unit_type():
|
||||||
service_unit_type = get_random("Healthcare Service Unit Type", filters={"inpatient_occupancy": 1})
|
service_unit_type = get_random("Healthcare Service Unit Type", filters={"inpatient_occupancy": 1})
|
||||||
|
|
||||||
@@ -116,6 +150,7 @@ def get_service_unit_type():
|
|||||||
return service_unit_type.name
|
return service_unit_type.name
|
||||||
return service_unit_type
|
return service_unit_type
|
||||||
|
|
||||||
|
|
||||||
def create_patient():
|
def create_patient():
|
||||||
patient = frappe.db.exists('Patient', '_Test IPD Patient')
|
patient = frappe.db.exists('Patient', '_Test IPD Patient')
|
||||||
if not patient:
|
if not patient:
|
||||||
|
|||||||
@@ -813,7 +813,7 @@
|
|||||||
"idx": 24,
|
"idx": 24,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-10-16 15:02:04.283657",
|
"modified": "2021-01-01 16:54:33.477439",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Employee",
|
"name": "Employee",
|
||||||
@@ -855,7 +855,6 @@
|
|||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
|
||||||
"search_fields": "employee_name",
|
"search_fields": "employee_name",
|
||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
|
|||||||
@@ -362,6 +362,27 @@ class TestLoan(unittest.TestCase):
|
|||||||
unpledge_request.load_from_db()
|
unpledge_request.load_from_db()
|
||||||
self.assertEqual(unpledge_request.docstatus, 1)
|
self.assertEqual(unpledge_request.docstatus, 1)
|
||||||
|
|
||||||
|
def test_santined_loan_security_unpledge(self):
|
||||||
|
pledge = [{
|
||||||
|
"loan_security": "Test Security 1",
|
||||||
|
"qty": 4000.00
|
||||||
|
}]
|
||||||
|
|
||||||
|
loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
|
||||||
|
create_pledge(loan_application)
|
||||||
|
|
||||||
|
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
|
||||||
|
loan.submit()
|
||||||
|
|
||||||
|
self.assertEquals(loan.loan_amount, 1000000)
|
||||||
|
|
||||||
|
unpledge_map = {'Test Security 1': 4000}
|
||||||
|
unpledge_request = unpledge_security(loan=loan.name, security_map = unpledge_map, save=1)
|
||||||
|
unpledge_request.submit()
|
||||||
|
unpledge_request.status = 'Approved'
|
||||||
|
unpledge_request.save()
|
||||||
|
unpledge_request.submit()
|
||||||
|
|
||||||
def test_disbursal_check_with_shortfall(self):
|
def test_disbursal_check_with_shortfall(self):
|
||||||
pledges = [{
|
pledges = [{
|
||||||
"loan_security": "Test Security 2",
|
"loan_security": "Test Security 2",
|
||||||
|
|||||||
@@ -44,10 +44,16 @@ class LoanSecurityUnpledge(Document):
|
|||||||
"valid_upto": (">=", get_datetime())
|
"valid_upto": (">=", get_datetime())
|
||||||
}, as_list=1))
|
}, as_list=1))
|
||||||
|
|
||||||
total_payment, principal_paid, interest_payable, written_off_amount = frappe.get_value("Loan", self.loan, ['total_payment', 'total_principal_paid',
|
loan_details = frappe.get_value("Loan", self.loan, ['total_payment', 'total_principal_paid',
|
||||||
'total_interest_payable', 'written_off_amount'])
|
'total_interest_payable', 'written_off_amount', 'disbursed_amount', 'status'], as_dict=1)
|
||||||
|
|
||||||
|
if loan_details.status == 'Disbursed':
|
||||||
|
pending_principal_amount = flt(loan_details.total_payment) - flt(loan_details.total_interest_payable) \
|
||||||
|
- flt(loan_details.total_principal_paid) - flt(loan_details.written_off_amount)
|
||||||
|
else:
|
||||||
|
pending_principal_amount = flt(loan_details.disbursed_amount) - flt(loan_details.total_interest_payable) \
|
||||||
|
- flt(loan_details.total_principal_paid) - flt(loan_details.written_off_amount)
|
||||||
|
|
||||||
pending_principal_amount = flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount)
|
|
||||||
security_value = 0
|
security_value = 0
|
||||||
unpledge_qty_map = {}
|
unpledge_qty_map = {}
|
||||||
ltv_ratio = 0
|
ltv_ratio = 0
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
# udpate sales cycle
|
# update sales cycle
|
||||||
for d in ['Sales Invoice', 'Sales Order', 'Quotation', 'Delivery Note']:
|
for d in ['Sales Invoice', 'Sales Order', 'Quotation', 'Delivery Note']:
|
||||||
frappe.db.sql("""update `tab%s` set taxes_and_charges=charge""" % d)
|
frappe.db.sql("""update `tab%s` set taxes_and_charges=charge""" % d)
|
||||||
|
|
||||||
# udpate purchase cycle
|
# update purchase cycle
|
||||||
for d in ['Purchase Invoice', 'Purchase Order', 'Supplier Quotation', 'Purchase Receipt']:
|
for d in ['Purchase Invoice', 'Purchase Order', 'Supplier Quotation', 'Purchase Receipt']:
|
||||||
frappe.db.sql("""update `tab%s` set taxes_and_charges=purchase_other_charges""" % d)
|
frappe.db.sql("""update `tab%s` set taxes_and_charges=purchase_other_charges""" % d)
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ frappe.ui.form.on('Payroll Entry', {
|
|||||||
}
|
}
|
||||||
).toggleClass('btn-primary', !(frm.doc.employees || []).length);
|
).toggleClass('btn-primary', !(frm.doc.employees || []).length);
|
||||||
}
|
}
|
||||||
if ((frm.doc.employees || []).length) {
|
if ((frm.doc.employees || []).length && !frappe.model.has_workflow(frm.doctype)) {
|
||||||
frm.page.clear_primary_action();
|
frm.page.clear_primary_action();
|
||||||
frm.page.set_primary_action(__('Create Salary Slips'), () => {
|
frm.page.set_primary_action(__('Create Salary Slips'), () => {
|
||||||
frm.save('Submit').then(() => {
|
frm.save('Submit').then(() => {
|
||||||
|
|||||||
@@ -151,7 +151,6 @@ frappe.ui.form.on("Salary Slip", {
|
|||||||
var salary_detail_fields = ["formula", "abbr", "statistical_component", "variable_based_on_taxable_salary"];
|
var salary_detail_fields = ["formula", "abbr", "statistical_component", "variable_based_on_taxable_salary"];
|
||||||
frm.fields_dict['earnings'].grid.set_column_disp(salary_detail_fields, false);
|
frm.fields_dict['earnings'].grid.set_column_disp(salary_detail_fields, false);
|
||||||
frm.fields_dict['deductions'].grid.set_column_disp(salary_detail_fields, false);
|
frm.fields_dict['deductions'].grid.set_column_disp(salary_detail_fields, false);
|
||||||
calculate_totals(frm);
|
|
||||||
frm.trigger("set_dynamic_labels");
|
frm.trigger("set_dynamic_labels");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -143,8 +143,8 @@ class SalarySlip(TransactionBase):
|
|||||||
self.salary_slip_based_on_timesheet = self._salary_structure_doc.salary_slip_based_on_timesheet or 0
|
self.salary_slip_based_on_timesheet = self._salary_structure_doc.salary_slip_based_on_timesheet or 0
|
||||||
self.set_time_sheet()
|
self.set_time_sheet()
|
||||||
self.pull_sal_struct()
|
self.pull_sal_struct()
|
||||||
payroll_based_on, consider_unmarked_attendance_as = frappe.db.get_value("Payroll Settings", None, ["payroll_based_on","consider_unmarked_attendance_as"])
|
ps = frappe.db.get_value("Payroll Settings", None, ["payroll_based_on","consider_unmarked_attendance_as"], as_dict=1)
|
||||||
return [payroll_based_on, consider_unmarked_attendance_as]
|
return [ps.payroll_based_on, ps.consider_unmarked_attendance_as]
|
||||||
|
|
||||||
def set_time_sheet(self):
|
def set_time_sheet(self):
|
||||||
if self.salary_slip_based_on_timesheet:
|
if self.salary_slip_based_on_timesheet:
|
||||||
@@ -424,16 +424,19 @@ class SalarySlip(TransactionBase):
|
|||||||
def calculate_net_pay(self):
|
def calculate_net_pay(self):
|
||||||
if self.salary_structure:
|
if self.salary_structure:
|
||||||
self.calculate_component_amounts("earnings")
|
self.calculate_component_amounts("earnings")
|
||||||
self.gross_pay = self.get_component_totals("earnings")
|
self.gross_pay = self.get_component_totals("earnings", depends_on_payment_days=1)
|
||||||
self.base_gross_pay = flt(flt(self.gross_pay) * flt(self.exchange_rate), self.precision('base_gross_pay'))
|
self.base_gross_pay = flt(flt(self.gross_pay) * flt(self.exchange_rate), self.precision('base_gross_pay'))
|
||||||
|
|
||||||
if self.salary_structure:
|
if self.salary_structure:
|
||||||
self.calculate_component_amounts("deductions")
|
self.calculate_component_amounts("deductions")
|
||||||
self.total_deduction = self.get_component_totals("deductions")
|
|
||||||
self.base_total_deduction = flt(flt(self.total_deduction) * flt(self.exchange_rate), self.precision('base_total_deduction'))
|
|
||||||
|
|
||||||
self.set_loan_repayment()
|
self.set_loan_repayment()
|
||||||
|
self.set_component_amounts_based_on_payment_days()
|
||||||
|
self.set_net_pay()
|
||||||
|
|
||||||
|
def set_net_pay(self):
|
||||||
|
self.total_deduction = self.get_component_totals("deductions")
|
||||||
|
self.base_total_deduction = flt(flt(self.total_deduction) * flt(self.exchange_rate), self.precision('base_total_deduction'))
|
||||||
self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
|
self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
|
||||||
self.rounded_total = rounded(self.net_pay)
|
self.rounded_total = rounded(self.net_pay)
|
||||||
self.base_net_pay = flt(flt(self.net_pay) * flt(self.exchange_rate), self.precision('base_net_pay'))
|
self.base_net_pay = flt(flt(self.net_pay) * flt(self.exchange_rate), self.precision('base_net_pay'))
|
||||||
@@ -455,8 +458,6 @@ class SalarySlip(TransactionBase):
|
|||||||
else:
|
else:
|
||||||
self.add_tax_components(payroll_period)
|
self.add_tax_components(payroll_period)
|
||||||
|
|
||||||
self.set_component_amounts_based_on_payment_days(component_type)
|
|
||||||
|
|
||||||
def add_structure_components(self, component_type):
|
def add_structure_components(self, component_type):
|
||||||
data = self.get_data_for_eval()
|
data = self.get_data_for_eval()
|
||||||
for struct_row in self._salary_structure_doc.get(component_type):
|
for struct_row in self._salary_structure_doc.get(component_type):
|
||||||
@@ -813,7 +814,7 @@ class SalarySlip(TransactionBase):
|
|||||||
cint(row.depends_on_payment_days) and cint(self.total_working_days) and
|
cint(row.depends_on_payment_days) and cint(self.total_working_days) and
|
||||||
(not self.salary_slip_based_on_timesheet or
|
(not self.salary_slip_based_on_timesheet or
|
||||||
getdate(self.start_date) < joining_date or
|
getdate(self.start_date) < joining_date or
|
||||||
getdate(self.end_date) > relieving_date
|
(relieving_date and getdate(self.end_date) > relieving_date)
|
||||||
)):
|
)):
|
||||||
additional_amount = flt((flt(row.additional_amount) * flt(self.payment_days)
|
additional_amount = flt((flt(row.additional_amount) * flt(self.payment_days)
|
||||||
/ cint(self.total_working_days)), row.precision("additional_amount"))
|
/ cint(self.total_working_days)), row.precision("additional_amount"))
|
||||||
@@ -946,15 +947,21 @@ class SalarySlip(TransactionBase):
|
|||||||
struct_row['variable_based_on_taxable_salary'] = component.variable_based_on_taxable_salary
|
struct_row['variable_based_on_taxable_salary'] = component.variable_based_on_taxable_salary
|
||||||
return struct_row
|
return struct_row
|
||||||
|
|
||||||
def get_component_totals(self, component_type):
|
def get_component_totals(self, component_type, depends_on_payment_days=0):
|
||||||
|
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
|
||||||
|
["date_of_joining", "relieving_date"])
|
||||||
|
|
||||||
total = 0.0
|
total = 0.0
|
||||||
for d in self.get(component_type):
|
for d in self.get(component_type):
|
||||||
if not d.do_not_include_in_total:
|
if not d.do_not_include_in_total:
|
||||||
d.amount = flt(d.amount, d.precision("amount"))
|
if depends_on_payment_days:
|
||||||
total += d.amount
|
amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0]
|
||||||
|
else:
|
||||||
|
amount = flt(d.amount, d.precision("amount"))
|
||||||
|
total += amount
|
||||||
return total
|
return total
|
||||||
|
|
||||||
def set_component_amounts_based_on_payment_days(self, component_type):
|
def set_component_amounts_based_on_payment_days(self):
|
||||||
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
|
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
|
||||||
["date_of_joining", "relieving_date"])
|
["date_of_joining", "relieving_date"])
|
||||||
|
|
||||||
@@ -964,8 +971,9 @@ class SalarySlip(TransactionBase):
|
|||||||
if not joining_date:
|
if not joining_date:
|
||||||
frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name)))
|
frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name)))
|
||||||
|
|
||||||
for d in self.get(component_type):
|
for component_type in ("earnings", "deductions"):
|
||||||
d.amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0]
|
for d in self.get(component_type):
|
||||||
|
d.amount = flt(self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0], d.precision("amount"))
|
||||||
|
|
||||||
def set_loan_repayment(self):
|
def set_loan_repayment(self):
|
||||||
self.total_loan_repayment = 0
|
self.total_loan_repayment = 0
|
||||||
@@ -1089,17 +1097,17 @@ class SalarySlip(TransactionBase):
|
|||||||
self.calculate_net_pay()
|
self.calculate_net_pay()
|
||||||
|
|
||||||
def set_totals(self):
|
def set_totals(self):
|
||||||
self.gross_pay = 0
|
self.gross_pay = 0.0
|
||||||
if self.salary_slip_based_on_timesheet == 1:
|
if self.salary_slip_based_on_timesheet == 1:
|
||||||
self.calculate_total_for_salary_slip_based_on_timesheet()
|
self.calculate_total_for_salary_slip_based_on_timesheet()
|
||||||
else:
|
else:
|
||||||
self.total_deduction = 0
|
self.total_deduction = 0.0
|
||||||
if self.earnings:
|
if self.earnings:
|
||||||
for earning in self.earnings:
|
for earning in self.earnings:
|
||||||
self.gross_pay += flt(earning.amount)
|
self.gross_pay += flt(earning.amount, earning.precision("amount"))
|
||||||
if self.deductions:
|
if self.deductions:
|
||||||
for deduction in self.deductions:
|
for deduction in self.deductions:
|
||||||
self.total_deduction += flt(deduction.amount)
|
self.total_deduction += flt(deduction.amount, deduction.precision("amount"))
|
||||||
self.net_pay = flt(self.gross_pay) - flt(self.total_deduction) - flt(self.total_loan_repayment)
|
self.net_pay = flt(self.gross_pay) - flt(self.total_deduction) - flt(self.total_loan_repayment)
|
||||||
self.set_base_totals()
|
self.set_base_totals()
|
||||||
|
|
||||||
@@ -1145,8 +1153,10 @@ class SalarySlip(TransactionBase):
|
|||||||
fields = ['sum(net_pay) as sum'],
|
fields = ['sum(net_pay) as sum'],
|
||||||
filters = {'employee_name' : self.employee_name,
|
filters = {'employee_name' : self.employee_name,
|
||||||
'start_date' : ['>=', period_start_date],
|
'start_date' : ['>=', period_start_date],
|
||||||
'end_date' : ['<', period_end_date]})
|
'end_date' : ['<', period_end_date],
|
||||||
|
'name': ['!=', self.name],
|
||||||
|
'docstatus': 1
|
||||||
|
})
|
||||||
|
|
||||||
year_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0
|
year_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0
|
||||||
|
|
||||||
@@ -1160,7 +1170,9 @@ class SalarySlip(TransactionBase):
|
|||||||
fields = ['sum(net_pay) as sum'],
|
fields = ['sum(net_pay) as sum'],
|
||||||
filters = {'employee_name' : self.employee_name,
|
filters = {'employee_name' : self.employee_name,
|
||||||
'start_date' : ['>=', first_day_of_the_month],
|
'start_date' : ['>=', first_day_of_the_month],
|
||||||
'end_date' : ['<', self.start_date]
|
'end_date' : ['<', self.start_date],
|
||||||
|
'name': ['!=', self.name],
|
||||||
|
'docstatus': 1
|
||||||
})
|
})
|
||||||
|
|
||||||
month_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0
|
month_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ class TestSalarySlip(unittest.TestCase):
|
|||||||
|
|
||||||
year_to_date = 0
|
year_to_date = 0
|
||||||
for slip in salary_slips:
|
for slip in salary_slips:
|
||||||
year_to_date += slip.net_pay
|
year_to_date += flt(slip.net_pay)
|
||||||
self.assertEqual(slip.year_to_date, year_to_date)
|
self.assertEqual(slip.year_to_date, year_to_date)
|
||||||
|
|
||||||
def test_tax_for_payroll_period(self):
|
def test_tax_for_payroll_period(self):
|
||||||
|
|||||||
@@ -48,9 +48,6 @@ def validate_regional(doc):
|
|||||||
|
|
||||||
def missing(field_label, regulation):
|
def missing(field_label, regulation):
|
||||||
"""Notify the user that a required field is missing."""
|
"""Notify the user that a required field is missing."""
|
||||||
context = 'Specific for Germany. Example: Remember to set Company Tax ID. It is required by § 14 Abs. 4 Nr. 2 UStG.'
|
translated_msg = _('Remember to set {field_label}. It is required by {regulation}.', context='Specific for Germany. Example: Remember to set Company Tax ID. It is required by § 14 Abs. 4 Nr. 2 UStG.') # noqa: E501
|
||||||
msgprint(_('Remember to set {field_label}. It is required by {regulation}.', context=context).format(
|
formatted_msg = translated_msg.format(field_label=frappe.bold(_(field_label)), regulation=regulation)
|
||||||
field_label=frappe.bold(_(field_label)),
|
msgprint(formatted_msg)
|
||||||
regulation=regulation
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|||||||
12
erpnext/regional/germany/test_accounts_controller.py
Normal file
12
erpnext/regional/germany/test_accounts_controller.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import frappe
|
||||||
|
import unittest
|
||||||
|
from erpnext.regional.germany.accounts_controller import validate_regional
|
||||||
|
|
||||||
|
|
||||||
|
class TestAccountsController(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.sales_invoice = frappe.get_last_doc('Sales Invoice')
|
||||||
|
|
||||||
|
def test_validate_regional(self):
|
||||||
|
validate_regional(self.sales_invoice)
|
||||||
@@ -15,7 +15,7 @@ from frappe import _, bold
|
|||||||
from pyqrcode import create as qrcreate
|
from pyqrcode import create as qrcreate
|
||||||
from frappe.integrations.utils import make_post_request, make_get_request
|
from frappe.integrations.utils import make_post_request, make_get_request
|
||||||
from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply
|
from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply
|
||||||
from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date
|
from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form
|
||||||
|
|
||||||
def validate_einvoice_fields(doc):
|
def validate_einvoice_fields(doc):
|
||||||
einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable'))
|
einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable'))
|
||||||
@@ -84,26 +84,32 @@ def get_doc_details(invoice):
|
|||||||
))
|
))
|
||||||
|
|
||||||
def get_party_details(address_name):
|
def get_party_details(address_name):
|
||||||
address = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0]
|
d = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0]
|
||||||
gstin = address.get('gstin')
|
|
||||||
|
|
||||||
gstin_details = get_gstin_details(gstin)
|
if (not d.gstin
|
||||||
legal_name = gstin_details.get('LegalName') or gstin_details.get('TradeName')
|
or not d.city
|
||||||
location = gstin_details.get('AddrLoc') or address.get('city')
|
or not d.pincode
|
||||||
state_code = gstin_details.get('StateCode')
|
or not d.address_title
|
||||||
pincode = gstin_details.get('AddrPncd')
|
or not d.address_line1
|
||||||
address_line1 = '{} {}'.format(gstin_details.get('AddrBno') or "", gstin_details.get('AddrFlno') or "")
|
or not d.gst_state_number):
|
||||||
address_line2 = '{} {}'.format(gstin_details.get('AddrBnm') or "", gstin_details.get('AddrSt') or "")
|
|
||||||
|
|
||||||
if state_code == 97:
|
frappe.throw(
|
||||||
|
msg=_('Address lines, city, pincode, gstin is mandatory for address {}. Please set them and try again.').format(
|
||||||
|
get_link_to_form('Address', address_name)
|
||||||
|
),
|
||||||
|
title=_('Missing Address Fields')
|
||||||
|
)
|
||||||
|
|
||||||
|
if d.gst_state_number == 97:
|
||||||
# according to einvoice standard
|
# according to einvoice standard
|
||||||
pincode = 999999
|
pincode = 999999
|
||||||
|
|
||||||
return frappe._dict(dict(
|
return frappe._dict(dict(
|
||||||
gstin=gstin, legal_name=legal_name,
|
gstin=d.gstin, legal_name=d.address_title,
|
||||||
location=location, pincode=pincode,
|
location=d.city, pincode=d.pincode,
|
||||||
state_code=state_code, address_line1=address_line1,
|
state_code=d.gst_state_number,
|
||||||
address_line2=address_line2
|
address_line1=d.address_line1,
|
||||||
|
address_line2=d.address_line2
|
||||||
))
|
))
|
||||||
|
|
||||||
def get_gstin_details(gstin):
|
def get_gstin_details(gstin):
|
||||||
@@ -124,14 +130,22 @@ def get_gstin_details(gstin):
|
|||||||
return GSPConnector.get_gstin_details(gstin)
|
return GSPConnector.get_gstin_details(gstin)
|
||||||
|
|
||||||
def get_overseas_address_details(address_name):
|
def get_overseas_address_details(address_name):
|
||||||
address_title, address_line1, address_line2, city, phone, email_id = frappe.db.get_value(
|
address_title, address_line1, address_line2, city = frappe.db.get_value(
|
||||||
'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city', 'phone', 'email_id']
|
'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not address_title or not address_line1 or not city:
|
||||||
|
frappe.throw(
|
||||||
|
msg=_('Address lines and city is mandatory for address {}. Please set them and try again.').format(
|
||||||
|
get_link_to_form('Address', address_name)
|
||||||
|
),
|
||||||
|
title=_('Missing Address Fields')
|
||||||
|
)
|
||||||
|
|
||||||
return frappe._dict(dict(
|
return frappe._dict(dict(
|
||||||
gstin='URP', legal_name=address_title, address_line1=address_line1,
|
gstin='URP', legal_name=address_title, location=city,
|
||||||
address_line2=address_line2, email=email_id, phone=phone,
|
address_line1=address_line1, address_line2=address_line2,
|
||||||
pincode=999999, state_code=96, place_of_supply=96, location=city
|
pincode=999999, state_code=96, place_of_supply=96
|
||||||
))
|
))
|
||||||
|
|
||||||
def get_item_list(invoice):
|
def get_item_list(invoice):
|
||||||
|
|||||||
Reference in New Issue
Block a user