Merge branch 'develop' into job-card-excess-transfer
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index 521432d..2005dac 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -425,7 +425,10 @@
status: ["!=", "Stopped"],
per_ordered: ["<", 100],
company: me.frm.doc.company
- }
+ },
+ allow_child_item_selection: true,
+ child_fielname: "items",
+ child_columns: ["item_code", "qty"]
})
}, __("Get Items From"));
diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json
index c7a5db5..12a09cd 100644
--- a/erpnext/buying/doctype/supplier/supplier.json
+++ b/erpnext/buying/doctype/supplier/supplier.json
@@ -433,12 +433,12 @@
"image_field": "image",
"links": [
{
- "group": "Item Group",
- "link_doctype": "Supplier Item Group",
- "link_fieldname": "supplier"
+ "group": "Allowed Items",
+ "link_doctype": "Party Specific Item",
+ "link_fieldname": "party"
}
],
- "modified": "2021-08-27 18:02:44.314077",
+ "modified": "2021-09-06 17:37:56.522233",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier",
diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json
deleted file mode 100644
index 1971458..0000000
--- a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json
+++ /dev/null
@@ -1,77 +0,0 @@
-{
- "actions": [],
- "creation": "2021-05-07 18:16:40.621421",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
- "supplier",
- "item_group"
- ],
- "fields": [
- {
- "fieldname": "supplier",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Supplier",
- "options": "Supplier",
- "reqd": 1
- },
- {
- "fieldname": "item_group",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Item Group",
- "options": "Item Group",
- "reqd": 1
- }
- ],
- "index_web_pages_for_search": 1,
- "links": [],
- "modified": "2021-05-19 13:48:16.742303",
- "modified_by": "Administrator",
- "module": "Buying",
- "name": "Supplier Item Group",
- "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": "Purchase User",
- "share": 1,
- "write": 1
- },
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Purchase Manager",
- "share": 1,
- "write": 1
- }
- ],
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py
deleted file mode 100644
index 6d71f7d..0000000
--- a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-
-import frappe
-from frappe import _
-from frappe.model.document import Document
-
-
-class SupplierItemGroup(Document):
- def validate(self):
- exists = frappe.db.exists({
- 'doctype': 'Supplier Item Group',
- 'supplier': self.supplier,
- 'item_group': self.item_group
- })
- if exists:
- frappe.throw(_("Item Group has already been linked to this supplier."))
diff --git a/erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py b/erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py
deleted file mode 100644
index 55ba85e..0000000
--- a/erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-from __future__ import unicode_literals
-
-# import frappe
-import unittest
-
-
-class TestSupplierItemGroup(unittest.TestCase):
- pass
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index ccd417b..9f28646 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -7,6 +7,7 @@
from collections import defaultdict
import frappe
+from frappe import scrub
from frappe.desk.reportview import get_filters_cond, get_match_cond
from frappe.utils import nowdate, unique
@@ -223,18 +224,29 @@
if not field in searchfields]
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
- if filters and isinstance(filters, dict) and filters.get('supplier'):
- item_group_list = frappe.get_all('Supplier Item Group',
- filters = {'supplier': filters.get('supplier')}, fields = ['item_group'])
+ if filters and isinstance(filters, dict):
+ if filters.get('customer') or filters.get('supplier'):
+ party = filters.get('customer') or filters.get('supplier')
+ item_rules_list = frappe.get_all('Party Specific Item',
+ filters = {'party': party}, fields = ['restrict_based_on', 'based_on_value'])
- item_groups = []
- for i in item_group_list:
- item_groups.append(i.item_group)
+ filters_dict = {}
+ for rule in item_rules_list:
+ if rule['restrict_based_on'] == 'Item':
+ rule['restrict_based_on'] = 'name'
+ filters_dict[rule.restrict_based_on] = []
- del filters['supplier']
+ for rule in item_rules_list:
+ filters_dict[rule.restrict_based_on].append(rule.based_on_value)
- if item_groups:
- filters['item_group'] = ['in', item_groups]
+ for filter in filters_dict:
+ filters[scrub(filter)] = ['in', filters_dict[filter]]
+
+ if filters.get('customer'):
+ del filters['customer']
+ else:
+ del filters['supplier']
+
description_cond = ''
if frappe.db.count('Item', cache=True) < 50000:
diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py
index ff2ed45..8e5952c 100644
--- a/erpnext/controllers/website_list_for_contact.py
+++ b/erpnext/controllers/website_list_for_contact.py
@@ -7,6 +7,7 @@
import frappe
from frappe import _
+from frappe.modules.utils import get_module_app
from frappe.utils import flt, has_common
from frappe.utils.user import is_website_user
@@ -21,8 +22,32 @@
"get_list": get_transaction_list
}
+def get_webform_list_context(module):
+ if get_module_app(module) != 'erpnext':
+ return
+ return {
+ "get_list": get_webform_transaction_list
+ }
-def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"):
+def get_webform_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"):
+ """ Get List of transactions for custom doctypes """
+ from frappe.www.list import get_list
+
+ if not filters:
+ filters = []
+
+ meta = frappe.get_meta(doctype)
+
+ for d in meta.fields:
+ if d.fieldtype == 'Link' and d.fieldname != 'amended_from':
+ allowed_docs = [d.name for d in get_transaction_list(doctype=d.options, custom=True)]
+ allowed_docs.append('')
+ filters.append((d.fieldname, 'in', allowed_docs))
+
+ return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=False,
+ fields=None, order_by="modified")
+
+def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified", custom=False):
user = frappe.session.user
ignore_permissions = False
@@ -46,7 +71,7 @@
filters.append(('customer', 'in', customers))
elif suppliers:
filters.append(('supplier', 'in', suppliers))
- else:
+ elif not custom:
return []
if doctype == 'Request for Quotation':
@@ -56,9 +81,16 @@
# Since customers and supplier do not have direct access to internal doctypes
ignore_permissions = True
+ if not customers and not suppliers and custom:
+ ignore_permissions = False
+ filters = []
+
transactions = get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length,
fields='name', ignore_permissions=ignore_permissions, order_by='modified desc')
+ if custom:
+ return transactions
+
return post_process(doctype, transactions)
def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length=20,
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 5b6e1ee..be05e35 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -62,6 +62,7 @@
# website
update_website_context = ["erpnext.shopping_cart.utils.update_website_context", "erpnext.education.doctype.education_settings.education_settings.update_website_context"]
my_account_context = "erpnext.shopping_cart.utils.update_my_account_context"
+webform_list_context = "erpnext.controllers.website_list_for_contact.get_webform_list_context"
calendars = ["Task", "Work Order", "Leave Application", "Sales Order", "Holiday List", "Course Schedule"]
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.js b/erpnext/hr/doctype/employee_advance/employee_advance.js
index fa4b06a..7d1c7cb 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.js
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.js
@@ -73,7 +73,7 @@
frm.trigger('make_return_entry');
}, __('Create'));
} else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")) {
- frm.add_custom_button(__("Deduction from salary"), function() {
+ frm.add_custom_button(__("Deduction from Salary"), function() {
frm.events.make_deduction_via_additional_salary(frm);
}, __('Create'));
}
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.json b/erpnext/hr/doctype/employee_advance/employee_advance.json
index ea25aa7..0475453 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.json
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.json
@@ -170,7 +170,7 @@
"default": "0",
"fieldname": "repay_unclaimed_amount_from_salary",
"fieldtype": "Check",
- "label": "Repay unclaimed amount from salary"
+ "label": "Repay Unclaimed Amount from Salary"
},
{
"depends_on": "eval:cur_frm.doc.employee",
@@ -200,10 +200,11 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 22:31:53.746659",
+ "modified": "2021-09-11 18:38:38.617478",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Advance",
+ "naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py
index 87d42d3..8d90bcc 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.py
@@ -172,7 +172,10 @@
@frappe.whitelist()
def create_return_through_additional_salary(doc):
import json
- doc = frappe._dict(json.loads(doc))
+
+ if isinstance(doc, str):
+ doc = frappe._dict(json.loads(doc))
+
additional_salary = frappe.new_doc('Additional Salary')
additional_salary.employee = doc.employee
additional_salary.currency = doc.currency
diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.py b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
index f8e5f53..c439d45 100644
--- a/erpnext/hr/doctype/employee_advance/test_employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
@@ -12,8 +12,11 @@
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.employee_advance.employee_advance import (
EmployeeAdvanceOverPayment,
+ create_return_through_additional_salary,
make_bank_entry,
)
+from erpnext.payroll.doctype.salary_component.test_salary_component import create_salary_component
+from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
class TestEmployeeAdvance(unittest.TestCase):
@@ -33,6 +36,46 @@
journal_entry1 = make_payment_entry(advance)
self.assertRaises(EmployeeAdvanceOverPayment, journal_entry1.submit)
+ def test_repay_unclaimed_amount_from_salary(self):
+ employee_name = make_employee("_T@employe.advance")
+ advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1})
+
+ args = {"type": "Deduction"}
+ create_salary_component("Advance Salary - Deduction", **args)
+ make_salary_structure("Test Additional Salary for Advance Return", "Monthly", employee=employee_name)
+
+ # additional salary for 700 first
+ advance.reload()
+ additional_salary = create_return_through_additional_salary(advance)
+ additional_salary.salary_component = "Advance Salary - Deduction"
+ additional_salary.payroll_date = nowdate()
+ additional_salary.amount = 700
+ additional_salary.insert()
+ additional_salary.submit()
+
+ advance.reload()
+ self.assertEqual(advance.return_amount, 700)
+
+ # additional salary for remaining 300
+ additional_salary = create_return_through_additional_salary(advance)
+ additional_salary.salary_component = "Advance Salary - Deduction"
+ additional_salary.payroll_date = nowdate()
+ additional_salary.amount = 300
+ additional_salary.insert()
+ additional_salary.submit()
+
+ advance.reload()
+ self.assertEqual(advance.return_amount, 1000)
+
+ # update advance return amount on additional salary cancellation
+ additional_salary.cancel()
+ advance.reload()
+ self.assertEqual(advance.return_amount, 700)
+
+ def tearDown(self):
+ frappe.db.rollback()
+
+
def make_payment_entry(advance):
journal_entry = frappe.get_doc(make_bank_entry("Employee Advance", advance.name))
journal_entry.cheque_no = "123123"
@@ -41,7 +84,7 @@
return journal_entry
-def make_employee_advance(employee_name):
+def make_employee_advance(employee_name, args=None):
doc = frappe.new_doc("Employee Advance")
doc.employee = employee_name
doc.company = "_Test company"
@@ -51,6 +94,10 @@
doc.advance_amount = 1000
doc.posting_date = nowdate()
doc.advance_account = "_Test Employee Advance - _TC"
+
+ if args:
+ doc.update(args)
+
doc.insert()
doc.submit()
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index deec644..b6f4cad 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -32,7 +32,10 @@
def update_employee(employee, details, date=None, cancel=False):
internal_work_history = {}
for item in details:
- fieldtype = frappe.get_meta("Employee").get_field(item.fieldname).fieldtype
+ field = frappe.get_meta("Employee").get_field(item.fieldname)
+ if not field:
+ continue
+ fieldtype = field.fieldtype
new_data = item.new if not cancel else item.current
if fieldtype == "Date" and new_data:
new_data = getdate(new_data)
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 15b196f..3bc40a1 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -304,5 +304,6 @@
erpnext.patches.v13_0.validate_options_for_data_field
erpnext.patches.v13_0.create_gst_payment_entry_fields
erpnext.patches.v14_0.delete_shopify_doctypes
+erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item
erpnext.patches.v13_0.update_dates_in_tax_withholding_category
erpnext.patches.v14_0.update_opportunity_currency_fields
diff --git a/erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py b/erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py
new file mode 100644
index 0000000..ba96fdd
--- /dev/null
+++ b/erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+
+
+def execute():
+ if frappe.db.table_exists('Supplier Item Group'):
+ frappe.reload_doc("selling", "doctype", "party_specific_item")
+ sig = frappe.db.get_all("Supplier Item Group", fields=["name", "supplier", "item_group"])
+ for item in sig:
+ psi = frappe.new_doc("Party Specific Item")
+ psi.party_type = "Supplier"
+ psi.party = item.supplier
+ psi.restrict_based_on = "Item Group"
+ psi.based_on_value = item.item_group
+ psi.insert()
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py
index ed10f2b..7c0a8ea 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py
@@ -14,12 +14,11 @@
class AdditionalSalary(Document):
def on_submit(self):
- if self.ref_doctype == "Employee Advance" and self.ref_docname:
- frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount)
-
+ self.update_return_amount_in_employee_advance()
self.update_employee_referral()
def on_cancel(self):
+ self.update_return_amount_in_employee_advance()
self.update_employee_referral(cancel=True)
def validate(self):
@@ -98,6 +97,17 @@
frappe.throw(_("Additional Salary for referral bonus can only be created against Employee Referral with status {0}").format(
frappe.bold("Accepted")))
+ def update_return_amount_in_employee_advance(self):
+ if self.ref_doctype == "Employee Advance" and self.ref_docname:
+ return_amount = frappe.db.get_value("Employee Advance", self.ref_docname, "return_amount")
+
+ if self.docstatus == 2:
+ return_amount -= self.amount
+ else:
+ return_amount += self.amount
+
+ frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", return_amount)
+
def update_employee_referral(self, cancel=False):
if self.ref_doctype == "Employee Referral":
status = "Unpaid" if cancel else "Paid"
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index 8c48345..d113e7e 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -487,7 +487,7 @@
self.calculate_component_amounts("deductions")
self.set_loan_repayment()
- self.set_component_amounts_based_on_payment_days()
+ self.set_precision_for_component_amounts()
self.set_net_pay()
def set_net_pay(self):
@@ -709,6 +709,17 @@
component_row.amount = amount
+ self.update_component_amount_based_on_payment_days(component_row)
+
+ def update_component_amount_based_on_payment_days(self, component_row):
+ joining_date, relieving_date = self.get_joining_and_relieving_dates()
+ component_row.amount = self.get_amount_based_on_payment_days(component_row, joining_date, relieving_date)[0]
+
+ def set_precision_for_component_amounts(self):
+ for component_type in ("earnings", "deductions"):
+ for component_row in self.get(component_type):
+ component_row.amount = flt(component_row.amount, component_row.precision("amount"))
+
def calculate_variable_based_on_taxable_salary(self, tax_component, payroll_period):
if not payroll_period:
frappe.msgprint(_("Start and end dates not in a valid Payroll Period, cannot calculate {0}.")
@@ -866,14 +877,7 @@
return total_tax_paid
def get_taxable_earnings(self, allow_tax_exemption=False, based_on_payment_days=0):
- joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
- ["date_of_joining", "relieving_date"])
-
- if not relieving_date:
- relieving_date = getdate(self.end_date)
-
- if not joining_date:
- frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name)))
+ joining_date, relieving_date = self.get_joining_and_relieving_dates()
taxable_earnings = 0
additional_income = 0
@@ -884,7 +888,10 @@
if based_on_payment_days:
amount, additional_amount = self.get_amount_based_on_payment_days(earning, joining_date, relieving_date)
else:
- amount, additional_amount = earning.amount, earning.additional_amount
+ if earning.additional_amount:
+ amount, additional_amount = earning.amount, earning.additional_amount
+ else:
+ amount, additional_amount = earning.default_amount, earning.additional_amount
if earning.is_tax_applicable:
if additional_amount:
@@ -1055,7 +1062,7 @@
total += amount
return total
- def set_component_amounts_based_on_payment_days(self):
+ def get_joining_and_relieving_dates(self):
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
["date_of_joining", "relieving_date"])
@@ -1065,9 +1072,7 @@
if not joining_date:
frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name)))
- for component_type in ("earnings", "deductions"):
- for d in self.get(component_type):
- d.amount = flt(self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0], d.precision("amount"))
+ return joining_date, relieving_date
def set_loan_repayment(self):
self.total_loan_repayment = 0
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 480daa2..bff36a4 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -17,6 +17,7 @@
getdate,
nowdate,
)
+from frappe.utils.make_random import get_random
import erpnext
from erpnext.accounts.utils import get_fiscal_year
@@ -134,6 +135,65 @@
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
+ def test_component_amount_dependent_on_another_payment_days_based_component(self):
+ from erpnext.hr.doctype.attendance.attendance import mark_attendance
+ from erpnext.payroll.doctype.salary_structure.test_salary_structure import (
+ create_salary_structure_assignment,
+ )
+
+ no_of_days = self.get_no_of_days()
+ # Payroll based on attendance
+ frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance")
+
+ salary_structure = make_salary_structure_for_payment_days_based_component_dependency()
+ employee = make_employee("test_payment_days_based_component@salary.com", company="_Test Company")
+
+ # base = 50000
+ create_salary_structure_assignment(employee, salary_structure.name, company="_Test Company", currency="INR")
+
+ # mark employee absent for a day since this case works fine if payment days are equal to working days
+ month_start_date = get_first_day(nowdate())
+ month_end_date = get_last_day(nowdate())
+
+ first_sunday = frappe.db.sql("""
+ select holiday_date from `tabHoliday`
+ where parent = 'Salary Slip Test Holiday List'
+ and holiday_date between %s and %s
+ order by holiday_date
+ """, (month_start_date, month_end_date))[0][0]
+
+ mark_attendance(employee, add_days(first_sunday, 1), 'Absent', ignore_validate=True) # counted as absent
+
+ # make salary slip and assert payment days
+ ss = make_salary_slip_for_payment_days_dependency_test("test_payment_days_based_component@salary.com", salary_structure.name)
+ self.assertEqual(ss.absent_days, 1)
+
+ days_in_month = no_of_days[0]
+ no_of_holidays = no_of_days[1]
+
+ self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 1)
+
+ ss.reload()
+ payment_days_based_comp_amount = 0
+ for component in ss.earnings:
+ if component.salary_component == "HRA - Payment Days":
+ payment_days_based_comp_amount = flt(component.amount, component.precision("amount"))
+ break
+
+ # check if the dependent component is calculated using the amount updated after payment days
+ actual_amount = 0
+ precision = 0
+ for component in ss.deductions:
+ if component.salary_component == "P - Employee Provident Fund":
+ precision = component.precision("amount")
+ actual_amount = flt(component.amount, precision)
+ break
+
+ expected_amount = flt((flt(ss.gross_pay) - payment_days_based_comp_amount) * 0.12, precision)
+
+ self.assertEqual(actual_amount, expected_amount)
+ frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
+
def test_salary_slip_with_holidays_included(self):
no_of_days = self.get_no_of_days()
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1)
@@ -864,3 +924,91 @@
holiday_list = holiday_list.name
return holiday_list
+
+def make_salary_structure_for_payment_days_based_component_dependency():
+ earnings = [
+ {
+ "salary_component": "Basic Salary - Payment Days",
+ "abbr": "P_BS",
+ "type": "Earning",
+ "formula": "base",
+ "amount_based_on_formula": 1
+ },
+ {
+ "salary_component": "HRA - Payment Days",
+ "abbr": "P_HRA",
+ "type": "Earning",
+ "depends_on_payment_days": 1,
+ "amount_based_on_formula": 1,
+ "formula": "base * 0.20"
+ }
+ ]
+
+ make_salary_component(earnings, False, company_list=["_Test Company"])
+
+ deductions = [
+ {
+ "salary_component": "P - Professional Tax",
+ "abbr": "P_PT",
+ "type": "Deduction",
+ "depends_on_payment_days": 1,
+ "amount": 200.00
+ },
+ {
+ "salary_component": "P - Employee Provident Fund",
+ "abbr": "P_EPF",
+ "type": "Deduction",
+ "exempted_from_income_tax": 1,
+ "amount_based_on_formula": 1,
+ "depends_on_payment_days": 0,
+ "formula": "(gross_pay - P_HRA) * 0.12"
+ }
+ ]
+
+ make_salary_component(deductions, False, company_list=["_Test Company"])
+
+ salary_structure = "Salary Structure with PF"
+ if frappe.db.exists("Salary Structure", salary_structure):
+ frappe.db.delete("Salary Structure", salary_structure)
+
+ details = {
+ "doctype": "Salary Structure",
+ "name": salary_structure,
+ "company": "_Test Company",
+ "payroll_frequency": "Monthly",
+ "payment_account": get_random("Account", filters={"account_currency": "INR"}),
+ "currency": "INR"
+ }
+
+ salary_structure_doc = frappe.get_doc(details)
+
+ for entry in earnings:
+ salary_structure_doc.append("earnings", entry)
+
+ for entry in deductions:
+ salary_structure_doc.append("deductions", entry)
+
+ salary_structure_doc.insert()
+ salary_structure_doc.submit()
+
+ return salary_structure_doc
+
+def make_salary_slip_for_payment_days_dependency_test(employee, salary_structure):
+ employee = frappe.db.get_value("Employee", {
+ "user_id": employee
+ },
+ ["name", "company", "employee_name"],
+ as_dict=True)
+
+ salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": employee})})
+
+ if not salary_slip_name:
+ salary_slip = make_salary_slip(salary_structure, employee=employee.name)
+ salary_slip.employee_name = employee.employee_name
+ salary_slip.payroll_frequency = "Monthly"
+ salary_slip.posting_date = nowdate()
+ salary_slip.insert()
+ else:
+ salary_slip = frappe.get_doc("Salary Slip", salary_slip_name)
+
+ return salary_slip
\ No newline at end of file
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index e0610eb..2a74d60 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -709,6 +709,9 @@
setters: opts.setters,
get_query: opts.get_query,
add_filters_group: 1,
+ allow_child_item_selection: opts.allow_child_item_selection,
+ child_fieldname: opts.child_fielname,
+ child_columns: opts.child_columns,
action: function(selections, args) {
let values = selections;
if(values.length === 0){
@@ -716,7 +719,7 @@
return;
}
opts.source_name = values;
- opts.setters = args;
+ opts.args = args;
d.dialog.hide();
_map();
},
diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json
index 5913b84..e811435 100644
--- a/erpnext/selling/doctype/customer/customer.json
+++ b/erpnext/selling/doctype/customer/customer.json
@@ -20,7 +20,6 @@
"tax_withholding_category",
"default_bank_account",
"lead_name",
- "prospect",
"opportunity_name",
"image",
"column_break0",
@@ -214,7 +213,8 @@
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Represents Company",
- "options": "Company"
+ "options": "Company",
+ "unique": 1
},
{
"depends_on": "represents_company",
@@ -498,14 +498,6 @@
"options": "Tax Withholding Category"
},
{
- "fieldname": "prospect",
- "fieldtype": "Link",
- "label": "Prospect",
- "no_copy": 1,
- "options": "Prospect",
- "print_hide": 1
- },
- {
"fieldname": "opportunity_name",
"fieldtype": "Link",
"label": "From Opportunity",
@@ -518,8 +510,14 @@
"idx": 363,
"image_field": "image",
"index_web_pages_for_search": 1,
- "links": [],
- "modified": "2021-08-25 18:56:09.929905",
+ "links": [
+ {
+ "group": "Allowed Items",
+ "link_doctype": "Party Specific Item",
+ "link_fieldname": "party"
+ }
+ ],
+ "modified": "2021-09-06 17:38:54.196663",
"modified_by": "Administrator",
"module": "Selling",
"name": "Customer",
diff --git a/erpnext/buying/doctype/supplier_item_group/__init__.py b/erpnext/selling/doctype/party_specific_item/__init__.py
similarity index 100%
rename from erpnext/buying/doctype/supplier_item_group/__init__.py
rename to erpnext/selling/doctype/party_specific_item/__init__.py
diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.js b/erpnext/selling/doctype/party_specific_item/party_specific_item.js
similarity index 78%
rename from erpnext/buying/doctype/supplier_item_group/supplier_item_group.js
rename to erpnext/selling/doctype/party_specific_item/party_specific_item.js
index f7da90d..077b936 100644
--- a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.js
+++ b/erpnext/selling/doctype/party_specific_item/party_specific_item.js
@@ -1,7 +1,7 @@
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Supplier Item Group', {
+frappe.ui.form.on('Party Specific Item', {
// refresh: function(frm) {
// }
diff --git a/erpnext/selling/doctype/party_specific_item/party_specific_item.json b/erpnext/selling/doctype/party_specific_item/party_specific_item.json
new file mode 100644
index 0000000..32b5d47
--- /dev/null
+++ b/erpnext/selling/doctype/party_specific_item/party_specific_item.json
@@ -0,0 +1,77 @@
+{
+ "actions": [],
+ "creation": "2021-08-27 19:28:07.559978",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "party_type",
+ "party",
+ "column_break_3",
+ "restrict_based_on",
+ "based_on_value"
+ ],
+ "fields": [
+ {
+ "fieldname": "party_type",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Party Type",
+ "options": "Customer\nSupplier",
+ "reqd": 1
+ },
+ {
+ "fieldname": "party",
+ "fieldtype": "Dynamic Link",
+ "in_list_view": 1,
+ "label": "Party Name",
+ "options": "party_type",
+ "reqd": 1
+ },
+ {
+ "fieldname": "restrict_based_on",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Restrict Items Based On",
+ "options": "Item\nItem Group\nBrand",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "based_on_value",
+ "fieldtype": "Dynamic Link",
+ "in_list_view": 1,
+ "label": "Based On Value",
+ "options": "restrict_based_on",
+ "reqd": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-09-14 13:27:58.612334",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Party Specific Item",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "party",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/party_specific_item/party_specific_item.py b/erpnext/selling/doctype/party_specific_item/party_specific_item.py
new file mode 100644
index 0000000..a408af5
--- /dev/null
+++ b/erpnext/selling/doctype/party_specific_item/party_specific_item.py
@@ -0,0 +1,19 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+
+
+class PartySpecificItem(Document):
+ def validate(self):
+ exists = frappe.db.exists({
+ 'doctype': 'Party Specific Item',
+ 'party_type': self.party_type,
+ 'party': self.party,
+ 'restrict_based_on': self.restrict_based_on,
+ 'based_on': self.based_on_value,
+ })
+ if exists:
+ frappe.throw(_("This item filter has already been applied for the {0}").format(self.party_type))
diff --git a/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py b/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py
new file mode 100644
index 0000000..874a364
--- /dev/null
+++ b/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py
@@ -0,0 +1,38 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import unittest
+
+import frappe
+
+from erpnext.controllers.queries import item_query
+
+test_dependencies = ['Item', 'Customer', 'Supplier']
+
+def create_party_specific_item(**args):
+ psi = frappe.new_doc("Party Specific Item")
+ psi.party_type = args.get('party_type')
+ psi.party = args.get('party')
+ psi.restrict_based_on = args.get('restrict_based_on')
+ psi.based_on_value = args.get('based_on_value')
+ psi.insert()
+
+class TestPartySpecificItem(unittest.TestCase):
+ def setUp(self):
+ self.customer = frappe.get_last_doc("Customer")
+ self.supplier = frappe.get_last_doc("Supplier")
+ self.item = frappe.get_last_doc("Item")
+
+ def test_item_query_for_customer(self):
+ create_party_specific_item(party_type='Customer', party=self.customer.name, restrict_based_on='Item', based_on_value=self.item.name)
+ filters = {'is_sales_item': 1, 'customer': self.customer.name}
+ items = item_query(doctype= 'Item', txt= '', searchfield= 'name', start= 0, page_len= 20,filters=filters, as_dict= False)
+ for item in items:
+ self.assertEqual(item[0], self.item.name)
+
+ def test_item_query_for_supplier(self):
+ create_party_specific_item(party_type='Supplier', party=self.supplier.name, restrict_based_on='Item Group', based_on_value=self.item.item_group)
+ filters = {'supplier': self.supplier.name, 'is_purchase_item': 1}
+ items = item_query(doctype= 'Item', txt= '', searchfield= 'name', start= 0, page_len= 20,filters=filters, as_dict= False)
+ for item in items:
+ self.assertEqual(item[2], self.item.item_group)
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 805c3d8..5c4d8b6 100644
--- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
+++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
@@ -73,7 +73,7 @@
`tabSales Order` so,
`tabSales Order Item` soi
LEFT JOIN `tabSales Invoice Item` sii
- ON sii.so_detail = soi.name
+ ON sii.so_detail = soi.name and sii.docstatus = 1
WHERE
soi.parent = so.name
and so.status not in ('Stopped', 'Closed', 'On Hold')
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 955ef5e..ddd4c4e 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -63,7 +63,7 @@
this.frm.set_query("item_code", "items", function() {
return {
query: "erpnext.controllers.queries.item_query",
- filters: {'is_sales_item': 1}
+ filters: {'is_sales_item': 1, 'customer': cur_frm.doc.customer}
}
});
}
@@ -247,7 +247,12 @@
var editable_price_list_rate = cint(frappe.defaults.get_default("editable_price_list_rate"));
if(df && editable_price_list_rate) {
- df.read_only = 0;
+ const parent_field = frappe.meta.get_parentfield(this.frm.doc.doctype, this.frm.doc.doctype + " Item");
+ if (!this.frm.fields_dict[parent_field]) return;
+
+ this.frm.fields_dict[parent_field].grid.update_docfield_property(
+ 'price_list_rate', 'read_only', 0
+ );
}
}
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index ddf3e66..b26c6a4 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -99,7 +99,7 @@
filter_engine = ProductFiltersBuilder(self.name)
context.field_filters = filter_engine.get_field_filters()
- context.attribute_filters = filter_engine.get_attribute_fitlers()
+ context.attribute_filters = filter_engine.get_attribute_filters()
context.update({
"parents": get_parent_item_groups(self.parent_item_group),
diff --git a/erpnext/shopping_cart/filters.py b/erpnext/shopping_cart/filters.py
index aaeff0f..4787ae5 100644
--- a/erpnext/shopping_cart/filters.py
+++ b/erpnext/shopping_cart/filters.py
@@ -4,7 +4,6 @@
from __future__ import unicode_literals
import frappe
-from frappe import _dict
class ProductFiltersBuilder:
@@ -57,37 +56,31 @@
return filter_data
- def get_attribute_fitlers(self):
+ def get_attribute_filters(self):
attributes = [row.attribute for row in self.doc.filter_attributes]
- attribute_docs = [
- frappe.get_doc('Item Attribute', attribute) for attribute in attributes
- ]
- valid_attributes = []
+ if not attributes:
+ return []
- 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:
- or_filters.extend([
- ["item_group", "=", self.item_group],
- ["Website Item Group", "item_group", "=", self.item_group]
- ])
+ result = frappe.db.sql(
+ """
+ select
+ distinct attribute, attribute_value
+ from
+ `tabItem Variant Attribute`
+ where
+ attribute in %(attributes)s
+ and attribute_value is not null
+ """,
+ {"attributes": attributes},
+ as_dict=1,
+ )
- if frappe.db.get_all("Item", filters, or_filters=or_filters, limit=1):
- selected_attributes.append(attr)
+ attribute_value_map = {}
+ for d in result:
+ attribute_value_map.setdefault(d.attribute, []).append(d.attribute_value)
- if selected_attributes:
- valid_attributes.append(
- _dict(
- item_attribute_values=selected_attributes,
- name=attr_doc.name
- )
- )
-
- return valid_attributes
+ out = []
+ for name, values in attribute_value_map.items():
+ out.append(frappe._dict(name=name, item_attribute_values=values))
+ return out
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 9eb4721..2569c04 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -6,10 +6,13 @@
from __future__ import unicode_literals
+import json
+
import frappe
from frappe import _, msgprint
from frappe.model.mapper import get_mapped_doc
from frappe.utils import cstr, flt, get_link_to_form, getdate, new_line_sep, nowdate
+from six import string_types
from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items
from erpnext.controllers.buying_controller import BuyingController
@@ -269,7 +272,10 @@
material_request.update_status(status)
@frappe.whitelist()
-def make_purchase_order(source_name, target_doc=None):
+def make_purchase_order(source_name, target_doc=None, args={}):
+
+ if isinstance(args, string_types):
+ args = json.loads(args)
def postprocess(source, target_doc):
if frappe.flags.args and frappe.flags.args.default_supplier:
@@ -284,7 +290,10 @@
set_missing_values(source, target_doc)
def select_item(d):
- return d.ordered_qty < d.stock_qty
+ filtered_items = args.get('filtered_children', [])
+ child_filter = d.name in filtered_items if filtered_items else True
+
+ return d.ordered_qty < d.stock_qty and child_filter
doclist = get_mapped_doc("Material Request", source_name, {
"Material Request": {
diff --git a/erpnext/tests/test_webform.py b/erpnext/tests/test_webform.py
new file mode 100644
index 0000000..19255db
--- /dev/null
+++ b/erpnext/tests/test_webform.py
@@ -0,0 +1,138 @@
+import unittest
+
+import frappe
+
+from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
+
+
+class TestWebsite(unittest.TestCase):
+ def test_permission_for_custom_doctype(self):
+ create_user('Supplier 1', 'supplier1@gmail.com')
+ create_user('Supplier 2', 'supplier2@gmail.com')
+ create_supplier_with_contact('Supplier1', 'All Supplier Groups', 'Supplier 1', 'supplier1@gmail.com')
+ create_supplier_with_contact('Supplier2', 'All Supplier Groups', 'Supplier 2', 'supplier2@gmail.com')
+ po1 = create_purchase_order(supplier='Supplier1')
+ po2 = create_purchase_order(supplier='Supplier2')
+
+ create_custom_doctype()
+ create_webform()
+ create_order_assignment(supplier='Supplier1', po = po1.name)
+ create_order_assignment(supplier='Supplier2', po = po2.name)
+
+ frappe.set_user("Administrator")
+ # checking if data consist of all order assignment of Supplier1 and Supplier2
+ self.assertTrue('Supplier1' and 'Supplier2' in [data.supplier for data in get_data()])
+
+ frappe.set_user("supplier1@gmail.com")
+ # checking if data only consist of order assignment of Supplier1
+ self.assertTrue('Supplier1' in [data.supplier for data in get_data()])
+ self.assertFalse([data.supplier for data in get_data() if data.supplier != 'Supplier1'])
+
+ frappe.set_user("supplier2@gmail.com")
+ # checking if data only consist of order assignment of Supplier2
+ self.assertTrue('Supplier2' in [data.supplier for data in get_data()])
+ self.assertFalse([data.supplier for data in get_data() if data.supplier != 'Supplier2'])
+
+ frappe.set_user("Administrator")
+
+def get_data():
+ webform_list_contexts = frappe.get_hooks('webform_list_context')
+ if webform_list_contexts:
+ context = frappe._dict(frappe.get_attr(webform_list_contexts[0])('Buying') or {})
+ kwargs = dict(doctype='Order Assignment', order_by = 'modified desc')
+ return context.get_list(**kwargs)
+
+def create_user(name, email):
+ frappe.get_doc({
+ 'doctype': 'User',
+ 'send_welcome_email': 0,
+ 'user_type': 'Website User',
+ 'first_name': name,
+ 'email': email,
+ 'roles': [{"doctype": "Has Role", "role": "Supplier"}]
+ }).insert(ignore_if_duplicate = True)
+
+def create_supplier_with_contact(name, group, contact_name, contact_email):
+ supplier = frappe.get_doc({
+ 'doctype': 'Supplier',
+ 'supplier_name': name,
+ 'supplier_group': group
+ }).insert(ignore_if_duplicate = True)
+
+ if not frappe.db.exists('Contact', contact_name+'-1-'+name):
+ new_contact = frappe.new_doc("Contact")
+ new_contact.first_name = contact_name
+ new_contact.is_primary_contact = True,
+ new_contact.append('links', {
+ "link_doctype": "Supplier",
+ "link_name": supplier.name
+ })
+ new_contact.append('email_ids', {
+ "email_id": contact_email,
+ "is_primary": 1
+ })
+
+ new_contact.insert(ignore_mandatory=True)
+
+def create_custom_doctype():
+ frappe.get_doc({
+ 'doctype': 'DocType',
+ 'name': 'Order Assignment',
+ 'module': 'Buying',
+ 'custom': 1,
+ 'autoname': 'field:po',
+ 'fields': [
+ {'label': 'PO', 'fieldname': 'po', 'fieldtype': 'Link', 'options': 'Purchase Order'},
+ {'label': 'Supplier', 'fieldname': 'supplier', 'fieldtype': 'Data', "fetch_from": "po.supplier"}
+ ],
+ 'permissions': [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "read": 1,
+ "role": "Supplier"
+ }
+ ]
+ }).insert(ignore_if_duplicate = True)
+
+def create_webform():
+ frappe.get_doc({
+ 'doctype': 'Web Form',
+ 'module': 'Buying',
+ 'title': 'SO Schedule',
+ 'route': 'so-schedule',
+ 'doc_type': 'Order Assignment',
+ 'web_form_fields': [
+ {
+ 'doctype': 'Web Form Field',
+ 'fieldname': 'po',
+ 'fieldtype': 'Link',
+ 'options': 'Purchase Order',
+ 'label': 'PO'
+ },
+ {
+ 'doctype': 'Web Form Field',
+ 'fieldname': 'supplier',
+ 'fieldtype': 'Data',
+ 'label': 'Supplier'
+ }
+ ]
+
+ }).insert(ignore_if_duplicate = True)
+
+def create_order_assignment(supplier, po):
+ frappe.get_doc({
+ 'doctype': 'Order Assignment',
+ 'po': po,
+ 'supplier': supplier,
+ }).insert(ignore_if_duplicate = True)
\ No newline at end of file
diff --git a/erpnext/www/all-products/index.html b/erpnext/www/all-products/index.html
index 7c18ecc..a7838ee 100644
--- a/erpnext/www/all-products/index.html
+++ b/erpnext/www/all-products/index.html
@@ -98,14 +98,14 @@
<div class="filter-options">
{% for attr_value in attribute.item_attribute_values %}
<div class="checkbox">
- <label data-value="{{ value }}">
+ <label>
<input type="checkbox"
class="product-filter attribute-filter"
- id="{{attr_value.name}}"
+ id="{{attr_value}}"
data-attribute-name="{{ attribute.name }}"
- data-attribute-value="{{ attr_value.attribute_value }}"
+ data-attribute-value="{{ attr_value }}"
{% if attr_value.checked %} checked {% endif %}>
- <span class="label-area">{{ attr_value.attribute_value }}</span>
+ <span class="label-area">{{ attr_value }}</span>
</label>
</div>
{% endfor %}
diff --git a/erpnext/www/all-products/index.py b/erpnext/www/all-products/index.py
index 335c104..df5258b 100644
--- a/erpnext/www/all-products/index.py
+++ b/erpnext/www/all-products/index.py
@@ -27,7 +27,7 @@
filter_engine = ProductFiltersBuilder()
context.field_filters = filter_engine.get_field_filters()
- context.attribute_filters = filter_engine.get_attribute_fitlers()
+ context.attribute_filters = filter_engine.get_attribute_filters()
context.product_settings = product_settings
context.body_class = "product-page"