Merge pull request #26132 from noahjacob/shipping_address_fix
fix: fetch preferred shipping address
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 60c614f..39d9a27 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
-__version__ = '13.5.1'
+__version__ = '13.5.2'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py
index dd346bc..2f86c6c 100644
--- a/erpnext/accounts/deferred_revenue.py
+++ b/erpnext/accounts/deferred_revenue.py
@@ -263,6 +263,9 @@
amount, base_amount = calculate_amount(doc, item, last_gl_entry,
total_days, total_booking_days, account_currency)
+ if not amount:
+ return
+
if via_journal_entry:
book_revenue_via_journal_entry(doc, credit_account, debit_account, against, amount,
base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process, submit_journal_entry)
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
index b2e8626..a8c07d6 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
@@ -49,7 +49,15 @@
doc: frm.doc,
btn: $(btn_primary),
method: "make_invoices",
- freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type])
+ freeze: 1,
+ freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type]),
+ callback: function(r) {
+ if (r.message.length == 1) {
+ frappe.msgprint(__("{0} Invoice created successfully.", [frm.doc.invoice_type]));
+ } else if (r.message.length < 50) {
+ frappe.msgprint(__("{0} Invoices created successfully.", [frm.doc.invoice_type]));
+ }
+ }
});
});
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
index 29dc96e..d76d909 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
@@ -216,7 +216,8 @@
return names
def publish(index, total, doctype):
- if total < 5: return
+ if total < 50:
+ return
frappe.publish_realtime(
"opening_invoice_creation_progress",
dict(
@@ -241,4 +242,3 @@
return accounts[0].name
-
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index d3ac3a6..439b1ed 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -7,6 +7,8 @@
frappe.ui.form.on('Payment Entry', {
onload: function(frm) {
+ frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice'];
+
if(frm.doc.__islocal) {
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
if (!frm.doc.paid_to) frm.set_value("paid_to_account_currency", null);
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 617b5b4..7562418 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -27,10 +27,6 @@
});
}
- company() {
- erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
- }
-
onload() {
super.onload();
@@ -569,5 +565,9 @@
frm: frm,
freeze_message: __("Creating Purchase Receipt ...")
})
- }
+ },
+
+ company: function(frm) {
+ erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
+ },
})
diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
index 60e675f..48bd730 100644
--- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
+++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
@@ -168,21 +168,24 @@
"label": _("Income"),
"fieldtype": "Currency",
"options": "currency",
- "width": 120
+ "width": 305
+
},
{
"fieldname": "expense",
"label": _("Expense"),
"fieldtype": "Currency",
"options": "currency",
- "width": 120
+ "width": 305
+
},
{
"fieldname": "gross_profit_loss",
"label": _("Gross Profit / Loss"),
"fieldtype": "Currency",
"options": "currency",
- "width": 120
+ "width": 307
+
}
]
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json
index 630a1dc..838a9ab 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.json
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.json
@@ -9,13 +9,14 @@
"supp_master_name",
"supplier_group",
"buying_price_list",
+ "maintain_same_rate_action",
+ "role_to_override_stop_action",
"column_break_3",
"po_required",
"pr_required",
"maintain_same_rate",
- "maintain_same_rate_action",
- "role_to_override_stop_action",
"allow_multiple_items",
+ "bill_for_rejected_quantity_in_purchase_invoice",
"subcontract",
"backflush_raw_materials_of_subcontract_based_on",
"column_break_11",
@@ -108,6 +109,13 @@
"fieldtype": "Link",
"label": "Role Allowed to Override Stop Action",
"options": "Role"
+ },
+ {
+ "default": "1",
+ "description": "If checked, Rejected Quantity will be included while making Purchase Invoice from Purchase Receipt.",
+ "fieldname": "bill_for_rejected_quantity_in_purchase_invoice",
+ "fieldtype": "Check",
+ "label": "Bill for Rejected Quantity in Purchase Invoice"
}
],
"icon": "fa fa-cog",
@@ -115,7 +123,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-04-04 20:01:44.087066",
+ "modified": "2021-06-23 19:40:00.120822",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index 0127eb8..a4ce84e 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -317,19 +317,21 @@
create_rfq_items(sq_doc, supplier, data)
def create_rfq_items(sq_doc, supplier, data):
- sq_doc.append('items', {
- "item_code": data.item_code,
- "item_name": data.item_name,
- "description": data.description,
- "qty": data.qty,
- "rate": data.rate,
- "conversion_factor": data.conversion_factor if data.conversion_factor else None,
- "supplier_part_no": frappe.db.get_value("Item Supplier", {'parent': data.item_code, 'supplier': supplier}, "supplier_part_no"),
- "warehouse": data.warehouse or '',
+ args = {}
+
+ for field in ['item_code', 'item_name', 'description', 'qty', 'rate', 'conversion_factor',
+ 'warehouse', 'material_request', 'material_request_item', 'stock_qty']:
+ args[field] = data.get(field)
+
+ args.update({
"request_for_quotation_item": data.name,
- "request_for_quotation": data.parent
+ "request_for_quotation": data.parent,
+ "supplier_part_no": frappe.db.get_value("Item Supplier",
+ {'parent': data.item_code, 'supplier': supplier}, "supplier_part_no")
})
+ sq_doc.append('items', args)
+
@frappe.whitelist()
def get_pdf(doctype, name, supplier):
doc = get_rfq_doc(doctype, name, supplier)
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 243939b..1c086e9 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -828,8 +828,14 @@
role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles():
- frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
- .format(item.item_code, item.idx, max_allowed_amt))
+ if self.doctype != "Purchase Invoice":
+ self.throw_overbill_exception(item, max_allowed_amt)
+ elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")):
+ self.throw_overbill_exception(item, max_allowed_amt)
+
+ def throw_overbill_exception(self, item, max_allowed_amt):
+ frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
+ .format(item.item_code, item.idx, max_allowed_amt))
def get_company_default(self, fieldname):
from erpnext.accounts.utils import get_company_default
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 7bd739a..2803193 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -19,7 +19,7 @@
fields = get_fields("Employee", ["name", "employee_name"])
return frappe.db.sql("""select {fields} from `tabEmployee`
- where status = 'Active'
+ where status in ('Active', 'Suspended')
and docstatus < 2
and ({key} like %(txt)s
or employee_name like %(txt)s)
@@ -315,7 +315,7 @@
return frappe.db.sql("""select {fields} from `tabProject`
where
`tabProject`.status not in ("Completed", "Cancelled")
- and {cond} {match_cond} {scond}
+ and {cond} {scond} {match_cond}
order by
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
idx desc,
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 2bb83ea..56da5b7 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -658,7 +658,13 @@
item.margin_type = None
item.margin_rate_or_amount = 0.0
- if item.margin_type and item.margin_rate_or_amount:
+ if not item.pricing_rules and flt(item.rate) > flt(item.price_list_rate):
+ item.margin_type = "Amount"
+ item.margin_rate_or_amount = flt(item.rate - item.price_list_rate,
+ item.precision("margin_rate_or_amount"))
+ item.rate_with_margin = item.rate
+
+ elif item.margin_type and item.margin_rate_or_amount:
margin_value = item.margin_rate_or_amount if item.margin_type == 'Amount' else flt(item.price_list_rate) * flt(item.margin_rate_or_amount) / 100
rate_with_margin = flt(item.price_list_rate) + flt(margin_value)
base_rate_with_margin = flt(rate_with_margin) * flt(self.doc.conversion_rate)
diff --git a/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js
index 3f5c95a..fe5707a 100644
--- a/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js
+++ b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js
@@ -22,10 +22,10 @@
get_chart_data: function (_columns, result) {
return {
data: {
- labels: result.map(d => d[0]),
+ labels: result.map(d => d.creation_date),
datasets: [{
name: "First Response Time",
- values: result.map(d => d[1])
+ values: result.map(d => d.first_response_time)
}]
},
type: "line",
@@ -35,8 +35,7 @@
hide_days: 0,
hide_seconds: 0
};
- value = frappe.utils.get_formatted_duration(d, duration_options);
- return value;
+ return frappe.utils.get_formatted_duration(d, duration_options);
}
}
}
diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json
index 5442ed5..d592a9c 100644
--- a/erpnext/hr/doctype/employee/employee.json
+++ b/erpnext/hr/doctype/employee/employee.json
@@ -207,7 +207,7 @@
"label": "Status",
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "Active\nInactive\nLeft",
+ "options": "Active\nInactive\nSuspended\nLeft",
"reqd": 1,
"search_index": 1
},
@@ -813,7 +813,7 @@
"idx": 24,
"image_field": "image",
"links": [],
- "modified": "2021-06-12 11:31:37.730760",
+ "modified": "2021-06-17 11:31:37.730760",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee",
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index bc56942..fa017d9 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
-from frappe.utils import getdate, validate_email_address, today, add_years, format_datetime, cstr
+from frappe.utils import getdate, validate_email_address, today, add_years, cstr
from frappe.model.naming import set_name_by_naming_series
from frappe import throw, _, scrub
from frappe.permissions import add_user_permission, remove_user_permission, \
@@ -12,7 +12,6 @@
from frappe.model.document import Document
from erpnext.utilities.transaction_base import delete_events
from frappe.utils.nestedset import NestedSet
-from erpnext.hr.doctype.job_offer.job_offer import get_staffing_plan_detail
class EmployeeUserDisabledError(frappe.ValidationError): pass
class EmployeeLeftValidationError(frappe.ValidationError): pass
@@ -37,7 +36,7 @@
def validate(self):
from erpnext.controllers.status_updater import validate_status
- validate_status(self.status, ["Active", "Inactive", "Left"])
+ validate_status(self.status, ["Active", "Inactive", "Suspended", "Left"])
self.employee = self.name
self.set_employee_name()
diff --git a/erpnext/hr/doctype/employee/employee_dashboard.py b/erpnext/hr/doctype/employee/employee_dashboard.py
index 285374d..e853bee 100644
--- a/erpnext/hr/doctype/employee/employee_dashboard.py
+++ b/erpnext/hr/doctype/employee/employee_dashboard.py
@@ -7,7 +7,8 @@
'heatmap_message': _('This is based on the attendance of this Employee'),
'fieldname': 'employee',
'non_standard_fieldnames': {
- 'Bank Account': 'party'
+ 'Bank Account': 'party',
+ 'Employee Grievance': 'raised_by'
},
'transactions': [
{
@@ -20,7 +21,7 @@
},
{
'label': _('Lifecycle'),
- 'items': ['Employee Transfer', 'Employee Promotion', 'Employee Separation']
+ 'items': ['Employee Transfer', 'Employee Promotion', 'Employee Separation', 'Employee Grievance']
},
{
'label': _('Shift'),
diff --git a/erpnext/hr/doctype/employee/employee_list.js b/erpnext/hr/doctype/employee/employee_list.js
index 6679e31..d37e149 100644
--- a/erpnext/hr/doctype/employee/employee_list.js
+++ b/erpnext/hr/doctype/employee/employee_list.js
@@ -3,7 +3,7 @@
filters: [["status","=", "Active"]],
get_indicator: function(doc) {
var indicator = [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status];
- indicator[1] = {"Active": "green", "Inactive": "red", "Left": "gray"}[doc.status];
+ indicator[1] = {"Active": "green", "Inactive": "red", "Left": "gray", "Suspended": "orange"}[doc.status];
return indicator;
}
};
diff --git a/erpnext/hr/doctype/employee_grievance/__init__.py b/erpnext/hr/doctype/employee_grievance/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hr/doctype/employee_grievance/__init__.py
diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance.js b/erpnext/hr/doctype/employee_grievance/employee_grievance.js
new file mode 100644
index 0000000..25c5bad
--- /dev/null
+++ b/erpnext/hr/doctype/employee_grievance/employee_grievance.js
@@ -0,0 +1,39 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Employee Grievance', {
+ setup: function(frm) {
+ frm.set_query('grievance_against_party', function() {
+ return {
+ filters: {
+ name: ['in', [
+ 'Company', 'Department', 'Employee Group', 'Employee Grade', 'Employee']
+ ]
+ }
+ };
+ });
+ frm.set_query('associated_document_type', function() {
+ let ignore_modules = ["Setup", "Core", "Integrations", "Automation", "Website",
+ "Utilities", "Event Streaming", "Social", "Chat", "Data Migration", "Printing", "Desk", "Custom"];
+ return {
+ filters: {
+ istable: 0,
+ issingle: 0,
+ module: ["Not In", ignore_modules]
+ }
+ };
+ });
+ },
+
+ grievance_against_party: function(frm) {
+ let filters = {};
+ if (frm.doc.grievance_against_party == 'Employee' && frm.doc.raised_by) {
+ filters.name = ["!=", frm.doc.raised_by];
+ }
+ frm.set_query('grievance_against', function() {
+ return {
+ filters: filters
+ };
+ });
+ },
+});
diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance.json b/erpnext/hr/doctype/employee_grievance/employee_grievance.json
new file mode 100644
index 0000000..5a91856
--- /dev/null
+++ b/erpnext/hr/doctype/employee_grievance/employee_grievance.json
@@ -0,0 +1,261 @@
+{
+ "actions": [],
+ "autoname": "HR-GRIEV-.YYYY.-.#####",
+ "creation": "2021-05-11 13:41:51.485295",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "subject",
+ "raised_by",
+ "employee_name",
+ "designation",
+ "column_break_3",
+ "date",
+ "status",
+ "reports_to",
+ "grievance_details_section",
+ "grievance_against_party",
+ "grievance_against",
+ "grievance_type",
+ "column_break_11",
+ "associated_document_type",
+ "associated_document",
+ "section_break_14",
+ "description",
+ "investigation_details_section",
+ "cause_of_grievance",
+ "resolution_details_section",
+ "resolved_by",
+ "resolution_date",
+ "employee_responsible",
+ "column_break_16",
+ "resolution_detail",
+ "amended_from"
+ ],
+ "fields": [
+ {
+ "fieldname": "grievance_type",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Grievance Type",
+ "options": "Grievance Type",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Date ",
+ "reqd": 1
+ },
+ {
+ "default": "Open",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Status",
+ "options": "Open\nInvestigated\nResolved\nInvalid",
+ "reqd": 1
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Text",
+ "label": "Description",
+ "reqd": 1
+ },
+ {
+ "fieldname": "cause_of_grievance",
+ "fieldtype": "Text",
+ "label": "Cause of Grievance",
+ "mandatory_depends_on": "eval: doc.status == \"Investigated\" || doc.status == \"Resolved\""
+ },
+ {
+ "fieldname": "resolution_details_section",
+ "fieldtype": "Section Break",
+ "label": "Resolution Details"
+ },
+ {
+ "fieldname": "resolved_by",
+ "fieldtype": "Link",
+ "label": "Resolved By",
+ "mandatory_depends_on": "eval: doc.status == \"Resolved\"",
+ "options": "User"
+ },
+ {
+ "fieldname": "employee_responsible",
+ "fieldtype": "Link",
+ "label": "Employee Responsible ",
+ "options": "Employee"
+ },
+ {
+ "fieldname": "resolution_detail",
+ "fieldtype": "Small Text",
+ "label": "Resolution Details",
+ "mandatory_depends_on": "eval: doc.status == \"Resolved\""
+ },
+ {
+ "fieldname": "column_break_16",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "resolution_date",
+ "fieldtype": "Date",
+ "label": "Resolution Date",
+ "mandatory_depends_on": "eval: doc.status == \"Resolved\""
+ },
+ {
+ "fieldname": "grievance_against",
+ "fieldtype": "Dynamic Link",
+ "label": "Grievance Against",
+ "options": "grievance_against_party",
+ "reqd": 1
+ },
+ {
+ "fieldname": "raised_by",
+ "fieldtype": "Link",
+ "label": "Raised By",
+ "options": "Employee",
+ "reqd": 1
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Employee Grievance",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fetch_from": "raised_by.designation",
+ "fieldname": "designation",
+ "fieldtype": "Link",
+ "label": "Designation",
+ "options": "Designation",
+ "read_only": 1
+ },
+ {
+ "fetch_from": "raised_by.reports_to",
+ "fieldname": "reports_to",
+ "fieldtype": "Link",
+ "label": "Reports To",
+ "options": "Employee",
+ "read_only": 1
+ },
+ {
+ "fieldname": "grievance_details_section",
+ "fieldtype": "Section Break",
+ "label": "Grievance Details"
+ },
+ {
+ "fieldname": "column_break_11",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_14",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "grievance_against_party",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Grievance Against Party",
+ "options": "DocType",
+ "reqd": 1
+ },
+ {
+ "fieldname": "associated_document_type",
+ "fieldtype": "Link",
+ "label": "Associated Document Type",
+ "options": "DocType"
+ },
+ {
+ "fieldname": "associated_document",
+ "fieldtype": "Dynamic Link",
+ "label": "Associated Document",
+ "options": "associated_document_type"
+ },
+ {
+ "fieldname": "investigation_details_section",
+ "fieldtype": "Section Break",
+ "label": "Investigation Details"
+ },
+ {
+ "fetch_from": "raised_by.employee_name",
+ "fieldname": "employee_name",
+ "fieldtype": "Data",
+ "label": "Employee Name",
+ "read_only": 1
+ },
+ {
+ "fieldname": "subject",
+ "fieldtype": "Data",
+ "label": "Subject",
+ "reqd": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2021-06-21 12:51:01.499486",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Employee Grievance",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "select": 1,
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR Manager",
+ "select": 1,
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR User",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "search_fields": "subject,raised_by,grievance_against_party",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "subject",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance.py b/erpnext/hr/doctype/employee_grievance/employee_grievance.py
new file mode 100644
index 0000000..503b5ea
--- /dev/null
+++ b/erpnext/hr/doctype/employee_grievance/employee_grievance.py
@@ -0,0 +1,15 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _, bold
+from frappe.model.document import Document
+
+class EmployeeGrievance(Document):
+ def on_submit(self):
+ if self.status not in ["Invalid", "Resolved"]:
+ frappe.throw(_("Only Employee Grievance with status {0} or {1} can be submitted").format(
+ bold("Invalid"),
+ bold("Resolved"))
+ )
+
diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js b/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js
new file mode 100644
index 0000000..fc08e21
--- /dev/null
+++ b/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js
@@ -0,0 +1,12 @@
+frappe.listview_settings["Employee Grievance"] = {
+ has_indicator_for_draft: 1,
+ get_indicator: function(doc) {
+ var colors = {
+ "Open": "red",
+ "Investigated": "orange",
+ "Resolved": "green",
+ "Invalid": "grey"
+ };
+ return [__(doc.status), colors[doc.status], "status,=," + doc.status];
+ }
+};
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py b/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py
new file mode 100644
index 0000000..a615b20
--- /dev/null
+++ b/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py
@@ -0,0 +1,51 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import frappe
+import unittest
+from frappe.utils import today
+from erpnext.hr.doctype.employee.test_employee import make_employee
+class TestEmployeeGrievance(unittest.TestCase):
+ def test_create_employee_grievance(self):
+ create_employee_grievance()
+
+def create_employee_grievance():
+ grievance_type = create_grievance_type()
+ emp_1 = make_employee("test_emp_grievance_@example.com", company="_Test Company")
+ emp_2 = make_employee("testculprit@example.com", company="_Test Company")
+
+ grievance = frappe.new_doc("Employee Grievance")
+ grievance.subject = "Test Employee Grievance"
+ grievance.raised_by = emp_1
+ grievance.date = today()
+ grievance.grievance_type = grievance_type
+ grievance.grievance_against_party = "Employee"
+ grievance.grievance_against = emp_2
+ grievance.description = "test descrip"
+
+ #set cause
+ grievance.cause_of_grievance = "test cause"
+
+ #resolution details
+ grievance.resolution_date = today()
+ grievance.resolution_detail = "test resolution detail"
+ grievance.resolved_by = "test_emp_grievance_@example.com"
+ grievance.employee_responsible = emp_2
+ grievance.status = "Resolved"
+
+ grievance.save()
+ grievance.submit()
+
+ return grievance
+
+
+def create_grievance_type():
+ if frappe.db.exists("Grievance Type", "Employee Abuse"):
+ return frappe.get_doc("Grievance Type", "Employee Abuse")
+ grievance_type = frappe.new_doc("Grievance Type")
+ grievance_type.name = "Employee Abuse"
+ grievance_type.description = "Test"
+ grievance_type.save()
+
+ return grievance_type.name
+
diff --git a/erpnext/hr/doctype/grievance_type/__init__.py b/erpnext/hr/doctype/grievance_type/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hr/doctype/grievance_type/__init__.py
diff --git a/erpnext/hr/doctype/grievance_type/grievance_type.js b/erpnext/hr/doctype/grievance_type/grievance_type.js
new file mode 100644
index 0000000..425f2fd
--- /dev/null
+++ b/erpnext/hr/doctype/grievance_type/grievance_type.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Grievance Type', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/hr/doctype/grievance_type/grievance_type.json b/erpnext/hr/doctype/grievance_type/grievance_type.json
new file mode 100644
index 0000000..1dce00a
--- /dev/null
+++ b/erpnext/hr/doctype/grievance_type/grievance_type.json
@@ -0,0 +1,70 @@
+{
+ "actions": [],
+ "autoname": "Prompt",
+ "creation": "2021-05-11 12:41:50.256071",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "section_break_5",
+ "description"
+ ],
+ "fields": [
+ {
+ "fieldname": "section_break_5",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Text",
+ "label": "Description"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-06-21 12:54:37.764712",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Grievance Type",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR User",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/grievance_type/grievance_type.py b/erpnext/hr/doctype/grievance_type/grievance_type.py
new file mode 100644
index 0000000..618cf0a
--- /dev/null
+++ b/erpnext/hr/doctype/grievance_type/grievance_type.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class GrievanceType(Document):
+ pass
diff --git a/erpnext/hr/doctype/grievance_type/test_grievance_type.py b/erpnext/hr/doctype/grievance_type/test_grievance_type.py
new file mode 100644
index 0000000..a02a34d
--- /dev/null
+++ b/erpnext/hr/doctype/grievance_type/test_grievance_type.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+import unittest
+
+class TestGrievanceType(unittest.TestCase):
+ pass
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant_list.js b/erpnext/hr/doctype/job_applicant/job_applicant_list.js
index 3b9141b..2ad0d59 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant_list.js
+++ b/erpnext/hr/doctype/job_applicant/job_applicant_list.js
@@ -2,7 +2,7 @@
// MIT License. See license.txt
frappe.listview_settings['Job Applicant'] = {
- add_fields: ["company", "designation", "job_applicant", "status"],
+ add_fields: ["status"],
get_indicator: function (doc) {
if (doc.status == "Accepted") {
return [__(doc.status), "green", "status,=," + doc.status];
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
index ae02c51..ae009ba 100644
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
@@ -110,6 +110,7 @@
"label": "Allocation"
},
{
+ "allow_on_submit": 1,
"bold": 1,
"fieldname": "new_leaves_allocated",
"fieldtype": "Float",
@@ -235,7 +236,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-04-14 15:28:26.335104",
+ "modified": "2021-06-03 15:28:26.335104",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Allocation",
@@ -277,4 +278,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"timeline_field": "employee"
-}
\ No newline at end of file
+}
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
index 11302ca..4757cd3 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
@@ -8,6 +8,7 @@
from frappe.model.document import Document
from erpnext.hr.utils import set_employee_name, get_leave_period
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import expire_allocation, create_leave_ledger_entry
+from erpnext.hr.doctype.leave_application.leave_application import get_approved_leaves_for_period
class OverlapError(frappe.ValidationError): pass
class BackDatedAllocationError(frappe.ValidationError): pass
@@ -55,6 +56,43 @@
if self.carry_forward:
self.set_carry_forwarded_leaves_in_previous_allocation(on_cancel=True)
+ def on_update_after_submit(self):
+ if self.has_value_changed("new_leaves_allocated"):
+ self.validate_against_leave_applications()
+ leaves_to_be_added = self.new_leaves_allocated - self.get_existing_leave_count()
+ args = {
+ "leaves": leaves_to_be_added,
+ "from_date": self.from_date,
+ "to_date": self.to_date,
+ "is_carry_forward": 0
+ }
+ create_leave_ledger_entry(self, args, True)
+
+ def get_existing_leave_count(self):
+ ledger_entries = frappe.get_all("Leave Ledger Entry",
+ filters={
+ "transaction_type": "Leave Allocation",
+ "transaction_name": self.name,
+ "employee": self.employee,
+ "company": self.company,
+ "leave_type": self.leave_type
+ },
+ pluck="leaves")
+ total_existing_leaves = 0
+ for entry in ledger_entries:
+ total_existing_leaves += entry
+
+ return total_existing_leaves
+
+ def validate_against_leave_applications(self):
+ leaves_taken = get_approved_leaves_for_period(self.employee, self.leave_type,
+ self.from_date, self.to_date)
+ if flt(leaves_taken) > flt(self.total_leaves_allocated):
+ if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"):
+ frappe.msgprint(_("Note: Total allocated leaves {0} shouldn't be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken))
+ else:
+ frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken), LessAllocationError)
+
def update_leave_policy_assignments_when_no_allocations_left(self):
allocations = frappe.db.get_list("Leave Allocation", filters = {
"docstatus": 1,
@@ -225,4 +263,4 @@
def validate_carry_forward(leave_type):
if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"):
- frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type))
\ No newline at end of file
+ frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type))
diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
index 6e7ae87..49dd701 100644
--- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
@@ -1,10 +1,10 @@
from __future__ import unicode_literals
import frappe
+import erpnext
import unittest
from frappe.utils import nowdate, add_months, getdate, add_days
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation, expire_allocation
-
class TestLeaveAllocation(unittest.TestCase):
@classmethod
def setUpClass(cls):
@@ -164,6 +164,53 @@
leave_allocation.cancel()
self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_allocation.name}))
+
+ def test_leave_addition_after_submit(self):
+ frappe.db.sql("delete from `tabLeave Allocation`")
+ frappe.db.sql("delete from `tabLeave Ledger Entry`")
+
+ leave_allocation = create_leave_allocation()
+ leave_allocation.submit()
+ self.assertTrue(leave_allocation.total_leaves_allocated, 15)
+ leave_allocation.new_leaves_allocated = 40
+ leave_allocation.submit()
+ self.assertTrue(leave_allocation.total_leaves_allocated, 40)
+
+ def test_leave_subtraction_after_submit(self):
+ frappe.db.sql("delete from `tabLeave Allocation`")
+ frappe.db.sql("delete from `tabLeave Ledger Entry`")
+
+ leave_allocation = create_leave_allocation()
+ leave_allocation.submit()
+ self.assertTrue(leave_allocation.total_leaves_allocated, 15)
+ leave_allocation.new_leaves_allocated = 10
+ leave_allocation.submit()
+ self.assertTrue(leave_allocation.total_leaves_allocated, 10)
+
+ def test_against_leave_application_validation_after_submit(self):
+ frappe.db.sql("delete from `tabLeave Allocation`")
+ frappe.db.sql("delete from `tabLeave Ledger Entry`")
+
+ leave_allocation = create_leave_allocation()
+ leave_allocation.submit()
+ self.assertTrue(leave_allocation.total_leaves_allocated, 15)
+ employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
+ leave_application = frappe.get_doc({
+ "doctype": 'Leave Application',
+ "employee": employee.name,
+ "leave_type": "_Test Leave Type",
+ "from_date": nowdate(),
+ "to_date": add_days(nowdate(), 10),
+ "company": erpnext.get_default_company() or "_Test Company",
+ "docstatus": 1,
+ "status": "Approved",
+ "leave_approver": 'test@example.com'
+ })
+ leave_application.submit()
+ leave_allocation.new_leaves_allocated = 8
+ leave_allocation.total_leaves_allocated = 8
+ self.assertRaises(frappe.ValidationError, leave_allocation.submit)
+
def create_leave_allocation(**args):
args = frappe._dict(args)
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.js b/erpnext/hr/doctype/staffing_plan/staffing_plan.js
index 04af232..228391b 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.js
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.js
@@ -103,4 +103,4 @@
})
frm.set_value('total_estimated_budget', estimated_budget);
}
-}
\ No newline at end of file
+};
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
index 533149a..e6c783a 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
@@ -41,7 +41,7 @@
detail.total_estimated_cost = 0
if detail.number_of_positions > 0:
- if detail.vacancies > 0 and detail.estimated_cost_per_position:
+ if detail.vacancies and detail.estimated_cost_per_position:
detail.total_estimated_cost = cint(detail.vacancies) * flt(detail.estimated_cost_per_position)
self.total_estimated_budget += detail.total_estimated_cost
@@ -76,12 +76,12 @@
if cint(staffing_plan_detail.vacancies) > cint(parent_plan_details[0].vacancies) or \
flt(staffing_plan_detail.total_estimated_cost) > flt(parent_plan_details[0].total_estimated_cost):
frappe.throw(_("You can only plan for upto {0} vacancies and budget {1} \
- for {2} as per staffing plan {3} for parent company {4}."
- .format(cint(parent_plan_details[0].vacancies),
+ for {2} as per staffing plan {3} for parent company {4}.").format(
+ cint(parent_plan_details[0].vacancies),
parent_plan_details[0].total_estimated_cost,
frappe.bold(staffing_plan_detail.designation),
parent_plan_details[0].name,
- parent_company)), ParentCompanyError)
+ parent_company), ParentCompanyError)
#Get vacanices already planned for all companies down the hierarchy of Parent Company
lft, rgt = frappe.get_cached_value('Company', parent_company, ["lft", "rgt"])
@@ -98,14 +98,14 @@
(flt(parent_plan_details[0].total_estimated_cost) < \
(flt(staffing_plan_detail.total_estimated_cost) + flt(all_sibling_details.total_estimated_cost))):
frappe.throw(_("{0} vacancies and {1} budget for {2} already planned for subsidiary companies of {3}. \
- You can only plan for upto {4} vacancies and and budget {5} as per staffing plan {6} for parent company {3}."
- .format(cint(all_sibling_details.vacancies),
+ You can only plan for upto {4} vacancies and and budget {5} as per staffing plan {6} for parent company {3}.").format(
+ cint(all_sibling_details.vacancies),
all_sibling_details.total_estimated_cost,
frappe.bold(staffing_plan_detail.designation),
parent_company,
cint(parent_plan_details[0].vacancies),
parent_plan_details[0].total_estimated_cost,
- parent_plan_details[0].name)))
+ parent_plan_details[0].name))
def validate_with_subsidiary_plans(self, staffing_plan_detail):
#Valdate this plan with all child company plan
@@ -121,11 +121,11 @@
cint(staffing_plan_detail.vacancies) < cint(children_details.vacancies) or \
flt(staffing_plan_detail.total_estimated_cost) < flt(children_details.total_estimated_cost):
frappe.throw(_("Subsidiary companies have already planned for {1} vacancies at a budget of {2}. \
- Staffing Plan for {0} should allocate more vacancies and budget for {3} than planned for its subsidiary companies"
- .format(self.company,
+ Staffing Plan for {0} should allocate more vacancies and budget for {3} than planned for its subsidiary companies").format(
+ self.company,
cint(children_details.vacancies),
children_details.total_estimated_cost,
- frappe.bold(staffing_plan_detail.designation))), SubsidiaryCompanyError)
+ frappe.bold(staffing_plan_detail.designation)), SubsidiaryCompanyError)
@frappe.whitelist()
def get_designation_counts(designation, company):
@@ -170,4 +170,4 @@
designation, from_date, to_date)
# Only a single staffing plan can be active for a designation on given date
- return staffing_plan if staffing_plan else None
\ No newline at end of file
+ return staffing_plan if staffing_plan else None
diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.json b/erpnext/hr/notification/training_scheduled/training_scheduled.json
index e49541e..f365003 100644
--- a/erpnext/hr/notification/training_scheduled/training_scheduled.json
+++ b/erpnext/hr/notification/training_scheduled/training_scheduled.json
@@ -11,8 +11,8 @@
"event": "Submit",
"idx": 0,
"is_standard": 1,
- "message": "<table class=\"panel-header\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div class=\"text-medium text-muted\">\n <span>{{_(\"Training Event:\")}} {{ doc.event_name }}</span>\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>\n\n<table class=\"panel-body\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div>\n {{ doc.introduction }}\n <ul class=\"list-unstyled\" style=\"line-height: 1.7\">\n <li>{{_(\"Event Location\")}}: <b>{{ doc.location }}</b></li>\n {% set start = frappe.utils.get_datetime(doc.start_time) %}\n {% set end = frappe.utils.get_datetime(doc.end_time) %}\n {% if start.date() == end.date() %}\n <li>{{_(\"Date\")}}: <b>{{ start.strftime(\"%A, %d %b %Y\") }}</b></li>\n <li>\n {{_(\"Timing\")}}: <b>{{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}</b>\n </li>\n {% else %}\n <li>{{_(\"Start Time\")}}: <b>{{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n <li>{{_(\"End Time\")}}: <b>{{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n {% endif %}\n <li>{{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>\n {% if doc.is_mandatory %}\n <li>Note: This Training Event is mandatory</li>\n {% endif %}\n </ul>\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>",
- "modified": "2021-05-24 16:29:13.165930",
+ "message": "<table class=\"panel-header\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div class=\"text-medium text-muted\">\n <span>{{_(\"Training Event:\")}} {{ doc.event_name }}</span>\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>\n\n<table class=\"panel-body\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div>\n {{ doc.introduction }}\n <ul class=\"list-unstyled\" style=\"line-height: 1.7\">\n <li>{{_(\"Event Location\")}}: <b>{{ doc.location }}</b></li>\n {% set start = frappe.utils.get_datetime(doc.start_time) %}\n {% set end = frappe.utils.get_datetime(doc.end_time) %}\n {% if start.date() == end.date() %}\n <li>{{_(\"Date\")}}: <b>{{ start.strftime(\"%A, %d %b %Y\") }}</b></li>\n <li>\n {{_(\"Timing\")}}: <b>{{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}</b>\n </li>\n {% else %}\n <li>\n {{_(\"Start Time\")}}: <b>{{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n <li>{{_(\"End Time\")}}: <b>{{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b></li>\n {% endif %}\n <li>{{ _(\"Event Link\") }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>\n {% if doc.is_mandatory %}\n <li>{{ _(\"Note: This Training Event is mandatory\") }}</li>\n {% endif %}\n </ul>\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>",
+ "modified": "2021-06-16 14:08:12.933367",
"modified_by": "Administrator",
"module": "HR",
"name": "Training Scheduled",
diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.md b/erpnext/hr/notification/training_scheduled/training_scheduled.md
index 418fd49..b9ba846 100644
--- a/erpnext/hr/notification/training_scheduled/training_scheduled.md
+++ b/erpnext/hr/notification/training_scheduled/training_scheduled.md
@@ -24,19 +24,19 @@
{% set start = frappe.utils.get_datetime(doc.start_time) %}
{% set end = frappe.utils.get_datetime(doc.end_time) %}
{% if start.date() == end.date() %}
- <li>{{_("Date")}}: <b>{{ start.strftime("%A, %d %b %Y") }}</b></li>
- <li>
- {{_("Timing")}}: <b>{{ start.strftime("%I:%M %p") + ' to ' + end.strftime("%I:%M %p") }}</b>
- </li>
+ <li>{{_("Date")}}: <b>{{ start.strftime("%A, %d %b %Y") }}</b></li>
+ <li>
+ {{_("Timing")}}: <b>{{ start.strftime("%I:%M %p") + ' to ' + end.strftime("%I:%M %p") }}</b>
+ </li>
{% else %}
- <li>{{_("Start Time")}}: <b>{{ start.strftime("%A, %d %b %Y at %I:%M %p") }}</b>
- </li>
- <li>{{_("End Time")}}: <b>{{ end.strftime("%A, %d %b %Y at %I:%M %p") }}</b>
- </li>
+ <li>
+ {{_("Start Time")}}: <b>{{ start.strftime("%A, %d %b %Y at %I:%M %p") }}</b>
+ </li>
+ <li>{{_("End Time")}}: <b>{{ end.strftime("%A, %d %b %Y at %I:%M %p") }}</b></li>
{% endif %}
- <li>{{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>
+ <li>{{ _("Event Link") }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>
{% if doc.is_mandatory %}
- <li>Note: This Training Event is mandatory</li>
+ <li>{{ _("Note: This Training Event is mandatory") }}</li>
{% endif %}
</ul>
</div>
@@ -44,4 +44,4 @@
<td width="15"></td>
</tr>
<tr height="10"></tr>
-</table>
\ No newline at end of file
+</table>
diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
index 4dd4570..b8953b3 100644
--- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
+++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
@@ -178,7 +178,7 @@
is_carry_forward, is_expired
FROM `tabLeave Ledger Entry`
WHERE employee=%(employee)s AND leave_type=%(leave_type)s
- AND docstatus=1 AND leaves>0
+ AND docstatus=1
AND (from_date between %(from_date)s AND %(to_date)s
OR to_date between %(from_date)s AND %(to_date)s
OR (from_date < %(from_date)s AND to_date > %(to_date)s))
diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json
index c5201c2..4500ba4 100644
--- a/erpnext/hr/workspace/hr/hr.json
+++ b/erpnext/hr/workspace/hr/hr.json
@@ -154,6 +154,24 @@
"type": "Link"
},
{
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Grievance Type",
+ "link_to": "Grievance Type",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Grievance",
+ "link_to": "Employee Grievance",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
"dependencies": "Employee",
"hidden": 0,
"is_query_report": 0,
@@ -823,7 +841,7 @@
"type": "Link"
}
],
- "modified": "2021-04-26 13:36:15.413819",
+ "modified": "2021-05-13 17:19:40.524444",
"modified_by": "Administrator",
"module": "HR",
"name": "HR",
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index 3e5a72d..8088d93 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -704,6 +704,8 @@
stop_work_order: function(frm, status) {
frappe.call({
method: "erpnext.manufacturing.doctype.work_order.work_order.stop_unstop",
+ freeze: true,
+ freeze_message: __("Updating Work Order status"),
args: {
work_order: frm.doc.name,
status: status
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 95cdc30..339e7f9 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -288,4 +288,5 @@
erpnext.patches.v13_0.update_timesheet_changes
erpnext.patches.v13_0.add_doctype_to_sla #14-06-2021
erpnext.patches.v13_0.set_training_event_attendance
+erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice
erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold
diff --git a/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py b/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py
new file mode 100644
index 0000000..7de9fa1
--- /dev/null
+++ b/erpnext/patches/v13_0/bill_for_rejected_quantity_in_purchase_invoice.py
@@ -0,0 +1,8 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.reload_doctype("Buying Settings")
+ buying_settings = frappe.get_single("Buying Settings")
+ buying_settings.bill_for_rejected_quantity_in_purchase_invoice = 0
+ buying_settings.save()
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.js b/erpnext/payroll/doctype/additional_salary/additional_salary.js
index d1ed91f..24ffce5 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.js
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.js
@@ -12,8 +12,12 @@
}
};
});
+ },
- frm.trigger('set_earning_component');
+ onload: function(frm) {
+ if (frm.doc.type) {
+ frm.trigger('set_component_query');
+ }
},
employee: function(frm) {
@@ -46,14 +50,19 @@
},
company: function(frm) {
- frm.trigger('set_earning_component');
+ frm.set_value("type", "");
+ frm.trigger('set_component_query');
},
- set_earning_component: function(frm) {
+ set_component_query: function(frm) {
if (!frm.doc.company) return;
+ let filters = {company: frm.doc.company};
+ if (frm.doc.type) {
+ filters.type = frm.doc.type;
+ }
frm.set_query("salary_component", function() {
return {
- filters: {type: ["in", ["earning", "deduction"]], company: frm.doc.company}
+ filters: filters
};
});
},
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index 7a70679..e71d81f 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -11,6 +11,7 @@
from erpnext.accounts.utils import get_fiscal_year
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
from frappe.desk.reportview import get_match_cond, get_filters_cond
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
class PayrollEntry(Document):
def onload(self):
@@ -41,7 +42,7 @@
emp_with_sal_slip.append(employee_details.employee)
if len(emp_with_sal_slip):
- frappe.throw(_("Salary Slip already exists for {0} ").format(comma_and(emp_with_sal_slip)))
+ frappe.throw(_("Salary Slip already exists for {0}").format(comma_and(emp_with_sal_slip)))
def on_cancel(self):
frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip`
@@ -211,7 +212,7 @@
return account_dict
def make_accrual_jv_entry(self):
- self.check_permission('write')
+ self.check_permission("write")
earnings = self.get_salary_component_total(component_type = "earnings") or {}
deductions = self.get_salary_component_total(component_type = "deductions") or {}
payroll_payable_account = self.payroll_payable_account
@@ -219,12 +220,13 @@
precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
if earnings or deductions:
- journal_entry = frappe.new_doc('Journal Entry')
- journal_entry.voucher_type = 'Journal Entry'
- journal_entry.user_remark = _('Accrual Journal Entry for salaries from {0} to {1}')\
+ journal_entry = frappe.new_doc("Journal Entry")
+ journal_entry.voucher_type = "Journal Entry"
+ journal_entry.user_remark = _("Accrual Journal Entry for salaries from {0} to {1}")\
.format(self.start_date, self.end_date)
journal_entry.company = self.company
journal_entry.posting_date = self.posting_date
+ accounting_dimensions = get_accounting_dimensions() or []
accounts = []
currencies = []
@@ -236,37 +238,34 @@
for acc_cc, amount in earnings.items():
exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
payable_amount += flt(amount, precision)
- accounts.append({
+ accounts.append(self.update_accounting_dimensions({
"account": acc_cc[0],
"debit_in_account_currency": flt(amt, precision),
"exchange_rate": flt(exchange_rate),
- "party_type": '',
"cost_center": acc_cc[1] or self.cost_center,
"project": self.project
- })
+ }, accounting_dimensions))
# Deductions
for acc_cc, amount in deductions.items():
exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
payable_amount -= flt(amount, precision)
- accounts.append({
+ accounts.append(self.update_accounting_dimensions({
"account": acc_cc[0],
"credit_in_account_currency": flt(amt, precision),
"exchange_rate": flt(exchange_rate),
"cost_center": acc_cc[1] or self.cost_center,
- "party_type": '',
"project": self.project
- })
+ }, accounting_dimensions))
# Payable amount
exchange_rate, payable_amt = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, payable_amount, company_currency, currencies)
- accounts.append({
+ accounts.append(self.update_accounting_dimensions({
"account": payroll_payable_account,
"credit_in_account_currency": flt(payable_amt, precision),
"exchange_rate": flt(exchange_rate),
- "party_type": '',
"cost_center": self.cost_center
- })
+ }, accounting_dimensions))
journal_entry.set("accounts", accounts)
if len(currencies) > 1:
@@ -286,6 +285,12 @@
return jv_name
+ def update_accounting_dimensions(self, row, accounting_dimensions):
+ for dimension in accounting_dimensions:
+ row.update({dimension: self.get(dimension)})
+
+ return row
+
def get_amount_and_exchange_rate_for_journal_entry(self, account, amount, company_currency, currencies):
conversion_rate = 1
exchange_rate = self.exchange_rate
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 9e7db97..ce88cc3 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -481,6 +481,7 @@
if not salary_structure:
salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip"
+
employee = frappe.db.get_value("Employee", {"user_id": user})
salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee=employee)
salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
index dce6b7a..e7d123c 100644
--- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
@@ -124,8 +124,8 @@
"doctype": "Salary Structure",
"name": salary_structure,
"company": company or erpnext.get_default_company(),
- "earnings": make_earning_salary_component(test_tax=test_tax, company_list=["_Test Company"]),
- "deductions": make_deduction_salary_component(test_tax=test_tax, company_list=["_Test Company"]),
+ "earnings": make_earning_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
+ "deductions": make_deduction_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
"payroll_frequency": payroll_frequency,
"payment_account": get_random("Account", filters={'account_currency': currency}),
"currency": currency
diff --git a/erpnext/portal/product_configurator/test_product_configurator.py b/erpnext/portal/product_configurator/test_product_configurator.py
index 0a5ebef..8aa0734 100644
--- a/erpnext/portal/product_configurator/test_product_configurator.py
+++ b/erpnext/portal/product_configurator/test_product_configurator.py
@@ -41,6 +41,30 @@
"show_variant_in_website": 1
}).insert()
+ def create_regular_web_item(self, name, item_group=None):
+ if not frappe.db.exists('Item', name):
+ doc = frappe.get_doc({
+ "description": name,
+ "item_code": name,
+ "item_name": name,
+ "doctype": "Item",
+ "is_stock_item": 1,
+ "item_group": item_group or "_Test Item Group",
+ "stock_uom": "_Test UOM",
+ "item_defaults": [{
+ "company": "_Test Company",
+ "default_warehouse": "_Test Warehouse - _TC",
+ "expense_account": "_Test Account Cost for Goods Sold - _TC",
+ "buying_cost_center": "_Test Cost Center - _TC",
+ "selling_cost_center": "_Test Cost Center - _TC",
+ "income_account": "Sales - _TC"
+ }],
+ "show_in_website": 1
+ }).insert()
+ else:
+ doc = frappe.get_doc("Item", name)
+ return doc
+
def test_product_list(self):
template_items = frappe.get_all('Item', {'show_in_website': 1})
variant_items = frappe.get_all('Item', {'show_variant_in_website': 1})
@@ -77,3 +101,42 @@
'Test Size': ['2XL']
})
self.assertEqual(len(items), 1)
+
+ def test_products_in_multiple_item_groups(self):
+ """Check if product is visible on multiple item group pages barring its own."""
+ from erpnext.shopping_cart.product_query import ProductQuery
+
+ if not frappe.db.exists("Item Group", {"name": "Tech Items"}):
+ item_group_doc = frappe.get_doc({
+ "doctype": "Item Group",
+ "item_group_name": "Tech Items",
+ "parent_item_group": "All Item Groups",
+ "show_in_website": 1
+ }).insert()
+ else:
+ item_group_doc = frappe.get_doc("Item Group", "Tech Items")
+
+ doc = self.create_regular_web_item("Portal Item", item_group="Tech Items")
+ if not frappe.db.exists("Website Item Group", {"parent": "Portal Item"}):
+ doc.append("website_item_groups", {
+ "item_group": "_Test Item Group Desktops"
+ })
+ doc.save()
+
+ # check if item is visible in its own Item Group's page
+ engine = ProductQuery()
+ items = engine.query({}, {"item_group": "Tech Items"}, None, start=0, item_group="Tech Items")
+ self.assertEqual(len(items), 1)
+ self.assertEqual(items[0].item_code, "Portal Item")
+
+ # check if item is visible in configured foreign Item Group's page
+ engine = ProductQuery()
+ items = engine.query({}, {"item_group": "_Test Item Group Desktops"}, None, start=0, item_group="_Test Item Group Desktops")
+ item_codes = [row.item_code for row in items]
+
+ self.assertIn(len(items), [2, 3])
+ self.assertIn("Portal Item", item_codes)
+
+ # teardown
+ doc.delete()
+ item_group_doc.delete()
\ No newline at end of file
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 210237f..8360337 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -888,9 +888,6 @@
}
- if (this.frm.doc.posting_date) var date = this.frm.doc.posting_date;
- else var date = this.frm.doc.transaction_date;
-
if (frappe.meta.get_docfield(this.frm.doctype, "shipping_address") &&
in_list(['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'], this.frm.doctype)) {
erpnext.utils.get_shipping_address(this.frm, function(){
diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss
index 9402cf9..5962859 100644
--- a/erpnext/public/scss/shopping_cart.scss
+++ b/erpnext/public/scss/shopping_cart.scss
@@ -467,11 +467,15 @@
.btn-change-address {
color: var(--blue-500);
- box-shadow: none;
- border: 1px solid var(--blue-500);
}
}
+.btn-new-address:hover, .btn-change-address:hover {
+ box-shadow: none;
+ color: var(--blue-500) !important;
+ border: 1px solid var(--blue-500);
+}
+
.modal .address-card {
.card-body {
padding: var(--padding-sm);
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index 11ebef7..5d33c1b 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -385,13 +385,16 @@
if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1:
frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.'))
- if abs(flt(value_details['TotInvVal']) + flt(value_details['Discount']) - flt(value_details['OthChrg']) - total_item_value) > 1:
+ if abs(
+ flt(value_details['TotInvVal']) + flt(value_details['Discount']) -
+ flt(value_details['OthChrg']) - flt(value_details['RndOffAmt']) -
+ total_item_value) > 1:
frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.'))
calculated_invoice_value = \
flt(value_details['AssVal']) + flt(value_details['CgstVal']) \
+ flt(value_details['SgstVal']) + flt(value_details['IgstVal']) \
- + flt(value_details['OthChrg']) - flt(value_details['Discount'])
+ + flt(value_details['OthChrg']) + flt(value_details['RndOffAmt']) - flt(value_details['Discount'])
if abs(flt(value_details['TotInvVal']) - calculated_invoice_value) > 1:
frappe.throw(_('Total Item Value + Taxes - Discount is not equal to the Invoice Grand Total. Please check taxes / discounts for any correction.'))
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 551f715..41f57a3 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -233,7 +233,7 @@
# Checks Sales Invoice
submit_rv = frappe.db.sql_list("""select t1.name
from `tabSales Invoice` t1,`tabSales Invoice Item` t2
- where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus = 1""",
+ where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus < 2""",
self.name)
if submit_rv:
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 9873710..974648d 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -1217,6 +1217,19 @@
# To test if the SO does NOT have a Blanket Order
self.assertEqual(so_doc.items[0].blanket_order, None)
+ def test_so_cancellation_when_si_drafted(self):
+ """
+ Test to check if Sales Order gets cancelled if Sales Invoice is in Draft state
+ Expected result: sales order should not get cancelled
+ """
+ so = make_sales_order()
+ so.submit()
+ si = make_sales_invoice(so.name)
+ si.save()
+
+ self.assertRaises(frappe.ValidationError, so.cancel)
+
+
def make_sales_order(**args):
so = frappe.new_doc("Sales Order")
diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
index f5feb95..8cb2446 100644
--- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
+++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
@@ -59,7 +59,7 @@
IF(so.status in ('Completed','To Bill'), 0, (SELECT delay_days)) as delay,
soi.qty, soi.delivered_qty,
(soi.qty - soi.delivered_qty) AS pending_qty,
- IFNULL(sii.qty, 0) as billed_qty,
+ IFNULL(SUM(sii.qty), 0) as billed_qty,
soi.base_amount as amount,
(soi.delivered_qty * soi.base_rate) as delivered_qty_amount,
(soi.billed_amt * IFNULL(so.conversion_rate, 1)) as billed_amount,
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index db480e0..1a83cb6 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -91,7 +91,7 @@
field_filters['item_group'] = self.name
engine = ProductQuery()
- context.items = engine.query(attribute_filters, field_filters, search, start)
+ context.items = engine.query(attribute_filters, field_filters, search, start, item_group=self.name)
filter_engine = ProductFiltersBuilder(self.name)
diff --git a/erpnext/shopping_cart/filters.py b/erpnext/shopping_cart/filters.py
index 6c63d87..7dfa09e 100644
--- a/erpnext/shopping_cart/filters.py
+++ b/erpnext/shopping_cart/filters.py
@@ -22,12 +22,15 @@
filter_data = []
for df in fields:
- filters = {}
+ filters, or_filters = {}, []
if df.fieldtype == "Link":
if self.item_group:
- filters['item_group'] = self.item_group
+ or_filters.extend([
+ ["item_group", "=", self.item_group],
+ ["Website Item Group", "item_group", "=", self.item_group]
+ ])
- values = frappe.get_all("Item", fields=[df.fieldname], filters=filters, distinct="True", pluck=df.fieldname)
+ values = frappe.get_all("Item", fields=[df.fieldname], filters=filters, or_filters=or_filters, distinct="True", pluck=df.fieldname)
else:
doctype = df.get_link_doctype()
@@ -44,7 +47,9 @@
values = [d.name for d in frappe.get_all(doctype, filters)]
# Remove None
- values = values.remove(None) if None in values else values
+ if None in values:
+ values.remove(None)
+
if values:
filter_data.append([df, values])
@@ -61,14 +66,18 @@
for attr_doc in attribute_docs:
selected_attributes = []
for attr in attr_doc.item_attribute_values:
+ or_filters = []
filters= [
["Item Variant Attribute", "attribute", "=", attr.parent],
["Item Variant Attribute", "attribute_value", "=", attr.attribute_value]
]
if self.item_group:
- filters.append(["item_group", "=", self.item_group])
+ or_filters.extend([
+ ["item_group", "=", self.item_group],
+ ["Website Item Group", "item_group", "=", self.item_group]
+ ])
- if frappe.db.get_all("Item", filters, limit=1):
+ if frappe.db.get_all("Item", filters, or_filters=or_filters, limit=1):
selected_attributes.append(attr)
if selected_attributes:
diff --git a/erpnext/shopping_cart/product_query.py b/erpnext/shopping_cart/product_query.py
index 36d446e..3eab4ff 100644
--- a/erpnext/shopping_cart/product_query.py
+++ b/erpnext/shopping_cart/product_query.py
@@ -22,13 +22,14 @@
self.settings = frappe.get_doc("Products Settings")
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.fields = ['name', 'item_name', 'item_code', 'website_image', 'variant_of', 'has_variants',
+ 'item_group', 'image', 'web_long_description', 'description', 'route', 'weightage']
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):
+ def query(self, attributes=None, fields=None, search_term=None, start=0, item_group=None):
"""Summary
Args:
@@ -44,6 +45,15 @@
if search_term: self.build_search_filters(search_term)
result = []
+ website_item_groups = []
+
+ # if from item group page consider website item group table
+ if item_group:
+ website_item_groups = frappe.db.get_all(
+ "Item",
+ fields=self.fields + ["`tabWebsite Item Group`.parent as wig_parent"],
+ filters=[["Website Item Group", "item_group", "=", item_group]]
+ )
if attributes:
all_items = []
@@ -61,22 +71,38 @@
],
or_filters=self.or_filters,
start=start,
- limit=self.page_length
+ limit=self.page_length,
+ order_by="weightage desc"
)
items_dict = {item.name: item for item in items}
- # TODO: Replace Variants by their parent templates
all_items.append(set(items_dict.keys()))
result = [items_dict.get(item) for item in list(set.intersection(*all_items))]
else:
- result = frappe.get_all("Item", fields=self.fields, filters=self.filters, or_filters=self.or_filters, start=start, limit=self.page_length)
+ result = frappe.get_all(
+ "Item",
+ fields=self.fields,
+ filters=self.filters,
+ or_filters=self.or_filters,
+ start=start,
+ limit=self.page_length
+ )
+
+ # Combine results having context of website item groups into item results
+ if item_group and website_item_groups:
+ items_list = {row.name for row in result}
+ for row in website_item_groups:
+ if row.wig_parent not in items_list:
+ result.append(row)
+
+ result = sorted(result, key=lambda x: x.get("weightage"), reverse=True)
for item in result:
product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info')
if product_info:
- item.formatted_price = product_info['price'].get('formatted_price') if product_info['price'] else None
+ item.formatted_price = (product_info.get('price') or {}).get('formatted_price')
return result
@@ -90,7 +116,16 @@
if not values:
continue
- if isinstance(values, list):
+ # handle multiselect fields in filter addition
+ meta = frappe.get_meta('Item', cached=True)
+ df = meta.get_field(field)
+ if df.fieldtype == 'Table MultiSelect':
+ child_doctype = df.options
+ child_meta = frappe.get_meta(child_doctype, cached=True)
+ fields = child_meta.get("fields")
+ if fields:
+ self.filters.append([child_doctype, fields[0].fieldname, 'IN', values])
+ elif isinstance(values, list):
# If value is a list use `IN` query
self.filters.append([field, 'IN', values])
else:
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 6585e1c..5f53be0 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -101,7 +101,8 @@
}
if (frm.doc.docstatus == 1 && frm.doc.status != 'Stopped') {
- if (flt(frm.doc.per_ordered, 2) < 100) {
+ let precision = frappe.defaults.get_default("float_precision");
+ if (flt(frm.doc.per_ordered, precision) < 100) {
let add_create_pick_list_button = () => {
frm.add_custom_button(__('Pick List'),
() => frm.events.create_pick_list(frm), __('Create'));
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index b8580f9..e488b69 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -581,7 +581,6 @@
@frappe.whitelist()
def make_purchase_invoice(source_name, target_doc=None):
- from frappe.model.mapper import get_mapped_doc
from erpnext.accounts.party import get_payment_terms_template
doc = frappe.get_doc('Purchase Receipt', source_name)
@@ -601,11 +600,16 @@
def update_item(source_doc, target_doc, source_parent):
target_doc.qty, returned_qty = get_pending_qty(source_doc)
+ if frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"):
+ target_doc.rejected_qty = 0
target_doc.stock_qty = flt(target_doc.qty) * flt(target_doc.conversion_factor, target_doc.precision("conversion_factor"))
returned_qty_map[source_doc.name] = returned_qty
def get_pending_qty(item_row):
- pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
+ qty = item_row.qty
+ if frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"):
+ qty = item_row.received_qty
+ pending_qty = qty - invoiced_qty_map.get(item_row.name, 0)
returned_qty = flt(returned_qty_map.get(item_row.name, 0))
if returned_qty:
if returned_qty >= pending_qty:
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 95096d7..99abf3a 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -421,11 +421,18 @@
self.assertEqual(return_pr_2.items[0].qty, -3)
# Make PI against unreturned amount
+ buying_settings = frappe.get_single("Buying Settings")
+ buying_settings.bill_for_rejected_quantity_in_purchase_invoice = 0
+ buying_settings.save()
+
pi = make_purchase_invoice(pr.name)
pi.submit()
self.assertEqual(pi.items[0].qty, 3)
+ buying_settings.bill_for_rejected_quantity_in_purchase_invoice = 1
+ buying_settings.save()
+
pr.load_from_db()
# PR should be completed on billing all unreturned amount
self.assertEqual(pr.items[0].billed_amt, 150)
@@ -767,8 +774,8 @@
pr1.items[0].purchase_receipt_item = pr.items[0].name
pr1.submit()
- pi = make_purchase_invoice(pr.name)
- self.assertEqual(pi.items[0].qty, 3)
+ pi1 = make_purchase_invoice(pr.name)
+ self.assertEqual(pi1.items[0].qty, 3)
pr1.cancel()
pr.reload()
diff --git a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js
index 576e0b7..18691fe 100644
--- a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js
+++ b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js
@@ -22,10 +22,10 @@
get_chart_data: function(_columns, result) {
return {
data: {
- labels: result.map(d => d[0]),
+ labels: result.map(d => d.creation_date),
datasets: [{
name: 'First Response Time',
- values: result.map(d => d[1])
+ values: result.map(d => d.first_response_time)
}]
},
type: "line",
@@ -35,8 +35,7 @@
hide_days: 0,
hide_seconds: 0
};
- value = frappe.utils.get_formatted_duration(d, duration_options);
- return value;
+ return frappe.utils.get_formatted_duration(d, duration_options);
}
}
}
diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html
index 84a9430..4482bc1 100644
--- a/erpnext/templates/includes/cart/cart_address.html
+++ b/erpnext/templates/includes/cart/cart_address.html
@@ -99,6 +99,7 @@
fieldname: 'country',
fieldtype: 'Link',
options: 'Country',
+ only_select: true,
reqd: 1
},
{
diff --git a/erpnext/templates/includes/transaction_row.html b/erpnext/templates/includes/transaction_row.html
index 3834131..3cfb8d8 100644
--- a/erpnext/templates/includes/transaction_row.html
+++ b/erpnext/templates/includes/transaction_row.html
@@ -13,9 +13,11 @@
{{ doc.items_preview }}
</div>
</div>
- <div class="col-sm-3 text-right bold">
- {{ doc.get_formatted("grand_total") }}
- </div>
+ {% if doc.get('grand_total') %}
+ <div class="col-sm-3 text-right bold">
+ {{ doc.get_formatted("grand_total") }}
+ </div>
+ {% endif %}
</div>
<a class="transaction-item-link" href="/{{ pathname }}/{{ doc.name }}">Link</a>
</div>