diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json index cf55d554fb..5858f10bb0 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json @@ -30,6 +30,7 @@ "fieldtype": "Link", "label": "Reference Document Type", "options": "DocType", + "read_only_depends_on": "eval:!doc.__islocal", "reqd": 1 }, { @@ -48,7 +49,7 @@ } ], "links": [], - "modified": "2020-03-22 20:34:39.805728", + "modified": "2021-02-08 16:37:53.936656", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting Dimension", diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index ef0d3a3cb1..dd26c4cec2 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -29,6 +29,16 @@ class AccountingDimension(Document): if exists and self.is_new(): frappe.throw("Document Type already used as a dimension") + if not self.is_new(): + self.validate_document_type_change() + + def validate_document_type_change(self): + doctype_before_save = frappe.db.get_value("Accounting Dimension", self.name, "document_type") + if doctype_before_save != self.document_type: + message = _("Cannot change Reference Document Type.") + message += _("Please create a new Accounting Dimension if required.") + frappe.throw(message) + def after_insert(self): if frappe.flags.in_test: make_dimension_in_accounting_doctypes(doc=self) diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json index c0327ad0ad..0f3fbc0b8d 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json +++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json @@ -108,7 +108,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-12-16 15:27:23.659285", + "modified": "2021-02-03 12:04:58.678402", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting Dimension Filter", @@ -125,6 +125,30 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 } ], "quick_entry": 1, diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 791b03a0d7..f7a15c04fa 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -88,19 +88,19 @@ class PaymentReconciliation(Document): voucher_type = ('Sales Invoice' if self.party_type == 'Customer' else "Purchase Invoice") - return frappe.db.sql(""" SELECT `tab{doc}`.name as reference_name, %(voucher_type)s as reference_type, - (sum(`tabGL Entry`.{dr_or_cr}) - sum(`tabGL Entry`.{reconciled_dr_or_cr})) as amount, + return frappe.db.sql(""" SELECT doc.name as reference_name, %(voucher_type)s as reference_type, + (sum(gl.{dr_or_cr}) - sum(gl.{reconciled_dr_or_cr})) as amount, account_currency as currency - FROM `tab{doc}`, `tabGL Entry` + FROM `tab{doc}` doc, `tabGL Entry` gl WHERE - (`tab{doc}`.name = `tabGL Entry`.against_voucher or `tab{doc}`.name = `tabGL Entry`.voucher_no) - and `tab{doc}`.{party_type_field} = %(party)s - and `tab{doc}`.is_return = 1 and `tab{doc}`.return_against IS NULL - and `tabGL Entry`.against_voucher_type = %(voucher_type)s - and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s - and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s - and `tabGL Entry`.is_cancelled = 0 - GROUP BY `tab{doc}`.name + (doc.name = gl.against_voucher or doc.name = gl.voucher_no) + and doc.{party_type_field} = %(party)s + and doc.is_return = 1 and ifnull(doc.return_against, "") = "" + and gl.against_voucher_type = %(voucher_type)s + and doc.docstatus = 1 and gl.party = %(party)s + and gl.party_type = %(party_type)s and gl.account = %(account)s + and gl.is_cancelled = 0 + GROUP BY doc.name Having amount > 0 """.format( @@ -113,7 +113,7 @@ class PaymentReconciliation(Document): 'party_type': self.party_type, 'voucher_type': voucher_type, 'account': self.receivable_payable_account - }, as_dict=1) + }, as_dict=1, debug=1) def add_payment_entries(self, entries): self.set('payments', []) diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 998003ac69..928b373eff 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -52,8 +52,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum row = { 'item_code': d.item_code, - 'item_name': item_record.item_name, - 'item_group': item_record.item_group, + 'item_name': item_record.item_name if item_record else d.item_name, + 'item_group': item_record.item_group if item_record else d.item_group, 'description': d.description, 'invoice': d.parent, 'posting_date': d.posting_date, @@ -383,6 +383,7 @@ def get_items(filters, additional_query_columns): `tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks, `tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total, `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description, + `tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`, `tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center, `tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom, diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 938cbfdc85..d1d096843b 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -352,7 +352,7 @@ def get_lead_with_phone_number(number): leads = frappe.get_all('Lead', or_filters={ 'phone': ['like', '%{}'.format(number)], 'mobile_no': ['like', '%{}'.format(number)] - }, limit=1) + }, limit=1, order_by="creation DESC") lead = leads[0].name if leads else None @@ -361,4 +361,4 @@ def get_lead_with_phone_number(number): def daily_open_lead(): leads = frappe.get_all("Lead", filters = [["contact_date", "Between", [nowdate(), nowdate()]]]) for lead in leads: - frappe.db.set_value("Lead", lead.name, "status", "Open") \ No newline at end of file + frappe.db.set_value("Lead", lead.name, "status", "Open") diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py index 8a918b0275..a8c7720a0a 100644 --- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py @@ -139,12 +139,13 @@ def create_inpatient(patient): inpatient_record.phone = patient_obj.phone inpatient_record.inpatient = "Scheduled" inpatient_record.scheduled_date = today() + inpatient_record.company = "_Test Company" return inpatient_record def get_healthcare_service_unit(unit_name=None): if not unit_name: - service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1}) + service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1, "company": "_Test Company"}) else: service_unit = frappe.db.exists("Healthcare Service Unit", {"healthcare_service_unit_name": unit_name}) diff --git a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py index 0d3f45f500..4b461f1a97 100644 --- a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py +++ b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py @@ -119,7 +119,7 @@ def create_records(patient): ip_record.expected_length_of_stay = 0 ip_record.save() ip_record.reload() - service_unit = get_healthcare_service_unit() + service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy') admit_patient(ip_record, service_unit, now_datetime()) ipmo = create_ipmo(patient) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index bc1d7628ff..109d9216e7 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -78,7 +78,7 @@ website_generators = ["Item Group", "Item", "BOM", "Sales Partner", "Job Opening", "Student Admission"] website_context = { - "favicon": "/assets/erpnext/images/favicon.png", + "favicon": "/assets/erpnext/images/erpnext-favicon.svg", "splash_image": "/assets/erpnext/images/erpnext-logo.svg" } diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index f4b214adc3..c0e614ac08 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -16,11 +16,13 @@ class TestEmployee(unittest.TestCase): employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0]) employee.date_of_birth = "1992" + frappe.utils.nowdate()[4:] employee.company_email = "test@example.com" + employee.company = "_Test Company" employee.save() from erpnext.hr.doctype.employee.employee import get_employees_who_are_born_today, send_birthday_reminders - self.assertTrue(employee.name in [e.name for e in get_employees_who_are_born_today()]) + employees_born_today = get_employees_who_are_born_today() + self.assertTrue(employees_born_today.get("_Test Company")) frappe.db.sql("delete from `tabEmail Queue`") diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py index 46082129e2..c5929c6bf9 100644 --- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py +++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py @@ -36,6 +36,8 @@ def execute(filters=None): conditions, filters = get_conditions(filters) columns, days = get_columns(filters) att_map = get_attendance_list(conditions, filters) + if not att_map: + return columns, [], None, None if filters.group_by: emp_map, group_by_parameters = get_employee_details(filters.group_by, filters.company) @@ -65,10 +67,14 @@ def execute(filters=None): if filters.group_by: emp_att_map = {} for parameter in group_by_parameters: - data.append([ ""+ parameter + ""]) - record, aaa = add_data(emp_map[parameter], att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=leave_list) - emp_att_map.update(aaa) - data += record + emp_map_set = set([key for key in emp_map[parameter].keys()]) + att_map_set = set([key for key in att_map.keys()]) + if (att_map_set & emp_map_set): + parameter_row = [""+ parameter + ""] + ['' for day in range(filters["total_days_in_month"] + 2)] + data.append(parameter_row) + record, emp_att_data = add_data(emp_map[parameter], att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=leave_list) + emp_att_map.update(emp_att_data) + data += record else: record, emp_att_map = add_data(emp_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=leave_list) data += record @@ -237,6 +243,9 @@ def get_attendance_list(conditions, filters): status from tabAttendance where docstatus = 1 %s order by employee, attendance_date""" % conditions, filters, as_dict=1) + if not attendance_list: + msgprint(_("No attendance record found"), alert=True, indicator="orange") + att_map = {} for d in attendance_list: att_map.setdefault(d.employee, frappe._dict()).setdefault(d.day_of_month, "") diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py index 4693d44509..21fd7c2878 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -386,7 +386,8 @@ def get_items(filters=None, search=None): r.description = r.web_long_description or r.description r.image = r.website_image or r.image product_info = get_product_info_for_website(r.item_code, skip_quotation_creation=True).get('product_info') - r.formatted_price = product_info['price'].get('formatted_price') if product_info['price'] else None + if product_info: + r.formatted_price = product_info['price'].get('formatted_price') if product_info['price'] else None return results diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index 9e807f728e..ea81b3eb64 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -288,7 +288,7 @@ def make_sales_invoice(source_name, item_code=None, customer=None): def make_salary_slip(source_name, target_doc=None): target = frappe.new_doc("Salary Slip") set_missing_values(source_name, target) - target.run_method("get_emp_and_leave_details") + target.run_method("get_emp_and_working_day_details") return target diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 4a40e8e8f9..7326238273 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -2,7 +2,7 @@ "css/erpnext.css": [ "public/less/erpnext.less", "public/less/hub.less", - "public/less/call_popup.less", + "public/scss/call_popup.scss", "public/scss/point-of-sale.scss" ], "css/marketplace.css": [ diff --git a/erpnext/public/images/erp-icon.svg b/erpnext/public/images/erp-icon.svg deleted file mode 100644 index 6bec40cc62..0000000000 --- a/erpnext/public/images/erp-icon.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - erpnext-logo - Created with Sketch. - - - - - \ No newline at end of file diff --git a/erpnext/public/images/erpnext-12.svg b/erpnext/public/images/erpnext-12.svg deleted file mode 100644 index fcc8e46fdd..0000000000 --- a/erpnext/public/images/erpnext-12.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - version-12 - Created with Sketch. - - - - - - - - - - - - - - - - - 12 - - - - \ No newline at end of file diff --git a/erpnext/public/images/erpnext-favicon.svg b/erpnext/public/images/erpnext-favicon.svg new file mode 100644 index 0000000000..a3ac3bb2ce --- /dev/null +++ b/erpnext/public/images/erpnext-favicon.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/erpnext/public/images/erpnext-footer.png b/erpnext/public/images/erpnext-footer.png deleted file mode 100644 index ffff7756b7..0000000000 Binary files a/erpnext/public/images/erpnext-footer.png and /dev/null differ diff --git a/erpnext/public/images/erpnext-logo.png b/erpnext/public/images/erpnext-logo.png deleted file mode 100644 index 115faaa6a8..0000000000 Binary files a/erpnext/public/images/erpnext-logo.png and /dev/null differ diff --git a/erpnext/public/images/erpnext_logo.svg b/erpnext/public/images/erpnext_logo.svg deleted file mode 100644 index af3a84953b..0000000000 --- a/erpnext/public/images/erpnext_logo.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/erpnext/public/images/favicon.png b/erpnext/public/images/favicon.png deleted file mode 100644 index b6948856f8..0000000000 Binary files a/erpnext/public/images/favicon.png and /dev/null differ diff --git a/erpnext/public/images/splash.png b/erpnext/public/images/splash.png deleted file mode 100644 index 8e5d055c66..0000000000 Binary files a/erpnext/public/images/splash.png and /dev/null differ diff --git a/erpnext/public/js/call_popup/call_popup.js b/erpnext/public/js/call_popup/call_popup.js index be1745e54f..c954f12ac6 100644 --- a/erpnext/public/js/call_popup/call_popup.js +++ b/erpnext/public/js/call_popup/call_popup.js @@ -7,10 +7,103 @@ class CallPopup { } make() { + frappe.utils.play_sound('incoming-call'); this.dialog = new frappe.ui.Dialog({ 'static': true, - 'minimizable': true, - 'fields': [{ + 'minimizable': true + }); + this.dialog.get_close_btn().show(); + this.setup_dialog(); + this.set_call_status(); + frappe.utils.bind_actions_with_object(this.dialog.$body, this); + this.dialog.$wrapper.addClass('call-popup'); + this.dialog.get_close_btn().unbind('click').click(this.close_modal.bind(this)); + this.dialog.show(); + } + + setup_dialog() { + this.setup_call_details(); + this.dialog.$body.empty().append(this.caller_info); + } + + set_indicator(color, blink=false) { + let classes = `indicator ${color} ${blink ? 'blink': ''}`; + this.dialog.header.find('.indicator').attr('class', classes); + } + + set_call_status(call_status) { + let title = ''; + call_status = call_status || this.call_log.status; + if (['Ringing'].includes(call_status) || !call_status) { + title = __('Incoming call from {0}', [this.get_caller_name() || this.caller_number]); + this.set_indicator('blue', true); + } else if (call_status === 'In Progress') { + title = __('Call Connected'); + this.set_indicator('green'); + } else if (['No Answer', 'Missed'].includes(call_status)) { + this.set_indicator('yellow'); + title = __('Call Missed'); + } else if (['Completed', 'Busy', 'Failed'].includes(call_status)) { + this.set_indicator('red'); + title = __('Call Ended'); + } else { + this.set_indicator('blue'); + title = call_status; + } + this.dialog.set_title(title); + } + + update_call_log(call_log, missed) { + this.call_log = call_log; + this.set_call_status(missed ? 'Missed': null); + } + + close_modal() { + this.dialog.hide(); + delete erpnext.call_popup; + } + + call_ended(call_log, missed) { + frappe.utils.play_sound('call-disconnect'); + this.update_call_log(call_log, missed); + setTimeout(() => { + if (!this.dialog.get_value('call_summary')) { + this.close_modal(); + } + }, 60000); + this.clear_listeners(); + } + + get_caller_name() { + const contact_link = this.get_contact_link(); + return contact_link.link_title || contact_link.link_name; + } + + get_contact_link() { + let log = this.call_log; + let contact_link = log.links.find(d => d.link_doctype === 'Contact'); + return contact_link || {}; + } + + setup_listener() { + frappe.realtime.on(`call_${this.call_log.id}_ended`, call_log => { + this.call_ended(call_log); + }); + + frappe.realtime.on(`call_${this.call_log.id}_missed`, call_log => { + this.call_ended(call_log, true); + }); + } + + clear_listeners() { + frappe.realtime.off(`call_${this.call_log.id}_ended`); + frappe.realtime.off(`call_${this.call_log.id}_missed`); + } + + setup_call_details() { + this.caller_info = $(`
`); + this.call_details = new frappe.ui.FieldGroup({ + fields: [{ 'fieldname': 'name', 'label': 'Name', 'default': this.get_caller_name() || __('Unknown Caller'), @@ -19,17 +112,17 @@ class CallPopup { }, { 'fieldtype': 'Button', 'label': __('Open Contact'), - 'click': () => frappe.set_route('Form', 'Contact', this.call_log.contact), - 'depends_on': () => this.call_log.contact - }, { - 'fieldtype': 'Button', - 'label': __('Open Lead'), - 'click': () => frappe.set_route('Form', 'Lead', this.call_log.lead), - 'depends_on': () => this.call_log.lead + 'click': () => frappe.set_route('Form', 'Contact', this.get_contact_link().link_name), + 'depends_on': () => this.get_caller_name() }, { 'fieldtype': 'Button', 'label': __('Create New Contact'), - 'click': () => frappe.new_doc('Contact', { 'mobile_no': this.caller_number }), + 'click': this.create_new_contact.bind(this), + 'depends_on': () => !this.get_caller_name() + }, { + 'fieldtype': 'Button', + 'label': __('Create New Customer'), + 'click': this.create_new_customer.bind(this), 'depends_on': () => !this.get_caller_name() }, { 'fieldtype': 'Button', @@ -44,26 +137,9 @@ class CallPopup { 'fieldtype': 'Data', 'default': this.caller_number, 'read_only': 1 - }, { - 'fielname': 'last_interaction', - 'fieldtype': 'Section Break', - 'label': __('Activity'), - 'depends_on': () => this.get_caller_name() - }, { - 'fieldtype': 'Small Text', - 'label': __('Last Issue'), - 'fieldname': 'last_issue', - 'read_only': true, - 'depends_on': () => this.call_log.contact, - 'default': `${__('No issue has been raised by the caller.')}` - }, { - 'fieldtype': 'Small Text', - 'label': __('Last Communication'), - 'fieldname': 'last_communication', - 'read_only': true, - 'default': `${__('No communication found.')}` }, { 'fieldtype': 'Section Break', + 'hide_border': 1, }, { 'fieldtype': 'Small Text', 'label': __('Call Summary'), @@ -72,7 +148,7 @@ class CallPopup { 'fieldtype': 'Button', 'label': __('Save'), 'click': () => { - const call_summary = this.dialog.get_value('call_summary'); + const call_summary = this.call_details.get_value('call_summary'); if (!call_summary) return; frappe.xcall('erpnext.telephony.doctype.call_log.call_log.add_call_summary', { 'call_log': this.call_log.name, @@ -94,108 +170,42 @@ class CallPopup { }); } }], + body: this.caller_info }); - this.set_call_status(); - this.dialog.get_close_btn().show(); - this.make_last_interaction_section(); - this.dialog.$body.addClass('call-popup'); - this.dialog.set_secondary_action(this.close_modal.bind(this)); - frappe.utils.play_sound('incoming-call'); - this.dialog.show(); + this.call_details.make(); } - set_indicator(color, blink=false) { - let classes = `indicator ${color} ${blink ? 'blink': ''}`; - this.dialog.header.find('.indicator').attr('class', classes); + is_known_caller() { + return Boolean(this.get_caller_name()); } - set_call_status(call_status) { - let title = ''; - call_status = call_status || this.call_log.status; - if (['Ringing'].includes(call_status) || !call_status) { - title = __('Incoming call from {0}', [this.get_caller_name() || this.caller_number]); - this.set_indicator('blue', true); - } else if (call_status === 'In Progress') { - title = __('Call Connected'); - this.set_indicator('yellow'); - } else if (call_status === 'Missed') { - this.set_indicator('red'); - title = __('Call Missed'); - } else if (['Completed', 'Disconnected'].includes(call_status)) { - this.set_indicator('red'); - title = __('Call Disconnected'); - } else { - this.set_indicator('blue'); - title = call_status; - } - this.dialog.set_title(title); + create_new_customer() { + // to avoid quick entry form + const new_customer = frappe.model.get_new_doc('Customer'); + new_customer.mobile_no = this.caller_number; + frappe.set_route('Form', new_customer.doctype, new_customer.name); } - update_call_log(call_log) { - this.call_log = call_log; - this.set_call_status(); - } - - close_modal() { - this.dialog.hide(); - delete erpnext.call_popup; - } - - call_disconnected(call_log) { - frappe.utils.play_sound('call-disconnect'); - this.update_call_log(call_log); - setTimeout(() => { - if (!this.dialog.get_value('call_summary')) { - this.close_modal(); - } - }, 30000); - } - - make_last_interaction_section() { - frappe.xcall('erpnext.crm.doctype.utils.get_last_interaction', { - 'contact': this.call_log.contact, - 'lead': this.call_log.lead - }).then(data => { - const comm_field = this.dialog.get_field('last_communication'); - if (data.last_communication) { - const comm = data.last_communication; - comm_field.set_value(comm.content); - } - - if (data.last_issue) { - const issue = data.last_issue; - const issue_field = this.dialog.get_field("last_issue"); - issue_field.set_value(issue.subject); - issue_field.$wrapper.append(` - - ${__('View all issues from {0}', [issue.customer])} - - `); - } - }); - } - - get_caller_name() { - let log = this.call_log; - return log.contact_name || log.lead_name; - } - - setup_listener() { - frappe.realtime.on(`call_${this.call_log.id}_disconnected`, call_log => { - this.call_disconnected(call_log); - // Remove call disconnect listener after the call is disconnected - frappe.realtime.off(`call_${this.call_log.id}_disconnected`); - }); + create_new_contact() { + // TODO: fix new_doc, it should accept child table values + const new_contact = frappe.model.get_new_doc('Contact'); + const phone_no = frappe.model.add_child(new_contact, 'Contact Phone', 'phone_nos'); + phone_no.phone = this.caller_number; + phone_no.is_primary_mobile_no = 1; + frappe.set_route('Form', new_contact.doctype, new_contact.name); } } $(document).on('app_ready', function () { frappe.realtime.on('show_call_popup', call_log => { - if (!erpnext.call_popup) { - erpnext.call_popup = new CallPopup(call_log); + let call_popup = erpnext.call_popup; + if (call_popup && call_log.name === call_popup.call_log.name) { + call_popup.update_call_log(call_log); + call_popup.dialog.show(); } else { - erpnext.call_popup.update_call_log(call_log); - erpnext.call_popup.dialog.show(); + erpnext.call_popup = new CallPopup(call_log); } }); }); + +window.CallPopup = CallPopup; diff --git a/erpnext/public/js/templates/call_link.html b/erpnext/public/js/templates/call_link.html index 08bdf142a7..071078c776 100644 --- a/erpnext/public/js/templates/call_link.html +++ b/erpnext/public/js/templates/call_link.html @@ -1,32 +1,31 @@
-
-
+
+
+ {{ type }} Call + {% if (duration) %} + • {{ frappe.format(duration, { fieldtype: "Duration" }) }} + {% endif %} + • {{ comment_when(creation) }} +
- - - {{ type }} Call - - - {{ frappe.format(duration, { fieldtype: "Duration" }) }} - - - {{ comment_when(creation) }} - - - - Details - {% if (show_call_button) { %} - Callback - {% } %} + + + + + +
-
+ + +
{% if (type === "Incoming") { %} Incoming call from {{ from }}, received by {{ to }} {% } else { %} Outgoing Call made by {{ from }} to {{ to }} {% } %} -
-
+
{% if (summary) { %} - {{ summary }} + {{ summary }} {% } else { %} {{ __("No Summary") }} {% } %} diff --git a/erpnext/public/less/call_popup.less b/erpnext/public/less/call_popup.less deleted file mode 100644 index 32e85ce16d..0000000000 --- a/erpnext/public/less/call_popup.less +++ /dev/null @@ -1,9 +0,0 @@ -.call-popup { - a:hover { - text-decoration: underline; - } - .for-description { - max-height: 250px; - overflow: scroll; - } -} \ No newline at end of file diff --git a/erpnext/public/scss/call_popup.scss b/erpnext/public/scss/call_popup.scss new file mode 100644 index 0000000000..95e31828c1 --- /dev/null +++ b/erpnext/public/scss/call_popup.scss @@ -0,0 +1,21 @@ +.call-popup { + a:hover { + text-decoration: underline; + } + .for-description { + max-height: 250px; + overflow: scroll; + } +} + +audio { + height: 40px; + width: 100%; + max-width: 500px; + background-color: var(--control-bg); + border-radius: var(--border-radius-sm); + &-webkit-media-controls-panel { + background: var(--control-bg); + } + outline: none; +} diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index c8e7e149c4..5f701f2aa6 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -20,11 +20,13 @@ from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds def validate_einvoice_fields(doc): einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable')) - invalid_doctype = doc.doctype not in ['Sales Invoice'] + invalid_doctype = doc.doctype != 'Sales Invoice' invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') + no_taxes_applied = len(doc.get('taxes', [])) == 0 - if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction: return + if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction or no_taxes_applied: + return if doc.docstatus == 0 and doc._action == 'save': if doc.irn: @@ -303,7 +305,7 @@ def validate_mandatory_fields(invoice): _('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'): + if invoice.gst_category != 'Overseas' and 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') @@ -444,6 +446,8 @@ class GSPConnector(): def get_credentials(self): if self.invoice: gstin = self.get_seller_gstin() + if not self.e_invoice_settings.enable: + frappe.throw(_("E-Invoicing is disabled. Please enable it from {} to generate e-invoices.").format(get_link_to_form("E Invoice Settings", "E Invoice Settings"))) credentials = next(d for d in self.e_invoice_settings.credentials if d.gstin == gstin) else: credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None @@ -817,4 +821,4 @@ def generate_eway_bill(doctype, docname, **kwargs): @frappe.whitelist() def cancel_eway_bill(doctype, docname, eway_bill, reason, remark): gsp_connector = GSPConnector(doctype, docname) - gsp_connector.cancel_eway_bill(eway_bill, reason, remark) \ No newline at end of file + gsp_connector.cancel_eway_bill(eway_bill, reason, remark) diff --git a/erpnext/shopping_cart/product_query.py b/erpnext/shopping_cart/product_query.py index da9e798327..36d446ed0f 100644 --- a/erpnext/shopping_cart/product_query.py +++ b/erpnext/shopping_cart/product_query.py @@ -23,8 +23,10 @@ class ProductQuery: self.cart_settings = frappe.get_doc("Shopping Cart Settings") self.page_length = self.settings.products_per_page or 20 self.fields = ['name', 'item_name', 'item_code', 'website_image', 'variant_of', 'has_variants', 'item_group', 'image', 'web_long_description', 'description', 'route'] - self.filters = [['show_in_website', '=', 1]] - self.or_filters = [] + self.filters = [] + self.or_filters = [['show_in_website', '=', 1]] + if not self.settings.get('hide_variants'): + self.or_filters.append(['show_variant_in_website', '=', 1]) def query(self, attributes=None, fields=None, search_term=None, start=0): """Summary @@ -73,7 +75,8 @@ class ProductQuery: for item in result: product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info') - item.formatted_price = product_info['price'].get('formatted_price') if product_info['price'] else None + if product_info: + item.formatted_price = product_info['price'].get('formatted_price') if product_info['price'] else None return result diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js index 9dbb64c343..95cb92b1b3 100644 --- a/erpnext/stock/dashboard/item_dashboard.js +++ b/erpnext/stock/dashboard/item_dashboard.js @@ -132,7 +132,7 @@ erpnext.stock.ItemDashboard = Class.extend({ var message = __("No Stock Available Currently"); this.content.find('.result').css('text-align', 'center'); - $(`
+ $(`
${message}
`).appendTo(this.result); } }, diff --git a/erpnext/telephony/doctype/call_log/call_log.js b/erpnext/telephony/doctype/call_log/call_log.js index 977f86da0d..e7afa0b7d0 100644 --- a/erpnext/telephony/doctype/call_log/call_log.js +++ b/erpnext/telephony/doctype/call_log/call_log.js @@ -2,7 +2,26 @@ // For license information, please see license.txt frappe.ui.form.on('Call Log', { - // refresh: function(frm) { - - // } + refresh: function(frm) { + frm.events.setup_recording_audio_control(frm); + const incoming_call = frm.doc.type == 'Incoming'; + frm.add_custom_button(incoming_call ? __('Callback'): __('Call Again'), () => { + const number = incoming_call ? frm.doc.from : frm.doc.to; + frappe.phone_call.handler(number, frm); + }); + }, + setup_recording_audio_control(frm) { + const recording_wrapper = frm.get_field('recording_html').$wrapper; + if (!frm.doc.recording_url || frm.doc.recording_url == 'null') { + recording_wrapper.empty(); + } else { + recording_wrapper.addClass('input-max-width'); + recording_wrapper.html(` + + `); + } + } }); diff --git a/erpnext/telephony/doctype/call_log/call_log.json b/erpnext/telephony/doctype/call_log/call_log.json index 1ecd884bbd..1d6c39edf6 100644 --- a/erpnext/telephony/doctype/call_log/call_log.json +++ b/erpnext/telephony/doctype/call_log/call_log.json @@ -5,6 +5,7 @@ "doctype": "DocType", "engine": "InnoDB", "field_order": [ + "call_details_section", "id", "from", "to", @@ -21,20 +22,9 @@ "section_break_11", "summary", "section_break_19", - "links", - "column_break_3", - "section_break_5" + "links" ], "fields": [ - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, - { - "fieldname": "section_break_5", - "fieldtype": "Section Break", - "label": "Call Details" - }, { "fieldname": "id", "fieldtype": "Data", @@ -75,6 +65,7 @@ { "fieldname": "recording_url", "fieldtype": "Data", + "hidden": 1, "label": "Recording URL" }, { @@ -112,13 +103,13 @@ }, { "fieldname": "summary", - "fieldtype": "Small Text", - "label": "Call Summary" + "fieldtype": "Small Text" }, { "fieldname": "section_break_11", "fieldtype": "Section Break", - "hide_border": 1 + "hide_border": 1, + "label": "Call Summary" }, { "fieldname": "start_time", @@ -138,12 +129,17 @@ "label": "Customer", "options": "Customer", "read_only": 1 + }, + { + "fieldname": "call_details_section", + "fieldtype": "Section Break", + "label": "Call Details" } ], "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2021-01-13 12:28:20.288985", + "modified": "2021-02-08 14:23:28.744844", "modified_by": "Administrator", "module": "Telephony", "name": "Call Log", diff --git a/erpnext/telephony/doctype/call_log/call_log.py b/erpnext/telephony/doctype/call_log/call_log.py index a277a5f956..4d553df08b 100644 --- a/erpnext/telephony/doctype/call_log/call_log.py +++ b/erpnext/telephony/doctype/call_log/call_log.py @@ -165,6 +165,8 @@ def get_linked_call_logs(doctype, docname): for log in logs: log.show_call_button = 0 timeline_contents.append({ + 'icon': 'call', + 'is_card': True, 'creation': log.creation, 'template': 'call_link', 'template_data': log diff --git a/erpnext/templates/pages/cart.html b/erpnext/templates/pages/cart.html index 2cabf5a9e0..ea343713a1 100644 --- a/erpnext/templates/pages/cart.html +++ b/erpnext/templates/pages/cart.html @@ -62,6 +62,9 @@
{% if doc.items %}
+ + {{ _("Continue Shopping") }} + {% if cart_settings.enable_checkout %}