Merge branch 'develop' into naming-series-proj

This commit is contained in:
Marica 2021-01-14 16:34:51 +05:30 committed by GitHub
commit 3c878505cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 569 additions and 53 deletions

View File

@ -498,7 +498,7 @@ cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
frappe.ui.form.on("Purchase Invoice", {
setup: function(frm) {
frm.custom_make_buttons = {
'Purchase Invoice': 'Debit Note',
'Purchase Invoice': 'Return / Debit Note',
'Payment Entry': 'Payment'
}

View File

@ -592,7 +592,7 @@ frappe.ui.form.on('Sales Invoice', {
frm.custom_make_buttons = {
'Delivery Note': 'Delivery',
'Sales Invoice': 'Sales Return',
'Sales Invoice': 'Return / Credit Note',
'Payment Request': 'Payment Request',
'Payment Entry': 'Payment'
},

View File

@ -446,7 +446,7 @@ class Subscription(Document):
if not self.generate_invoice_at_period_start:
return False
if self.is_new_subscription():
if self.is_new_subscription() and getdate() >= getdate(self.current_invoice_start):
return True
# Check invoice dates and make sure it doesn't have outstanding invoices

View File

@ -58,8 +58,8 @@ frappe.ui.form.on("Purchase Order Item", {
erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({
setup: function() {
this.frm.custom_make_buttons = {
'Purchase Receipt': 'Receipt',
'Purchase Invoice': 'Invoice',
'Purchase Receipt': 'Purchase Receipt',
'Purchase Invoice': 'Purchase Invoice',
'Stock Entry': 'Material to Supplier',
'Payment Entry': 'Payment',
}

View File

@ -124,21 +124,24 @@ class ProgramEnrollment(Document):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_program_courses(doctype, txt, searchfield, start, page_len, filters):
if filters.get('program'):
return frappe.db.sql("""select course, course_name from `tabProgram Course`
where parent = %(program)s and course like %(txt)s {match_cond}
order by
if(locate(%(_txt)s, course), locate(%(_txt)s, course), 99999),
idx desc,
`tabProgram Course`.course asc
limit {start}, {page_len}""".format(
match_cond=get_match_cond(doctype),
start=start,
page_len=page_len), {
"txt": "%{0}%".format(txt),
"_txt": txt.replace('%', ''),
"program": filters['program']
})
if not filters.get('program'):
frappe.msgprint(_("Please select a Program first."))
return []
return frappe.db.sql("""select course, course_name from `tabProgram Course`
where parent = %(program)s and course like %(txt)s {match_cond}
order by
if(locate(%(_txt)s, course), locate(%(_txt)s, course), 99999),
idx desc,
`tabProgram Course`.course asc
limit {start}, {page_len}""".format(
match_cond=get_match_cond(doctype),
start=start,
page_len=page_len), {
"txt": "%{0}%".format(txt),
"_txt": txt.replace('%', ''),
"program": filters['program']
})
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs

View File

@ -19,6 +19,7 @@
"valid_days",
"inpatient_settings_section",
"allow_discharge_despite_unbilled_services",
"do_not_bill_inpatient_encounters",
"healthcare_service_items",
"inpatient_visit_charge_item",
"op_consulting_charge_item",
@ -315,11 +316,17 @@
"fieldname": "allow_discharge_despite_unbilled_services",
"fieldtype": "Check",
"label": "Allow Discharge Despite Unbilled Healthcare Services"
},
{
"default": "0",
"fieldname": "do_not_bill_inpatient_encounters",
"fieldtype": "Check",
"label": "Do Not Bill Patient Encounters for Inpatients"
}
],
"issingle": 1,
"links": [],
"modified": "2021-01-04 10:19:22.329272",
"modified": "2021-01-13 09:04:35.877700",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare Settings",

View File

@ -8,6 +8,8 @@ import unittest
from frappe.utils import now_datetime, today
from frappe.utils.make_random import get_random
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
from erpnext.healthcare.doctype.lab_test.test_lab_test import create_patient_encounter
from erpnext.healthcare.utils import get_encounters_to_invoice
class TestInpatientRecord(unittest.TestCase):
def test_admit_and_discharge(self):
@ -42,7 +44,7 @@ class TestInpatientRecord(unittest.TestCase):
def test_allow_discharge_despite_unbilled_services(self):
frappe.db.sql("""delete from `tabInpatient Record`""")
setup_inpatient_settings()
setup_inpatient_settings(key="allow_discharge_despite_unbilled_services", value=1)
patient = create_patient()
# Schedule Admission
ip_record = create_inpatient(patient)
@ -64,6 +66,35 @@ 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_status"))
setup_inpatient_settings(key="allow_discharge_despite_unbilled_services", value=0)
def test_do_not_bill_patient_encounters_for_inpatients(self):
frappe.db.sql("""delete from `tabInpatient Record`""")
setup_inpatient_settings(key="do_not_bill_inpatient_encounters", value=1)
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())
# Patient Encounter
patient_encounter = create_patient_encounter()
encounters = get_encounters_to_invoice(patient, "_Test Company")
encounter_ids = [entry.reference_name for entry in encounters]
self.assertFalse(patient_encounter.name in encounter_ids)
# 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)
mark_invoiced_inpatient_occupancy(ip_record)
discharge_patient(ip_record)
setup_inpatient_settings(key="do_not_bill_inpatient_encounters", value=0)
def test_validate_overlap_admission(self):
frappe.db.sql("""delete from `tabInpatient Record`""")
@ -89,9 +120,9 @@ def mark_invoiced_inpatient_occupancy(ip_record):
ip_record.save(ignore_permissions = True)
def setup_inpatient_settings():
def setup_inpatient_settings(key, value):
settings = frappe.get_single("Healthcare Settings")
settings.allow_discharge_despite_unbilled_services = 1
settings.set(key, value)
settings.save()

View File

@ -77,11 +77,13 @@ def get_appointments_to_invoice(patient, company):
def get_encounters_to_invoice(patient, company):
if not isinstance(patient, str):
patient = patient.name
encounters_to_invoice = []
encounters = frappe.get_list(
'Patient Encounter',
fields=['*'],
filters={'patient': patient.name, 'company': company, 'invoiced': False, 'docstatus': 1}
filters={'patient': patient, 'company': company, 'invoiced': False, 'docstatus': 1}
)
if encounters:
for encounter in encounters:
@ -90,6 +92,10 @@ def get_encounters_to_invoice(patient, company):
income_account = None
service_item = None
if encounter.practitioner:
if encounter.inpatient_record and \
frappe.db.get_single_value('Healthcare Settings', 'do_not_bill_inpatient_encounters'):
continue
service_item, practitioner_charge = get_service_item_and_practitioner_charge(encounter)
income_account = get_income_account(encounter.practitioner, encounter.company)

View File

@ -103,7 +103,7 @@ def get_data(filters):
loan_repayments = frappe.get_all("Loan Repayment",
filters = query_filters,
fields=["posting_date", "applicant", "name", "against_loan", "payment_type", "payable_amount",
fields=["posting_date", "applicant", "name", "against_loan", "payable_amount",
"pending_principal_amount", "interest_payable", "penalty_amount", "amount_paid"]
)

View File

@ -456,10 +456,10 @@ class WorkOrder(Document):
if data and len(data):
dates = [d.posting_datetime for d in data]
self.actual_start_date = min(dates)
self.db_set('actual_start_date', min(dates))
if self.status == "Completed":
self.actual_end_date = max(dates)
self.db_set('actual_end_date', max(dates))
self.set_lead_time()

View File

@ -20,6 +20,7 @@ def get_columns():
_("Item") + ":Link/Item:150",
_("Description") + "::300",
_("BOM Qty") + ":Float:160",
_("BOM UoM") + "::160",
_("Required Qty") + ":Float:120",
_("In Stock Qty") + ":Float:120",
_("Enough Parts to Build") + ":Float:200",
@ -32,7 +33,7 @@ def get_bom_stock(filters):
bom = filters.get("bom")
table = "`tabBOM Item`"
qty_field = "qty"
qty_field = "stock_qty"
qty_to_produce = filters.get("qty_to_produce", 1)
if int(qty_to_produce) <= 0:
@ -40,7 +41,6 @@ def get_bom_stock(filters):
if filters.get("show_exploded_view"):
table = "`tabBOM Explosion Item`"
qty_field = "stock_qty"
if filters.get("warehouse"):
warehouse_details = frappe.db.get_value("Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1)
@ -59,6 +59,7 @@ def get_bom_stock(filters):
bom_item.item_code,
bom_item.description ,
bom_item.{qty_field},
bom_item.stock_uom,
bom_item.{qty_field} * {qty_to_produce} / bom.quantity,
sum(ledger.actual_qty) as actual_qty,
sum(FLOOR(ledger.actual_qty / (bom_item.{qty_field} * {qty_to_produce} / bom.quantity)))

View File

@ -5,7 +5,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import date_diff, getdate, formatdate, cint, month_diff, flt
from frappe.utils import date_diff, getdate, formatdate, cint, month_diff, flt, add_months
from frappe.model.document import Document
from erpnext.hr.utils import get_holidays_for_employee
@ -88,6 +88,8 @@ def get_period_factor(employee, start_date, end_date, payroll_frequency, payroll
period_start = joining_date
if relieving_date and getdate(relieving_date) < getdate(period_end):
period_end = relieving_date
if month_diff(period_end, start_date) > 1:
start_date = add_months(start_date, - (month_diff(period_end, start_date)+1))
total_sub_periods, remaining_sub_periods = 0.0, 0.0

View File

@ -24,9 +24,8 @@
},
{
"fieldname": "reference_invoice",
"fieldtype": "Link",
"label": "Reference Invoice",
"options": "Sales Invoice"
"fieldtype": "Data",
"label": "Reference Invoice"
},
{
"fieldname": "headers",
@ -64,7 +63,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-12-24 21:09:38.882866",
"modified": "2021-01-13 12:06:57.253111",
"modified_by": "Administrator",
"module": "Regional",
"name": "E Invoice Request Log",

View File

@ -7,6 +7,7 @@
"field_order": [
"enable",
"section_break_2",
"sandbox_mode",
"credentials",
"auth_token",
"token_expiry"
@ -41,12 +42,18 @@
"label": "Credentials",
"mandatory_depends_on": "enable",
"options": "E Invoice User"
},
{
"default": "0",
"fieldname": "sandbox_mode",
"fieldtype": "Check",
"label": "Sandbox Mode"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2020-12-22 15:34:57.280044",
"modified": "2021-01-13 12:04:49.449199",
"modified_by": "Administrator",
"module": "Regional",
"name": "E Invoice Settings",

View File

@ -218,8 +218,8 @@ def update_item_taxes(invoice, item):
def get_invoice_value_details(invoice):
invoice_value_details = frappe._dict(dict())
invoice_value_details.base_total = abs(invoice.base_total)
invoice_value_details.invoice_discount_amt = invoice.discount_amount
invoice_value_details.round_off = invoice.rounding_adjustment
invoice_value_details.invoice_discount_amt = invoice.base_discount_amount
invoice_value_details.round_off = invoice.base_rounding_adjustment
invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total)
invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total)
@ -322,7 +322,10 @@ def make_einvoice(invoice):
shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({})
if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name:
shipping_details = get_party_details(invoice.shipping_address_name)
if invoice.gst_category == 'Overseas':
shipping_details = get_overseas_address_details(invoice.shipping_address_name)
else:
shipping_details = get_party_details(invoice.shipping_address_name)
if invoice.is_pos and invoice.base_paid_amount:
payment_details = get_payment_details(invoice)
@ -414,15 +417,19 @@ class RequestFailed(Exception): pass
class GSPConnector():
def __init__(self, doctype=None, docname=None):
self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings')
sandbox_mode = self.e_invoice_settings.sandbox_mode
self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None
self.credentials = self.get_credentials()
self.base_url = 'https://gsp.adaequare.com'
self.authenticate_url = self.base_url + '/gsp/authenticate?grant_type=token'
self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin'
self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice'
self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn'
# authenticate url is same for sandbox & live
self.authenticate_url = 'https://gsp.adaequare.com/gsp/authenticate?grant_type=token'
self.base_url = 'https://gsp.adaequare.com' if not sandbox_mode else 'https://gsp.adaequare.com/test'
self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel'
self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn'
self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice'
self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin'
self.cancel_ewaybill_url = self.base_url + '/enriched/ei/api/ewayapi'
self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill'
@ -758,7 +765,7 @@ class GSPConnector():
_file = frappe.new_doc('File')
_file.update({
'file_name': f'QRCode_{docname}.png',
'file_name': 'QRCode_{}.png'.format(docname.replace('/', '-')),
'attached_to_doctype': doctype,
'attached_to_name': docname,
'content': 'qrcode',

View File

@ -48,6 +48,9 @@ def validate_gstin_for_india(doc, method):
validate_gstin_check_digit(doc.gstin)
set_gst_state_and_state_number(doc)
if not doc.gst_state:
frappe.throw(_("Please Enter GST state"))
if doc.gst_state_number != doc.gstin[:2]:
frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.")
.format(doc.gst_state_number))

View File

@ -7,7 +7,7 @@
frappe.ui.form.on('Quotation', {
setup: function(frm) {
frm.custom_make_buttons = {
'Sales Order': 'Make Sales Order'
'Sales Order': 'Sales Order'
},
frm.set_query("quotation_to", function() {

View File

@ -13,7 +13,7 @@ frappe.ui.form.on("Delivery Note", {
frm.custom_make_buttons = {
'Packing Slip': 'Packing Slip',
'Installation Note': 'Installation Note',
'Sales Invoice': 'Invoice',
'Sales Invoice': 'Sales Invoice',
'Stock Entry': 'Return',
'Shipment': 'Shipment'
},

View File

@ -424,6 +424,7 @@ class TestMaterialRequest(unittest.TestCase):
"basic_rate": 1.0
})
se_doc.get("items")[1].update({
"item_code": "_Test Item Home Desktop 100",
"qty": 3.0,
"transfer_qty": 3.0,
"s_warehouse": "_Test Warehouse 1 - _TC",
@ -534,7 +535,7 @@ class TestMaterialRequest(unittest.TestCase):
mr = make_material_request(item_code='_Test FG Item', material_request_type='Manufacture',
uom="_Test UOM 1", conversion_factor=12)
requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
self.assertEqual(requested_qty, existing_requested_qty + 120)

View File

@ -1333,9 +1333,6 @@ class StockEntry(StockController):
frappe.MappingMismatchError)
elif self.purpose == "Material Transfer" and self.add_to_transit:
continue
elif mreq_item.warehouse != (item.s_warehouse if self.purpose == "Material Issue" else item.t_warehouse):
frappe.throw(_("Warehouse for row {0} does not match Material Request").format(item.idx),
frappe.MappingMismatchError)
def validate_batch(self):
if self.purpose in ["Material Transfer for Manufacture", "Manufacture", "Repack", "Send to Subcontractor"]:

View File

@ -28,7 +28,7 @@
{
"hidden": 0,
"label": "Reports",
"links": "[\n {\n \"dependencies\": [\n \"Issue\"\n ],\n \"doctype\": \"Issue\",\n \"is_query_report\": true,\n \"label\": \"First Response Time for Issues\",\n \"name\": \"First Response Time for Issues\",\n \"type\": \"report\"\n }\n]"
"links": "[\n {\n \"dependencies\": [\n \"Issue\"\n ],\n \"doctype\": \"Issue\",\n \"is_query_report\": true,\n \"label\": \"First Response Time for Issues\",\n \"name\": \"First Response Time for Issues\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Issue\"\n ],\n \"doctype\": \"Issue\",\n \"is_query_report\": true,\n \"label\": \"Issue Summary\",\n \"name\": \"Issue Summary\",\n \"type\": \"report\"\n }\n]"
}
],
"category": "Modules",
@ -43,7 +43,7 @@
"idx": 0,
"is_standard": 1,
"label": "Support",
"modified": "2020-08-11 15:49:34.307341",
"modified": "2020-10-12 18:40:22.252915",
"modified_by": "Administrator",
"module": "Support",
"name": "Support",

View File

@ -0,0 +1,73 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Issue Summary"] = {
"filters": [
{
fieldname: "company",
label: __("Company"),
fieldtype: "Link",
options: "Company",
default: frappe.defaults.get_user_default("Company"),
reqd: 1
},
{
fieldname: "based_on",
label: __("Based On"),
fieldtype: "Select",
options: ["Customer", "Issue Type", "Issue Priority", "Assigned To"],
default: "Customer",
reqd: 1
},
{
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_global_default("year_start_date"),
reqd: 1
},
{
fieldname:"to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.defaults.get_global_default("year_end_date"),
reqd: 1
},
{
fieldname: "status",
label: __("Status"),
fieldtype: "Select",
options:[
{label: __('Open'), value: 'Open'},
{label: __('Replied'), value: 'Replied'},
{label: __('Resolved'), value: 'Resolved'},
{label: __('Closed'), value: 'Closed'}
]
},
{
fieldname: "priority",
label: __("Issue Priority"),
fieldtype: "Link",
options: "Issue Priority"
},
{
fieldname: "customer",
label: __("Customer"),
fieldtype: "Link",
options: "Customer"
},
{
fieldname: "project",
label: __("Project"),
fieldtype: "Link",
options: "Project"
},
{
fieldname: "assigned_to",
label: __("Assigned To"),
fieldtype: "Link",
options: "User"
}
]
};

View File

@ -0,0 +1,26 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2020-10-12 01:01:55.181777",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"modified": "2020-10-12 14:54:55.655920",
"modified_by": "Administrator",
"module": "Support",
"name": "Issue Summary",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Issue",
"report_name": "Issue Summary",
"report_type": "Script Report",
"roles": [
{
"role": "Support Team"
}
]
}

View File

@ -0,0 +1,353 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
import json
from six import iteritems
from frappe import _, scrub
from frappe.utils import flt
def execute(filters=None):
return IssueSummary(filters).run()
class IssueSummary(object):
def __init__(self, filters=None):
self.filters = frappe._dict(filters or {})
def run(self):
self.get_columns()
self.get_data()
self.get_chart_data()
self.get_report_summary()
return self.columns, self.data, None, self.chart, self.report_summary
def get_columns(self):
self.columns = []
if self.filters.based_on == 'Customer':
self.columns.append({
'label': _('Customer'),
'options': 'Customer',
'fieldname': 'customer',
'fieldtype': 'Link',
'width': 200
})
elif self.filters.based_on == 'Assigned To':
self.columns.append({
'label': _('User'),
'fieldname': 'user',
'fieldtype': 'Link',
'options': 'User',
'width': 200
})
elif self.filters.based_on == 'Issue Type':
self.columns.append({
'label': _('Issue Type'),
'fieldname': 'issue_type',
'fieldtype': 'Link',
'options': 'Issue Type',
'width': 200
})
elif self.filters.based_on == 'Issue Priority':
self.columns.append({
'label': _('Issue Priority'),
'fieldname': 'priority',
'fieldtype': 'Link',
'options': 'Issue Priority',
'width': 200
})
self.statuses = ['Open', 'Replied', 'Resolved', 'Closed']
for status in self.statuses:
self.columns.append({
'label': _(status),
'fieldname': scrub(status),
'fieldtype': 'Int',
'width': 80
})
self.columns.append({
'label': _('Total Issues'),
'fieldname': 'total_issues',
'fieldtype': 'Int',
'width': 100
})
self.sla_status_map = {
'SLA Failed': 'failed',
'SLA Fulfilled': 'fulfilled',
'SLA Ongoing': 'ongoing'
}
for label, fieldname in self.sla_status_map.items():
self.columns.append({
'label': _(label),
'fieldname': fieldname,
'fieldtype': 'Int',
'width': 100
})
self.metrics = ['Avg First Response Time', 'Avg Response Time', 'Avg Hold Time',
'Avg Resolution Time', 'Avg User Resolution Time']
for metric in self.metrics:
self.columns.append({
'label': _(metric),
'fieldname': scrub(metric),
'fieldtype': 'Duration',
'width': 170
})
def get_data(self):
self.get_issues()
self.get_rows()
def get_issues(self):
filters = self.get_common_filters()
self.field_map = {
'Customer': 'customer',
'Issue Type': 'issue_type',
'Issue Priority': 'priority',
'Assigned To': '_assign'
}
self.entries = frappe.db.get_all('Issue',
fields=[self.field_map.get(self.filters.based_on), 'name', 'opening_date', 'status', 'avg_response_time',
'first_response_time', 'total_hold_time', 'user_resolution_time', 'resolution_time', 'agreement_status'],
filters=filters
)
def get_common_filters(self):
filters = {}
filters['opening_date'] = ('between', [self.filters.from_date, self.filters.to_date])
if self.filters.get('assigned_to'):
filters['_assign'] = ('like', '%' + self.filters.get('assigned_to') + '%')
for entry in ['company', 'status', 'priority', 'customer', 'project']:
if self.filters.get(entry):
filters[entry] = self.filters.get(entry)
return filters
def get_rows(self):
self.data = []
self.get_summary_data()
for entity, data in iteritems(self.issue_summary_data):
if self.filters.based_on == 'Customer':
row = {'customer': entity}
elif self.filters.based_on == 'Assigned To':
row = {'user': entity}
elif self.filters.based_on == 'Issue Type':
row = {'issue_type': entity}
elif self.filters.based_on == 'Issue Priority':
row = {'priority': entity}
for status in self.statuses:
count = flt(data.get(status, 0.0))
row[scrub(status)] = count
row['total_issues'] = data.get('total_issues', 0.0)
for sla_status in self.sla_status_map.values():
value = flt(data.get(sla_status), 0.0)
row[sla_status] = value
for metric in self.metrics:
value = flt(data.get(scrub(metric)), 0.0)
row[scrub(metric)] = value
self.data.append(row)
def get_summary_data(self):
self.issue_summary_data = frappe._dict()
for d in self.entries:
status = d.status
agreement_status = scrub(d.agreement_status)
if self.filters.based_on == 'Assigned To':
if d._assign:
for entry in json.loads(d._assign):
self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault(status, 0.0)
self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault(agreement_status, 0.0)
self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault('total_issues', 0.0)
self.issue_summary_data[entry][status] += 1
self.issue_summary_data[entry][agreement_status] += 1
self.issue_summary_data[entry]['total_issues'] += 1
else:
field = self.field_map.get(self.filters.based_on)
value = d.get(field)
if not value:
value = _('Not Specified')
self.issue_summary_data.setdefault(value, frappe._dict()).setdefault(status, 0.0)
self.issue_summary_data.setdefault(value, frappe._dict()).setdefault(agreement_status, 0.0)
self.issue_summary_data.setdefault(value, frappe._dict()).setdefault('total_issues', 0.0)
self.issue_summary_data[value][status] += 1
self.issue_summary_data[value][agreement_status] += 1
self.issue_summary_data[value]['total_issues'] += 1
self.get_metrics_data()
def get_metrics_data(self):
issues = []
metrics_list = ['avg_response_time', 'avg_first_response_time', 'avg_hold_time',
'avg_resolution_time', 'avg_user_resolution_time']
for entry in self.entries:
issues.append(entry.name)
field = self.field_map.get(self.filters.based_on)
if issues:
if self.filters.based_on == 'Assigned To':
assignment_map = frappe._dict()
for d in self.entries:
if d._assign:
for entry in json.loads(d._assign):
for metric in metrics_list:
self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault(metric, 0.0)
self.issue_summary_data[entry]['avg_response_time'] += d.get('avg_response_time') or 0.0
self.issue_summary_data[entry]['avg_first_response_time'] += d.get('first_response_time') or 0.0
self.issue_summary_data[entry]['avg_hold_time'] += d.get('total_hold_time') or 0.0
self.issue_summary_data[entry]['avg_resolution_time'] += d.get('resolution_time') or 0.0
self.issue_summary_data[entry]['avg_user_resolution_time'] += d.get('user_resolution_time') or 0.0
if not assignment_map.get(entry):
assignment_map[entry] = 0
assignment_map[entry] += 1
for entry in assignment_map:
for metric in metrics_list:
self.issue_summary_data[entry][metric] /= flt(assignment_map.get(entry))
else:
data = frappe.db.sql("""
SELECT
{0}, AVG(first_response_time) as avg_frt,
AVG(avg_response_time) as avg_resp_time,
AVG(total_hold_time) as avg_hold_time,
AVG(resolution_time) as avg_resolution_time,
AVG(user_resolution_time) as avg_user_resolution_time
FROM `tabIssue`
WHERE
name IN %(issues)s
GROUP BY {0}
""".format(field), {'issues': issues}, as_dict=1)
for entry in data:
value = entry.get(field)
if not value:
value = _('Not Specified')
for metric in metrics_list:
self.issue_summary_data.setdefault(value, frappe._dict()).setdefault(metric, 0.0)
self.issue_summary_data[value]['avg_response_time'] = entry.get('avg_resp_time') or 0.0
self.issue_summary_data[value]['avg_first_response_time'] = entry.get('avg_frt') or 0.0
self.issue_summary_data[value]['avg_hold_time'] = entry.get('avg_hold_time') or 0.0
self.issue_summary_data[value]['avg_resolution_time'] = entry.get('avg_resolution_time') or 0.0
self.issue_summary_data[value]['avg_user_resolution_time'] = entry.get('avg_user_resolution_time') or 0.0
def get_chart_data(self):
if not self.data:
return None
labels = []
open_issues = []
replied_issues = []
resolved_issues = []
closed_issues = []
entity = self.filters.based_on
entity_field = self.field_map.get(entity)
if entity == 'Assigned To':
entity_field = 'user'
for entry in self.data:
labels.append(entry.get(entity_field))
open_issues.append(entry.get('open'))
replied_issues.append(entry.get('replied'))
resolved_issues.append(entry.get('resolved'))
closed_issues.append(entry.get('closed'))
self.chart = {
'data': {
'labels': labels[:30],
'datasets': [
{
'name': 'Open',
'values': open_issues[:30]
},
{
'name': 'Replied',
'values': replied_issues[:30]
},
{
'name': 'Resolved',
'values': resolved_issues[:30]
},
{
'name': 'Closed',
'values': closed_issues[:30]
}
]
},
'type': 'bar',
'barOptions': {
'stacked': True
}
}
def get_report_summary(self):
if not self.data:
return None
open_issues = 0
replied = 0
resolved = 0
closed = 0
for entry in self.data:
open_issues += entry.get('open')
replied += entry.get('replied')
resolved += entry.get('resolved')
closed += entry.get('closed')
self.report_summary = [
{
'value': open_issues,
'indicator': 'Red',
'label': _('Open'),
'datatype': 'Int',
},
{
'value': replied,
'indicator': 'Grey',
'label': _('Replied'),
'datatype': 'Int',
},
{
'value': resolved,
'indicator': 'Green',
'label': _('Resolved'),
'datatype': 'Int',
},
{
'value': closed,
'indicator': 'Green',
'label': _('Closed'),
'datatype': 'Int',
}
]