diff --git a/erpnext/__init__.py b/erpnext/__init__.py index e822fda512..84b2b0388a 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -4,7 +4,7 @@ import inspect import frappe from erpnext.hooks import regional_overrides -__version__ = '9.2.0' +__version__ = '9.2.1' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index fb01efe13b..1103b701dc 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -200,7 +200,7 @@ def update_doc(new_document, reference_doc, args, schedule_date): new_document.run_method("on_recurring", reference_doc=reference_doc, subscription_doc=args) def set_subscription_period(args, mcount, new_document): - if new_document.meta.get_field('from_date') and new_document.meta.get_field('to_date'): + if mcount and new_document.meta.get_field('from_date') and new_document.meta.get_field('to_date'): last_ref_doc = frappe.db.sql(""" select name, from_date, to_date from `tab{0}` diff --git a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py index 2345872983..53f82e034c 100644 --- a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py +++ b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py @@ -19,13 +19,13 @@ class TestFeeValidity(unittest.TestCase): patient = frappe.new_doc("Patient") patient.patient_name = "Test Patient" patient.sex = "Male" - patient.save(ignore_permissions = True) + patient.save(ignore_permissions=True) patient = patient.name if not physician: physician = frappe.new_doc("Physician") - physician.first_name= "Amit Jain" - physician.save(ignore_permissions = True) + physician.first_name = "Amit Jain" + physician.save(ignore_permissions=True) physician = physician.name frappe.db.set_value("Healthcare Settings", None, "max_visit", 2) @@ -50,5 +50,5 @@ def create_appointment(patient, physician, appointment_date): appointment.patient = patient appointment.physician = physician appointment.appointment_date = appointment_date - appointment.save(ignore_permissions = True) + appointment.save(ignore_permissions=True) return appointment diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index 2532ed115a..1942b66f7b 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -30,6 +30,14 @@ frappe.ui.form.on('Patient Appointment', { frm.add_custom_button(__('Cancel'), function() { btn_update_status(frm, "Cancelled"); }); + + frm.add_custom_button(__("Consultation"),function(){ + btn_create_consultation(frm); + },"Create"); + + frm.add_custom_button(__('Vital Signs'), function() { + btn_create_vital_signs(frm); + },"Create"); } if(frm.doc.status == "Pending"){ frm.add_custom_button(__('Set Open'), function() { @@ -40,14 +48,6 @@ frappe.ui.form.on('Patient Appointment', { }); } - frm.add_custom_button(__("Consultation"),function(){ - btn_create_consultation(frm); - },"Create"); - - frm.add_custom_button(__('Vital Signs'), function() { - btn_create_vital_signs(frm); - },"Create"); - if(!frm.doc.__islocal){ if(frm.doc.sales_invoice && frappe.user.has_role("Accounts User")){ frm.add_custom_button(__('Invoice'), function() { @@ -188,7 +188,7 @@ var btn_update_status = function(frm, status){ frappe.call({ method: "erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_status", - args: {appointmentId: doc.name, status:status}, + args: {appointment_id: doc.name, status:status}, callback: function(data){ if(!data.exc){ frm.reload_doc(); diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json index 57e568b6b3..1663c5b64f 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json @@ -234,6 +234,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "eval:!doc.__islocal", "fieldname": "section_break_1", "fieldtype": "Section Break", "hidden": 0, @@ -755,7 +756,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-10-05 12:13:03.204936", + "modified": "2017-10-25 23:33:36.060803", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Appointment", diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index 2647034f78..4379986ddc 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -6,66 +6,100 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document import json -from frappe.utils import getdate +from frappe.utils import getdate, cint from frappe import _ import datetime from frappe.core.doctype.sms_settings.sms_settings import send_sms from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account,get_income_account + class PatientAppointment(Document): def on_update(self): today = datetime.date.today() appointment_date = getdate(self.appointment_date) - #If appointment created for today set as open - if(today == appointment_date): - frappe.db.set_value("Patient Appointment",self.name,"status","Open") + + # If appointment created for today set as open + if today == appointment_date: + frappe.db.set_value("Patient Appointment", self.name, "status", "Open") self.reload() def after_insert(self): - #Check fee validity exists + # Check fee validity exists appointment = self validity_exist = validity_exists(appointment.physician, appointment.patient) - if validity_exist : - fee_validity = frappe.get_doc("Fee Validity",validity_exist[0][0]) - #Check if the validity is valid + if validity_exist: + fee_validity = frappe.get_doc("Fee Validity", validity_exist[0][0]) + + # Check if the validity is valid appointment_date = getdate(appointment.appointment_date) - if((fee_validity.valid_till >= appointment_date) and (fee_validity.visited < fee_validity.max_visit)): + if (fee_validity.valid_till >= appointment_date) and (fee_validity.visited < fee_validity.max_visit): visited = fee_validity.visited + 1 - frappe.db.set_value("Fee Validity",fee_validity.name,"visited",visited) - if(fee_validity.ref_invoice): - frappe.db.set_value("Patient Appointment",appointment.name,"sales_invoice",fee_validity.ref_invoice) + frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited) + if fee_validity.ref_invoice: + frappe.db.set_value("Patient Appointment", appointment.name, "sales_invoice", fee_validity.ref_invoice) frappe.msgprint(_("{0} has fee validity till {1}").format(appointment.patient, fee_validity.valid_till)) confirm_sms(self) -def appointment_cancel(appointmentId): - appointment = frappe.get_doc("Patient Appointment",appointmentId) - #If invoice --> fee_validity update with -1 visit - if (appointment.sales_invoice): - validity = frappe.db.exists({"doctype": "Fee Validity","ref_invoice": appointment.sales_invoice}) - if(validity): - fee_validity = frappe.get_doc("Fee Validity",validity[0][0]) - visited = fee_validity.visited - 1 - frappe.db.set_value("Fee Validity",fee_validity.name,"visited",visited) - if visited <= 0: - frappe.msgprint(_("Appointment cancelled, Please review and cancel the invoice {0}".format(appointment.sales_invoice))) - else: - frappe.msgprint(_("Appointment cancelled")) + def save(self, *args, **kwargs): + # duration is the only changeable field in the document + if not self.is_new(): + self.db_set('duration', cint(self.duration)) + else: + super(PatientAppointment, self).save(*args, **kwargs) + + +def appointment_cancel(appointment_id): + appointment = frappe.get_doc("Patient Appointment", appointment_id) + + # If invoice --> fee_validity update with -1 visit + if appointment.sales_invoice: + validity = frappe.db.exists({"doctype": "Fee Validity", "ref_invoice": appointment.sales_invoice}) + if validity: + fee_validity = frappe.get_doc("Fee Validity", validity[0][0]) + visited = fee_validity.visited - 1 + frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited) + if visited <= 0: + frappe.msgprint( + _("Appointment cancelled, Please review and cancel the invoice {0}".format(appointment.sales_invoice)) + ) + else: + frappe.msgprint(_("Appointment cancelled")) + @frappe.whitelist() def get_availability_data(date, physician): - # get availability data of 'physician' on 'date' + """ + Get availability data of 'physician' on 'date' + :param date: Date to check in schedule + :param physician: Name of the physician + :return: dict containing a list of available slots, list of appointments and time of appointments + """ + date = getdate(date) weekday = date.strftime("%A") available_slots = [] + physician_schedule_name = None + physician_schedule = None + time_per_appointment = None + # get physicians schedule physician_schedule_name = frappe.db.get_value("Physician", physician, "physician_schedule") - physician_schedule = frappe.get_doc("Physician Schedule", physician_schedule_name) - time_per_appointment = frappe.db.get_value("Physician", physician, "time_per_appointment") + if physician_schedule_name: + physician_schedule = frappe.get_doc("Physician Schedule", physician_schedule_name) + time_per_appointment = frappe.db.get_value("Physician", physician, "time_per_appointment") + else: + frappe.throw(_("Dr {0} does not have a Physician Schedule. Add it in Physician master".format(physician))) - for t in physician_schedule.time_slots: - if weekday == t.day: - available_slots.append(t) + if physician_schedule: + for t in physician_schedule.time_slots: + if weekday == t.day: + available_slots.append(t) + + # `time_per_appointment` should never be None since validation in `Patient` is supposed to prevent + # that. However, it isn't impossible so we'll prepare for that. + if not time_per_appointment: + frappe.throw(_('"Time Per Appointment" hasn"t been set for Dr {0}. Add it in Physician master.').format(physician)) # if physician not available return if not available_slots: @@ -89,27 +123,36 @@ def get_availability_data(date, physician): "time_per_appointment": time_per_appointment } + @frappe.whitelist() -def update_status(appointmentId, status): - frappe.db.set_value("Patient Appointment",appointmentId,"status",status) - if(status=="Cancelled"): - appointment_cancel(appointmentId) +def update_status(appointment_id, status): + frappe.db.set_value("Patient Appointment", appointment_id, "status", status) + if status == "Cancelled": + appointment_cancel(appointment_id) + @frappe.whitelist() def set_open_appointments(): today = getdate() - frappe.db.sql("""update `tabPatient Appointment` set status='Open' where status = 'Scheduled' and appointment_date = %s""",(today)) + frappe.db.sql( + "update `tabPatient Appointment` set status='Open' where status = 'Scheduled'" + " and appointment_date = %s", today) + @frappe.whitelist() def set_pending_appointments(): today = getdate() - frappe.db.sql("""update `tabPatient Appointment` set status='Pending' where status in ('Scheduled','Open') and appointment_date < %s""",(today)) + frappe.db.sql( + "update `tabPatient Appointment` set status='Pending' where status in " + "('Scheduled','Open') and appointment_date < %s", today) + def confirm_sms(doc): - if (frappe.db.get_value("Healthcare Settings", None, "app_con")=='1'): + if frappe.db.get_value("Healthcare Settings", None, "app_con") == '1': message = frappe.db.get_value("Healthcare Settings", None, "app_con_msg") send_message(doc, message) + @frappe.whitelist() def create_invoice(company, physician, patient, appointment_id, appointment_date): if not appointment_id: @@ -134,21 +177,24 @@ def create_invoice(company, physician, patient, appointment_id, appointment_date frappe.db.set_value("Consultation", consultation[0][0], "invoice", sales_invoice.name) return sales_invoice.name + def get_fee_validity(physician, patient, date): validity_exist = validity_exists(physician, patient) - if validity_exist : - fee_validity = frappe.get_doc("Fee Validity",validity_exist[0][0]) + if validity_exist: + fee_validity = frappe.get_doc("Fee Validity", validity_exist[0][0]) fee_validity = update_fee_validity(fee_validity, date) else: fee_validity = create_fee_validity(physician, patient, date) return fee_validity + def validity_exists(physician, patient): return frappe.db.exists({ "doctype": "Fee Validity", "physician": physician, "patient": patient}) + def update_fee_validity(fee_validity, date): max_visit = frappe.db.get_value("Healthcare Settings", None, "max_visit") valid_days = frappe.db.get_value("Healthcare Settings", None, "valid_days") @@ -164,6 +210,7 @@ def update_fee_validity(fee_validity, date): fee_validity.save(ignore_permissions=True) return fee_validity + def create_fee_validity(physician, patient, date): fee_validity = frappe.new_doc("Fee Validity") fee_validity.physician = physician @@ -171,6 +218,7 @@ def create_fee_validity(physician, patient, date): fee_validity = update_fee_validity(fee_validity, date) return fee_validity + def create_invoice_items(appointment_id, physician, company, invoice): item_line = invoice.append("items") item_line.item_name = "Consulting Charges" @@ -178,16 +226,17 @@ def create_invoice_items(appointment_id, physician, company, invoice): item_line.qty = 1 item_line.uom = "Nos" item_line.conversion_factor = 1 - item_line.income_account = get_income_account(physician,company) + item_line.income_account = get_income_account(physician, company) op_consulting_charge = frappe.db.get_value("Physician", physician, "op_consulting_charge") if op_consulting_charge: item_line.rate = op_consulting_charge item_line.amount = op_consulting_charge return invoice + @frappe.whitelist() def create_consultation(appointment): - appointment = frappe.get_doc("Patient Appointment",appointment) + appointment = frappe.get_doc("Patient Appointment", appointment) consultation = frappe.new_doc("Consultation") consultation.appointment = appointment.name consultation.patient = appointment.patient @@ -199,29 +248,37 @@ def create_consultation(appointment): consultation.invoice = appointment.sales_invoice return consultation.as_dict() + def remind_appointment(): - if (frappe.db.get_value("Healthcare Settings", None, "app_rem")=='1'): + if frappe.db.get_value("Healthcare Settings", None, "app_rem") == '1': rem_before = datetime.datetime.strptime(frappe.get_value("Healthcare Settings", None, "rem_before"), "%H:%M:%S") - rem_dt = datetime.datetime.now() + datetime.timedelta(hours = rem_before.hour, minutes=rem_before.minute, seconds= rem_before.second) + rem_dt = datetime.datetime.now() + datetime.timedelta( + hours=rem_before.hour, minutes=rem_before.minute, seconds=rem_before.second) - appointment_list = frappe.db.sql("select name from `tabPatient Appointment` where start_dt between %s and %s and reminded = 0 ", (datetime.datetime.now(), rem_dt)) + appointment_list = frappe.db.sql( + "select name from `tabPatient Appointment` where start_dt between %s and %s and reminded = 0 ", + (datetime.datetime.now(), rem_dt) + ) - for i in range (0,len(appointment_list)): + for i in range(0, len(appointment_list)): doc = frappe.get_doc("Patient Appointment", appointment_list[i][0]) message = frappe.db.get_value("Healthcare Settings", None, "app_rem_msg") send_message(doc, message) - frappe.db.set_value("Patient Appointment",doc.name,"reminded",1) + frappe.db.set_value("Patient Appointment", doc.name, "reminded",1) + def send_message(doc, message): - patient = frappe.get_doc("Patient",doc.patient) - if(patient.mobile): + patient = frappe.get_doc("Patient", doc.patient) + if patient.mobile: context = {"doc": doc, "alert": doc, "comments": None} if doc.get("_comments"): context["comments"] = json.loads(doc.get("_comments")) - #jinja to string convertion happens here + + # jinja to string convertion happens here message = frappe.render_template(message, context) number = [patient.mobile] - send_sms(number,message) + send_sms(number, message) + @frappe.whitelist() def get_events(start, end, filters=None): diff --git a/erpnext/healthcare/doctype/physician/physician.json b/erpnext/healthcare/doctype/physician/physician.json index 4348e9035e..3edad0b827 100644 --- a/erpnext/healthcare/doctype/physician/physician.json +++ b/erpnext/healthcare/doctype/physician/physician.json @@ -501,6 +501,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "description": "In minutes", "fieldname": "time_per_appointment", "fieldtype": "Data", "hidden": 0, @@ -809,7 +810,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-10-04 17:35:44.363742", + "modified": "2017-10-05 16:08:24.624644", "modified_by": "Administrator", "module": "Healthcare", "name": "Physician", diff --git a/erpnext/healthcare/doctype/physician/physician.py b/erpnext/healthcare/doctype/physician/physician.py index 8680d5d127..4d035d39f6 100644 --- a/erpnext/healthcare/doctype/physician/physician.py +++ b/erpnext/healthcare/doctype/physician/physician.py @@ -20,22 +20,32 @@ class Physician(Document): [cstr(self.get(f)).strip() for f in ["first_name","middle_name","last_name"]])) def validate(self): + self.validate_schedule_and_time() validate_party_accounts(self) + if self.user_id: self.validate_for_enabled_user_id() self.validate_duplicate_user_id() existing_user_id = frappe.db.get_value("Physician", self.name, "user_id") - if(self.user_id != existing_user_id): + if self.user_id != existing_user_id: frappe.permissions.remove_user_permission( "Physician", self.name, existing_user_id) - else: existing_user_id = frappe.db.get_value("Physician", self.name, "user_id") if existing_user_id: frappe.permissions.remove_user_permission( "Physician", self.name, existing_user_id) + def validate_schedule_and_time(self): + if (self.physician_schedule or self.time_per_appointment) and \ + not (self.physician_schedule and self.time_per_appointment): + frappe.msgprint( + _('Both "Physician Schedule" and Time Per Appointment" must be set for Dr {0}').format( + self.first_name), + title='Error', raise_exception=1, indicator='red' + ) + def on_update(self): if self.user_id: frappe.permissions.add_user_permission("Physician", self.name, self.user_id) diff --git a/erpnext/healthcare/doctype/physician/test_physician.py b/erpnext/healthcare/doctype/physician/test_physician.py index b6ea92cc72..e57bdae46e 100644 --- a/erpnext/healthcare/doctype/physician/test_physician.py +++ b/erpnext/healthcare/doctype/physician/test_physician.py @@ -3,8 +3,35 @@ # See license.txt from __future__ import unicode_literals import unittest +import frappe + +test_dependencies = ['Physician Schedule'] -# test_records = frappe.get_test_records('Physician') class TestPhysician(unittest.TestCase): - pass + def tearDown(self): + frappe.delete_doc_if_exists('Physician', '_Testdoctor2', force=1) + + def test_schedule_and_time(self): + physician = frappe.new_doc('Physician') + physician.first_name = '_Testdoctor2' + physician.physician_schedule = '_Test Testdoctor Schedule' + + self.assertRaises(frappe.ValidationError, physician.insert) + + physician.physician_schedule = '' + physician.time_per_appointment = 15 + + self.assertRaises(frappe.ValidationError, physician.insert) + + physician.physician_schedule = '_Test Testdoctor Schedule' + physician.time_per_appointment = 15 + + physician.insert() + + def test_new_physician_without_schedule(self): + physician = frappe.new_doc('Physician') + physician.first_name = '_Testdoctor2' + + physician.insert() + self.assertEqual(frappe.get_value('Physician', '_Testdoctor2', 'first_name'), '_Testdoctor2') diff --git a/erpnext/healthcare/doctype/physician_schedule/physician_schedule.py b/erpnext/healthcare/doctype/physician_schedule/physician_schedule.py index 5cbdd126a9..167e9cd4b8 100644 --- a/erpnext/healthcare/doctype/physician_schedule/physician_schedule.py +++ b/erpnext/healthcare/doctype/physician_schedule/physician_schedule.py @@ -5,5 +5,7 @@ from __future__ import unicode_literals from frappe.model.document import Document + class PhysicianSchedule(Document): - pass + def autoname(self): + self.name = self.schedule_name diff --git a/erpnext/manufacturing/doctype/production_order/production_order.py b/erpnext/manufacturing/doctype/production_order/production_order.py index f4d37760d3..89ecbe7ec5 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.py +++ b/erpnext/manufacturing/doctype/production_order/production_order.py @@ -569,7 +569,7 @@ def get_events(start, end, filters=None): where ((ifnull(planned_start_date, '0000-00-00')!= '0000-00-00') \ and (planned_start_date <= %(end)s) \ and ((ifnull(planned_start_date, '0000-00-00')!= '0000-00-00') \ - and planned_end_date >= %(start)s)) {conditions} + and ifnull(planned_end_date, '2199-12-31 00:00:00') >= %(start)s)) {conditions} """.format(conditions=conditions), { "start": start, "end": end diff --git a/erpnext/patches/v9_0/set_schedule_date_for_material_request_and_purchase_order.py b/erpnext/patches/v9_0/set_schedule_date_for_material_request_and_purchase_order.py index 7ab0e2ca83..3d012978fa 100644 --- a/erpnext/patches/v9_0/set_schedule_date_for_material_request_and_purchase_order.py +++ b/erpnext/patches/v9_0/set_schedule_date_for_material_request_and_purchase_order.py @@ -17,6 +17,8 @@ def execute(): doc = frappe.get_doc(doctype, record) if doc.items: if not doc.schedule_date: - min_schedule_date = min([d.schedule_date for d in doc.items]) - frappe.db.set_value(doctype, record, - "schedule_date", min_schedule_date, update_modified=False) \ No newline at end of file + schedule_dates = [d.schedule_date for d in doc.items if d.schedule_date] + if len(schedule_dates) > 0: + min_schedule_date = min(schedule_dates) + frappe.db.set_value(doctype, record, + "schedule_date", min_schedule_date, update_modified=False) \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index 7400e432ae..20b5bb05b6 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -71,9 +71,9 @@ def get_items(start, page_length, price_list, item_group, search_value=""): def get_conditions(item_code, serial_no, batch_no, barcode): if serial_no or batch_no or barcode: - return frappe.db.escape(item_code), "i.item_code = %(item_code)s" + return frappe.db.escape(item_code), "i.name = %(item_code)s" - condition = """(i.item_code like %(item_code)s + condition = """(i.name like %(item_code)s or i.item_name like %(item_code)s)""" return '%%%s%%'%(frappe.db.escape(item_code)), condition diff --git a/erpnext/setup/setup_wizard/setup_wizard.py b/erpnext/setup/setup_wizard/setup_wizard.py index f7b09d6934..766f9b5223 100644 --- a/erpnext/setup/setup_wizard/setup_wizard.py +++ b/erpnext/setup/setup_wizard/setup_wizard.py @@ -40,7 +40,7 @@ def setup_complete(args=None): frappe.local.message_log = [] domain_settings = frappe.get_single('Domain Settings') - domain_settings.set_active_domains([args.get('domain')]) + domain_settings.set_active_domains([_(args.get('domain'))]) frappe.db.commit() login_as_first_user(args) diff --git a/erpnext/stock/doctype/item/item_list.js b/erpnext/stock/doctype/item/item_list.js index cc46177f53..db53ae9651 100644 --- a/erpnext/stock/doctype/item/item_list.js +++ b/erpnext/stock/doctype/item/item_list.js @@ -4,12 +4,12 @@ frappe.listview_settings['Item'] = { filters: [["disabled", "=", "0"]], get_indicator: function(doc) { - if(doc.total_projected_qty < 0) { - return [__("Shortage"), "red", "total_projected_qty,<,0"]; - } else if (doc.disabled) { + if (doc.disabled) { return [__("Disabled"), "grey", "disabled,=,Yes"]; } else if (doc.end_of_life && doc.end_of_life < frappe.datetime.get_today()) { return [__("Expired"), "grey", "end_of_life,<,Today"]; + } else if(doc.total_projected_qty < 0) { + return [__("Shortage"), "red", "total_projected_qty,<,0"]; } else if (doc.has_variants) { return [__("Template"), "orange", "has_variants,=,Yes"]; } else if (doc.variant_of) {