Merge branch 'develop' into mr-se-warehouse-validation

This commit is contained in:
Marica 2021-01-07 15:41:21 +05:30 committed by GitHub
commit d91c7e2b38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 390 additions and 169 deletions

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
import frappe.defaults import frappe.defaults
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate, get_link_to_form from frappe.utils import cint, flt, getdate, add_days, cstr, nowdate, get_link_to_form, formatdate
from frappe import _, msgprint, throw from frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date from erpnext.accounts.party import get_party_account, get_due_date
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
@ -549,7 +549,12 @@ class SalesInvoice(SellingController):
self.against_income_account = ','.join(against_acc) self.against_income_account = ','.join(against_acc)
def add_remarks(self): def add_remarks(self):
if not self.remarks: self.remarks = 'No Remarks' if not self.remarks:
if self.po_no and self.po_date:
self.remarks = _("Against Customer Order {0} dated {1}").format(self.po_no,
formatdate(self.po_date))
else:
self.remarks = _("No Remarks")
def validate_auto_set_posting_time(self): def validate_auto_set_posting_time(self):
# Don't auto set the posting date and time if invoice is amended # Don't auto set the posting date and time if invoice is amended

View File

@ -75,62 +75,70 @@ frappe.query_reports["Purchase Analytics"] = {
return Object.assign(options, { return Object.assign(options, {
checkboxColumn: true, checkboxColumn: true,
events: { events: {
onCheckRow: function(data) { onCheckRow: function (data) {
if (!data) return;
const data_doctype = $(
data[2].html
)[0].attributes.getNamedItem("data-doctype").value;
const tree_type = frappe.query_report.filters[0].value;
if (data_doctype != tree_type) return;
row_name = data[2].content; row_name = data[2].content;
length = data.length; length = data.length;
var tree_type = frappe.query_report.filters[0].value; if (tree_type == "Supplier") {
row_values = data
if(tree_type == "Supplier" || tree_type == "Item") { .slice(4, length - 1)
row_values = data.slice(4,length-1).map(function (column) { .map(function (column) {
return column.content; return column.content;
}) });
} } else if (tree_type == "Item") {
else { row_values = data
row_values = data.slice(3,length-1).map(function (column) { .slice(5, length - 1)
.map(function (column) {
return column.content; return column.content;
}) });
} else {
row_values = data
.slice(3, length - 1)
.map(function (column) {
return column.content;
});
} }
entry = { entry = {
'name':row_name, name: row_name,
'values':row_values values: row_values,
} };
let raw_data = frappe.query_report.chart.data; let raw_data = frappe.query_report.chart.data;
let new_datasets = raw_data.datasets; let new_datasets = raw_data.datasets;
var found = false; let element_found = new_datasets.some((element, index, array)=>{
if(element.name == row_name){
for(var i=0; i < new_datasets.length;i++){ array.splice(index, 1)
if(new_datasets[i].name == row_name){ return true
found = true;
new_datasets.splice(i,1);
break;
}
} }
return false
})
if(!found){ if (!element_found) {
new_datasets.push(entry); new_datasets.push(entry);
} }
let new_data = { let new_data = {
labels: raw_data.labels, labels: raw_data.labels,
datasets: new_datasets datasets: new_datasets,
} };
chart_options = {
setTimeout(() => { data: new_data,
frappe.query_report.chart.update(new_data) type: "line",
},500) };
frappe.query_report.render_chart(chart_options);
setTimeout(() => {
frappe.query_report.chart.draw(true);
}, 1000)
frappe.query_report.raw_chart_data = new_data; frappe.query_report.raw_chart_data = new_data;
}, },
} },
}); });
} }
} }

View File

@ -328,6 +328,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.po_detail = source_doc.po_detail target_doc.po_detail = source_doc.po_detail
target_doc.pr_detail = source_doc.pr_detail target_doc.pr_detail = source_doc.pr_detail
target_doc.purchase_invoice_item = source_doc.name target_doc.purchase_invoice_item = source_doc.name
target_doc.price_list_rate = 0
elif doctype == "Delivery Note": elif doctype == "Delivery Note":
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype) returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
@ -353,6 +354,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.dn_detail = source_doc.dn_detail target_doc.dn_detail = source_doc.dn_detail
target_doc.expense_account = source_doc.expense_account target_doc.expense_account = source_doc.expense_account
target_doc.sales_invoice_item = source_doc.name target_doc.sales_invoice_item = source_doc.name
target_doc.price_list_rate = 0
if default_warehouse_for_sales_return: if default_warehouse_for_sales_return:
target_doc.warehouse = default_warehouse_for_sales_return target_doc.warehouse = default_warehouse_for_sales_return

View File

@ -233,7 +233,7 @@ class SellingController(StockController):
'allow_zero_valuation': d.allow_zero_valuation_rate, 'allow_zero_valuation': d.allow_zero_valuation_rate,
'sales_invoice_item': d.get("sales_invoice_item"), 'sales_invoice_item': d.get("sales_invoice_item"),
'dn_detail': d.get("dn_detail"), 'dn_detail': d.get("dn_detail"),
'incoming_rate': p.incoming_rate 'incoming_rate': p.get("incoming_rate")
})) }))
else: else:
il.append(frappe._dict({ il.append(frappe._dict({
@ -252,7 +252,7 @@ class SellingController(StockController):
'allow_zero_valuation': d.allow_zero_valuation_rate, 'allow_zero_valuation': d.allow_zero_valuation_rate,
'sales_invoice_item': d.get("sales_invoice_item"), 'sales_invoice_item': d.get("sales_invoice_item"),
'dn_detail': d.get("dn_detail"), 'dn_detail': d.get("dn_detail"),
'incoming_rate': d.incoming_rate 'incoming_rate': d.get("incoming_rate")
})) }))
return il return il

View File

@ -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",

View File

@ -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):

View File

@ -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:

View File

@ -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",

View File

@ -11,6 +11,7 @@
"employee", "employee",
"employee_name", "employee_name",
"department", "department",
"company",
"column_break1", "column_break1",
"leave_type", "leave_type",
"from_date", "from_date",
@ -219,6 +220,15 @@
"label": "Leave Policy Assignment", "label": "Leave Policy Assignment",
"options": "Leave Policy Assignment", "options": "Leave Policy Assignment",
"read_only": 1 "read_only": 1
},
{
"fetch_from": "employee.company",
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"read_only": 1,
"reqd": 1
} }
], ],
"icon": "fa fa-ok", "icon": "fa fa-ok",
@ -226,7 +236,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-08-20 14:25:10.314323", "modified": "2021-01-04 18:46:13.184104",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Leave Allocation", "name": "Leave Allocation",

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"creation": "2019-05-09 15:47:39.760406", "creation": "2019-05-09 15:47:39.760406",
"doctype": "DocType", "doctype": "DocType",
"engine": "InnoDB", "engine": "InnoDB",
@ -8,6 +9,7 @@
"leave_type", "leave_type",
"transaction_type", "transaction_type",
"transaction_name", "transaction_name",
"company",
"leaves", "leaves",
"column_break_7", "column_break_7",
"from_date", "from_date",
@ -106,12 +108,22 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Holiday List", "label": "Holiday List",
"options": "Holiday List" "options": "Holiday List"
},
{
"fetch_from": "employee.company",
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"read_only": 1,
"reqd": 1
} }
], ],
"in_create": 1, "in_create": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"modified": "2020-09-04 12:16:36.569066", "links": [],
"modified": "2021-01-04 18:47:45.146652",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Leave Ledger Entry", "name": "Leave Ledger Entry",

View File

@ -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",

View File

@ -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

View File

@ -742,3 +742,4 @@ erpnext.patches.v13_0.updates_for_multi_currency_payroll
erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
erpnext.patches.v13_0.add_po_to_global_search erpnext.patches.v13_0.add_po_to_global_search
erpnext.patches.v13_0.update_returned_qty_in_pr_dn erpnext.patches.v13_0.update_returned_qty_in_pr_dn
erpnext.patches.v13_0.set_company_in_leave_ledger_entry

View File

@ -0,0 +1,7 @@
import frappe
def execute():
frappe.reload_doc('HR', 'doctype', 'Leave Allocation')
frappe.reload_doc('HR', 'doctype', 'Leave Ledger Entry')
frappe.db.sql("""update `tabLeave Ledger Entry` as lle set company = (select company from `tabEmployee` where employee = lle.employee)""")
frappe.db.sql("""update `tabLeave Allocation` as la set company = (select company from `tabEmployee` where employee = la.employee)""")

View File

@ -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)

View File

@ -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(() => {

View File

@ -21,6 +21,9 @@ class PayrollEntry(Document):
if cint(entries) == len(self.employees): if cint(entries) == len(self.employees):
self.set_onload("submitted_ss", True) self.set_onload("submitted_ss", True)
def validate(self):
self.number_of_employees = len(self.employees)
def on_submit(self): def on_submit(self):
self.create_salary_slips() self.create_salary_slips()
@ -113,7 +116,7 @@ class PayrollEntry(Document):
for d in employees: for d in employees:
self.append('employees', d) self.append('employees', d)
self.number_of_employees = len(employees) self.number_of_employees = len(self.employees)
if self.validate_attendance: if self.validate_attendance:
return self.validate_employee_attendance() return self.validate_employee_attendance()
@ -145,8 +148,8 @@ class PayrollEntry(Document):
""" """
self.check_permission('write') self.check_permission('write')
self.created = 1 self.created = 1
emp_list = [d.employee for d in self.get_emp_list()] employees = [emp.employee for emp in self.employees]
if emp_list: if employees:
args = frappe._dict({ args = frappe._dict({
"salary_slip_based_on_timesheet": self.salary_slip_based_on_timesheet, "salary_slip_based_on_timesheet": self.salary_slip_based_on_timesheet,
"payroll_frequency": self.payroll_frequency, "payroll_frequency": self.payroll_frequency,
@ -160,10 +163,10 @@ class PayrollEntry(Document):
"exchange_rate": self.exchange_rate, "exchange_rate": self.exchange_rate,
"currency": self.currency "currency": self.currency
}) })
if len(emp_list) > 30: if len(employees) > 30:
frappe.enqueue(create_salary_slips_for_employees, timeout=600, employees=emp_list, args=args) frappe.enqueue(create_salary_slips_for_employees, timeout=600, employees=employees, args=args)
else: else:
create_salary_slips_for_employees(emp_list, args, publish_progress=False) create_salary_slips_for_employees(employees, args, publish_progress=False)
# since this method is called via frm.call this doc needs to be updated manually # since this method is called via frm.call this doc needs to be updated manually
self.reload() self.reload()

View File

@ -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");
}, },

View File

@ -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 component_type in ("earnings", "deductions"):
for d in self.get(component_type): for d in self.get(component_type):
d.amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0] 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

View File

@ -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):

View File

@ -543,6 +543,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
company: me.frm.doc.company, company: me.frm.doc.company,
order_type: me.frm.doc.order_type, order_type: me.frm.doc.order_type,
is_pos: cint(me.frm.doc.is_pos), is_pos: cint(me.frm.doc.is_pos),
is_return: cint(me.frm.doc.is_return),
is_subcontracted: me.frm.doc.is_subcontracted, is_subcontracted: me.frm.doc.is_subcontracted,
transaction_date: me.frm.doc.transaction_date || me.frm.doc.posting_date, transaction_date: me.frm.doc.transaction_date || me.frm.doc.posting_date,
ignore_pricing_rule: me.frm.doc.ignore_pricing_rule, ignore_pricing_rule: me.frm.doc.ignore_pricing_rule,

View File

@ -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
)
)

View 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)

View File

@ -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,29 +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'), gstin_details.get('AddrFlno')) or not d.gst_state_number):
address_line2 = '{} {}'.format(gstin_details.get('AddrBnm'), gstin_details.get('AddrSt'))
email_id = address.get('email_id')
phone = address.get('phone')
# get last 10 digit
phone = phone.replace(" ", "")[-10:] if phone else ''
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, location=location, gstin=d.gstin, legal_name=d.address_title,
pincode=pincode, state_code=state_code, address_line1=address_line1, location=d.city, pincode=d.pincode,
address_line2=address_line2, email=email_id, phone=phone state_code=d.gst_state_number,
address_line1=d.address_line1,
address_line2=d.address_line2
)) ))
def get_gstin_details(gstin): def get_gstin_details(gstin):
@ -127,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):
@ -146,9 +157,10 @@ def get_item_list(invoice):
item.update(d.as_dict()) item.update(d.as_dict())
item.sr_no = d.idx item.sr_no = d.idx
item.discount_amount = abs(item.discount_amount * item.qty) item.description = d.item_name.replace('"', '\\"')
item.description = d.item_name
item.qty = abs(item.qty) item.qty = abs(item.qty)
item.discount_amount = abs(item.discount_amount * item.qty)
item.unit_rate = abs(item.base_amount / item.qty) item.unit_rate = abs(item.base_amount / item.qty)
item.gross_amount = abs(item.base_amount) item.gross_amount = abs(item.base_amount)
item.taxable_value = abs(item.base_amount) item.taxable_value = abs(item.base_amount)
@ -156,6 +168,7 @@ def get_item_list(invoice):
item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y' item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y'
item.serial_no = ""
item = update_item_taxes(invoice, item) item = update_item_taxes(invoice, item)
@ -272,7 +285,25 @@ def get_eway_bill_details(invoice):
vehicle_type=vehicle_type[invoice.gst_vehicle_type] vehicle_type=vehicle_type[invoice.gst_vehicle_type]
)) ))
def validate_mandatory_fields(invoice):
if not invoice.company_address:
frappe.throw(_('Company Address is mandatory to fetch company GSTIN details.'), title=_('Missing Fields'))
if not invoice.customer_address:
frappe.throw(_('Customer Address is mandatory to fetch customer GSTIN details.'), title=_('Missing Fields'))
if not frappe.db.get_value('Address', invoice.company_address, 'gstin'):
frappe.throw(
_('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'),
title=_('Missing Fields')
)
if not frappe.db.get_value('Address', invoice.customer_address, 'gstin'):
frappe.throw(
_('GSTIN is mandatory to fetch customer GSTIN details. Please enter GSTIN in selected customer address.'),
title=_('Missing Fields')
)
def make_einvoice(invoice): def make_einvoice(invoice):
validate_mandatory_fields(invoice)
schema = read_json('einv_template') schema = read_json('einv_template')
transaction_details = get_transaction_details(invoice) transaction_details = get_transaction_details(invoice)

View File

@ -74,67 +74,71 @@ frappe.query_reports["Sales Analytics"] = {
return Object.assign(options, { return Object.assign(options, {
checkboxColumn: true, checkboxColumn: true,
events: { events: {
onCheckRow: function(data) { onCheckRow: function (data) {
if (!data) return;
const data_doctype = $(
data[2].html
)[0].attributes.getNamedItem("data-doctype").value;
const tree_type = frappe.query_report.filters[0].value;
if (data_doctype != tree_type) return;
row_name = data[2].content; row_name = data[2].content;
length = data.length; length = data.length;
var tree_type = frappe.query_report.filters[0].value; if (tree_type == "Customer") {
row_values = data
if(tree_type == "Customer") { .slice(4, length - 1)
row_values = data.slice(4,length-1).map(function (column) { .map(function (column) {
return column.content; return column.content;
}) });
} else if (tree_type == "Item") { } else if (tree_type == "Item") {
row_values = data.slice(5,length-1).map(function (column) { row_values = data
.slice(5, length - 1)
.map(function (column) {
return column.content; return column.content;
}) });
} } else {
else { row_values = data
row_values = data.slice(3,length-1).map(function (column) { .slice(3, length - 1)
.map(function (column) {
return column.content; return column.content;
}) });
} }
entry = { entry = {
'name':row_name, name: row_name,
'values':row_values values: row_values,
} };
let raw_data = frappe.query_report.chart.data; let raw_data = frappe.query_report.chart.data;
let new_datasets = raw_data.datasets; let new_datasets = raw_data.datasets;
var found = false; let element_found = new_datasets.some((element, index, array)=>{
if(element.name == row_name){
for(var i=0; i < new_datasets.length;i++){ array.splice(index, 1)
if(new_datasets[i].name == row_name){ return true
found = true;
new_datasets.splice(i,1);
break;
}
} }
return false
})
if(!found){ if (!element_found) {
new_datasets.push(entry); new_datasets.push(entry);
} }
let new_data = { let new_data = {
labels: raw_data.labels, labels: raw_data.labels,
datasets: new_datasets datasets: new_datasets,
} };
chart_options = {
setTimeout(() => { data: new_data,
frappe.query_report.chart.update(new_data) type: "line",
}, 500) };
frappe.query_report.render_chart(chart_options);
setTimeout(() => {
frappe.query_report.chart.draw(true);
}, 1000)
frappe.query_report.raw_chart_data = new_data; frappe.query_report.raw_chart_data = new_data;
}, },
} },
}) });
}, },
} }

View File

@ -74,6 +74,8 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
update_party_blanket_order(args, out) update_party_blanket_order(args, out)
if not doc or cint(doc.get('is_return')) == 0:
# get price list rate only if the invoice is not a credit or debit note
get_price_list_rate(args, item, out) get_price_list_rate(args, item, out)
if args.customer and cint(args.is_pos): if args.customer and cint(args.is_pos):