Merge branch 'develop' into job-card-excess-transfer
This commit is contained in:
commit
de9f78350b
@ -425,7 +425,10 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
|||||||
status: ["!=", "Stopped"],
|
status: ["!=", "Stopped"],
|
||||||
per_ordered: ["<", 100],
|
per_ordered: ["<", 100],
|
||||||
company: me.frm.doc.company
|
company: me.frm.doc.company
|
||||||
}
|
},
|
||||||
|
allow_child_item_selection: true,
|
||||||
|
child_fielname: "items",
|
||||||
|
child_columns: ["item_code", "qty"]
|
||||||
})
|
})
|
||||||
}, __("Get Items From"));
|
}, __("Get Items From"));
|
||||||
|
|
||||||
|
@ -433,12 +433,12 @@
|
|||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [
|
"links": [
|
||||||
{
|
{
|
||||||
"group": "Item Group",
|
"group": "Allowed Items",
|
||||||
"link_doctype": "Supplier Item Group",
|
"link_doctype": "Party Specific Item",
|
||||||
"link_fieldname": "supplier"
|
"link_fieldname": "party"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2021-08-27 18:02:44.314077",
|
"modified": "2021-09-06 17:37:56.522233",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Supplier",
|
"name": "Supplier",
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -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."))
|
|
@ -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
|
|
@ -7,6 +7,7 @@ import json
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import scrub
|
||||||
from frappe.desk.reportview import get_filters_cond, get_match_cond
|
from frappe.desk.reportview import get_filters_cond, get_match_cond
|
||||||
from frappe.utils import nowdate, unique
|
from frappe.utils import nowdate, unique
|
||||||
|
|
||||||
@ -223,18 +224,29 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
|||||||
if not field in searchfields]
|
if not field in searchfields]
|
||||||
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
|
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
|
||||||
|
|
||||||
if filters and isinstance(filters, dict) and filters.get('supplier'):
|
if filters and isinstance(filters, dict):
|
||||||
item_group_list = frappe.get_all('Supplier Item Group',
|
if filters.get('customer') or filters.get('supplier'):
|
||||||
filters = {'supplier': filters.get('supplier')}, fields = ['item_group'])
|
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 = []
|
filters_dict = {}
|
||||||
for i in item_group_list:
|
for rule in item_rules_list:
|
||||||
item_groups.append(i.item_group)
|
if rule['restrict_based_on'] == 'Item':
|
||||||
|
rule['restrict_based_on'] = 'name'
|
||||||
|
filters_dict[rule.restrict_based_on] = []
|
||||||
|
|
||||||
|
for rule in item_rules_list:
|
||||||
|
filters_dict[rule.restrict_based_on].append(rule.based_on_value)
|
||||||
|
|
||||||
|
for filter in filters_dict:
|
||||||
|
filters[scrub(filter)] = ['in', filters_dict[filter]]
|
||||||
|
|
||||||
|
if filters.get('customer'):
|
||||||
|
del filters['customer']
|
||||||
|
else:
|
||||||
del filters['supplier']
|
del filters['supplier']
|
||||||
|
|
||||||
if item_groups:
|
|
||||||
filters['item_group'] = ['in', item_groups]
|
|
||||||
|
|
||||||
description_cond = ''
|
description_cond = ''
|
||||||
if frappe.db.count('Item', cache=True) < 50000:
|
if frappe.db.count('Item', cache=True) < 50000:
|
||||||
|
@ -7,6 +7,7 @@ import json
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.modules.utils import get_module_app
|
||||||
from frappe.utils import flt, has_common
|
from frappe.utils import flt, has_common
|
||||||
from frappe.utils.user import is_website_user
|
from frappe.utils.user import is_website_user
|
||||||
|
|
||||||
@ -21,8 +22,32 @@ def get_list_context(context=None):
|
|||||||
"get_list": get_transaction_list
|
"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
|
user = frappe.session.user
|
||||||
ignore_permissions = False
|
ignore_permissions = False
|
||||||
|
|
||||||
@ -46,7 +71,7 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p
|
|||||||
filters.append(('customer', 'in', customers))
|
filters.append(('customer', 'in', customers))
|
||||||
elif suppliers:
|
elif suppliers:
|
||||||
filters.append(('supplier', 'in', suppliers))
|
filters.append(('supplier', 'in', suppliers))
|
||||||
else:
|
elif not custom:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
if doctype == 'Request for Quotation':
|
if doctype == 'Request for Quotation':
|
||||||
@ -56,9 +81,16 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p
|
|||||||
# Since customers and supplier do not have direct access to internal doctypes
|
# Since customers and supplier do not have direct access to internal doctypes
|
||||||
ignore_permissions = True
|
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,
|
transactions = get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length,
|
||||||
fields='name', ignore_permissions=ignore_permissions, order_by='modified desc')
|
fields='name', ignore_permissions=ignore_permissions, order_by='modified desc')
|
||||||
|
|
||||||
|
if custom:
|
||||||
|
return transactions
|
||||||
|
|
||||||
return post_process(doctype, transactions)
|
return post_process(doctype, transactions)
|
||||||
|
|
||||||
def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length=20,
|
def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length=20,
|
||||||
|
@ -62,6 +62,7 @@ treeviews = ['Account', 'Cost Center', 'Warehouse', 'Item Group', 'Customer Grou
|
|||||||
# website
|
# website
|
||||||
update_website_context = ["erpnext.shopping_cart.utils.update_website_context", "erpnext.education.doctype.education_settings.education_settings.update_website_context"]
|
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"
|
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"]
|
calendars = ["Task", "Work Order", "Leave Application", "Sales Order", "Holiday List", "Course Schedule"]
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ frappe.ui.form.on('Employee Advance', {
|
|||||||
frm.trigger('make_return_entry');
|
frm.trigger('make_return_entry');
|
||||||
}, __('Create'));
|
}, __('Create'));
|
||||||
} else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")) {
|
} 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);
|
frm.events.make_deduction_via_additional_salary(frm);
|
||||||
}, __('Create'));
|
}, __('Create'));
|
||||||
}
|
}
|
||||||
|
@ -170,7 +170,7 @@
|
|||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "repay_unclaimed_amount_from_salary",
|
"fieldname": "repay_unclaimed_amount_from_salary",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Repay unclaimed amount from salary"
|
"label": "Repay Unclaimed Amount from Salary"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:cur_frm.doc.employee",
|
"depends_on": "eval:cur_frm.doc.employee",
|
||||||
@ -200,10 +200,11 @@
|
|||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-31 22:31:53.746659",
|
"modified": "2021-09-11 18:38:38.617478",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Employee Advance",
|
"name": "Employee Advance",
|
||||||
|
"naming_rule": "By \"Naming Series\" field",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
|
@ -172,7 +172,10 @@ def get_paying_amount_paying_exchange_rate(payment_account, doc):
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_return_through_additional_salary(doc):
|
def create_return_through_additional_salary(doc):
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
if isinstance(doc, str):
|
||||||
doc = frappe._dict(json.loads(doc))
|
doc = frappe._dict(json.loads(doc))
|
||||||
|
|
||||||
additional_salary = frappe.new_doc('Additional Salary')
|
additional_salary = frappe.new_doc('Additional Salary')
|
||||||
additional_salary.employee = doc.employee
|
additional_salary.employee = doc.employee
|
||||||
additional_salary.currency = doc.currency
|
additional_salary.currency = doc.currency
|
||||||
|
@ -12,8 +12,11 @@ import erpnext
|
|||||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||||
from erpnext.hr.doctype.employee_advance.employee_advance import (
|
from erpnext.hr.doctype.employee_advance.employee_advance import (
|
||||||
EmployeeAdvanceOverPayment,
|
EmployeeAdvanceOverPayment,
|
||||||
|
create_return_through_additional_salary,
|
||||||
make_bank_entry,
|
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):
|
class TestEmployeeAdvance(unittest.TestCase):
|
||||||
@ -33,6 +36,46 @@ class TestEmployeeAdvance(unittest.TestCase):
|
|||||||
journal_entry1 = make_payment_entry(advance)
|
journal_entry1 = make_payment_entry(advance)
|
||||||
self.assertRaises(EmployeeAdvanceOverPayment, journal_entry1.submit)
|
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):
|
def make_payment_entry(advance):
|
||||||
journal_entry = frappe.get_doc(make_bank_entry("Employee Advance", advance.name))
|
journal_entry = frappe.get_doc(make_bank_entry("Employee Advance", advance.name))
|
||||||
journal_entry.cheque_no = "123123"
|
journal_entry.cheque_no = "123123"
|
||||||
@ -41,7 +84,7 @@ def make_payment_entry(advance):
|
|||||||
|
|
||||||
return journal_entry
|
return journal_entry
|
||||||
|
|
||||||
def make_employee_advance(employee_name):
|
def make_employee_advance(employee_name, args=None):
|
||||||
doc = frappe.new_doc("Employee Advance")
|
doc = frappe.new_doc("Employee Advance")
|
||||||
doc.employee = employee_name
|
doc.employee = employee_name
|
||||||
doc.company = "_Test company"
|
doc.company = "_Test company"
|
||||||
@ -51,6 +94,10 @@ def make_employee_advance(employee_name):
|
|||||||
doc.advance_amount = 1000
|
doc.advance_amount = 1000
|
||||||
doc.posting_date = nowdate()
|
doc.posting_date = nowdate()
|
||||||
doc.advance_account = "_Test Employee Advance - _TC"
|
doc.advance_account = "_Test Employee Advance - _TC"
|
||||||
|
|
||||||
|
if args:
|
||||||
|
doc.update(args)
|
||||||
|
|
||||||
doc.insert()
|
doc.insert()
|
||||||
doc.submit()
|
doc.submit()
|
||||||
|
|
||||||
|
@ -32,7 +32,10 @@ def set_employee_name(doc):
|
|||||||
def update_employee(employee, details, date=None, cancel=False):
|
def update_employee(employee, details, date=None, cancel=False):
|
||||||
internal_work_history = {}
|
internal_work_history = {}
|
||||||
for item in details:
|
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
|
new_data = item.new if not cancel else item.current
|
||||||
if fieldtype == "Date" and new_data:
|
if fieldtype == "Date" and new_data:
|
||||||
new_data = getdate(new_data)
|
new_data = getdate(new_data)
|
||||||
|
@ -304,5 +304,6 @@ erpnext.patches.v13_0.set_operation_time_based_on_operating_cost
|
|||||||
erpnext.patches.v13_0.validate_options_for_data_field
|
erpnext.patches.v13_0.validate_options_for_data_field
|
||||||
erpnext.patches.v13_0.create_gst_payment_entry_fields
|
erpnext.patches.v13_0.create_gst_payment_entry_fields
|
||||||
erpnext.patches.v14_0.delete_shopify_doctypes
|
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.v13_0.update_dates_in_tax_withholding_category
|
||||||
erpnext.patches.v14_0.update_opportunity_currency_fields
|
erpnext.patches.v14_0.update_opportunity_currency_fields
|
||||||
|
@ -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()
|
@ -14,12 +14,11 @@ from erpnext.hr.utils import validate_active_employee
|
|||||||
|
|
||||||
class AdditionalSalary(Document):
|
class AdditionalSalary(Document):
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
if self.ref_doctype == "Employee Advance" and self.ref_docname:
|
self.update_return_amount_in_employee_advance()
|
||||||
frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount)
|
|
||||||
|
|
||||||
self.update_employee_referral()
|
self.update_employee_referral()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
|
self.update_return_amount_in_employee_advance()
|
||||||
self.update_employee_referral(cancel=True)
|
self.update_employee_referral(cancel=True)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
@ -98,6 +97,17 @@ class AdditionalSalary(Document):
|
|||||||
frappe.throw(_("Additional Salary for referral bonus can only be created against Employee Referral with status {0}").format(
|
frappe.throw(_("Additional Salary for referral bonus can only be created against Employee Referral with status {0}").format(
|
||||||
frappe.bold("Accepted")))
|
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):
|
def update_employee_referral(self, cancel=False):
|
||||||
if self.ref_doctype == "Employee Referral":
|
if self.ref_doctype == "Employee Referral":
|
||||||
status = "Unpaid" if cancel else "Paid"
|
status = "Unpaid" if cancel else "Paid"
|
||||||
|
@ -487,7 +487,7 @@ class SalarySlip(TransactionBase):
|
|||||||
self.calculate_component_amounts("deductions")
|
self.calculate_component_amounts("deductions")
|
||||||
|
|
||||||
self.set_loan_repayment()
|
self.set_loan_repayment()
|
||||||
self.set_component_amounts_based_on_payment_days()
|
self.set_precision_for_component_amounts()
|
||||||
self.set_net_pay()
|
self.set_net_pay()
|
||||||
|
|
||||||
def set_net_pay(self):
|
def set_net_pay(self):
|
||||||
@ -709,6 +709,17 @@ class SalarySlip(TransactionBase):
|
|||||||
|
|
||||||
component_row.amount = amount
|
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):
|
def calculate_variable_based_on_taxable_salary(self, tax_component, payroll_period):
|
||||||
if not payroll_period:
|
if not payroll_period:
|
||||||
frappe.msgprint(_("Start and end dates not in a valid Payroll Period, cannot calculate {0}.")
|
frappe.msgprint(_("Start and end dates not in a valid Payroll Period, cannot calculate {0}.")
|
||||||
@ -866,14 +877,7 @@ class SalarySlip(TransactionBase):
|
|||||||
return total_tax_paid
|
return total_tax_paid
|
||||||
|
|
||||||
def get_taxable_earnings(self, allow_tax_exemption=False, based_on_payment_days=0):
|
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,
|
joining_date, relieving_date = self.get_joining_and_relieving_dates()
|
||||||
["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)))
|
|
||||||
|
|
||||||
taxable_earnings = 0
|
taxable_earnings = 0
|
||||||
additional_income = 0
|
additional_income = 0
|
||||||
@ -884,7 +888,10 @@ class SalarySlip(TransactionBase):
|
|||||||
if based_on_payment_days:
|
if based_on_payment_days:
|
||||||
amount, additional_amount = self.get_amount_based_on_payment_days(earning, joining_date, relieving_date)
|
amount, additional_amount = self.get_amount_based_on_payment_days(earning, joining_date, relieving_date)
|
||||||
else:
|
else:
|
||||||
|
if earning.additional_amount:
|
||||||
amount, additional_amount = earning.amount, 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 earning.is_tax_applicable:
|
||||||
if additional_amount:
|
if additional_amount:
|
||||||
@ -1055,7 +1062,7 @@ class SalarySlip(TransactionBase):
|
|||||||
total += amount
|
total += amount
|
||||||
return total
|
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,
|
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
|
||||||
["date_of_joining", "relieving_date"])
|
["date_of_joining", "relieving_date"])
|
||||||
|
|
||||||
@ -1065,9 +1072,7 @@ class SalarySlip(TransactionBase):
|
|||||||
if not joining_date:
|
if not joining_date:
|
||||||
frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name)))
|
frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name)))
|
||||||
|
|
||||||
for component_type in ("earnings", "deductions"):
|
return joining_date, relieving_date
|
||||||
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"))
|
|
||||||
|
|
||||||
def set_loan_repayment(self):
|
def set_loan_repayment(self):
|
||||||
self.total_loan_repayment = 0
|
self.total_loan_repayment = 0
|
||||||
|
@ -17,6 +17,7 @@ from frappe.utils import (
|
|||||||
getdate,
|
getdate,
|
||||||
nowdate,
|
nowdate,
|
||||||
)
|
)
|
||||||
|
from frappe.utils.make_random import get_random
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
@ -134,6 +135,65 @@ class TestSalarySlip(unittest.TestCase):
|
|||||||
|
|
||||||
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
|
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):
|
def test_salary_slip_with_holidays_included(self):
|
||||||
no_of_days = self.get_no_of_days()
|
no_of_days = self.get_no_of_days()
|
||||||
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1)
|
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1)
|
||||||
@ -864,3 +924,91 @@ def make_holiday_list():
|
|||||||
holiday_list = holiday_list.name
|
holiday_list = holiday_list.name
|
||||||
|
|
||||||
return holiday_list
|
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
|
@ -709,6 +709,9 @@ erpnext.utils.map_current_doc = function(opts) {
|
|||||||
setters: opts.setters,
|
setters: opts.setters,
|
||||||
get_query: opts.get_query,
|
get_query: opts.get_query,
|
||||||
add_filters_group: 1,
|
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) {
|
action: function(selections, args) {
|
||||||
let values = selections;
|
let values = selections;
|
||||||
if(values.length === 0){
|
if(values.length === 0){
|
||||||
@ -716,7 +719,7 @@ erpnext.utils.map_current_doc = function(opts) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
opts.source_name = values;
|
opts.source_name = values;
|
||||||
opts.setters = args;
|
opts.args = args;
|
||||||
d.dialog.hide();
|
d.dialog.hide();
|
||||||
_map();
|
_map();
|
||||||
},
|
},
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
"tax_withholding_category",
|
"tax_withholding_category",
|
||||||
"default_bank_account",
|
"default_bank_account",
|
||||||
"lead_name",
|
"lead_name",
|
||||||
"prospect",
|
|
||||||
"opportunity_name",
|
"opportunity_name",
|
||||||
"image",
|
"image",
|
||||||
"column_break0",
|
"column_break0",
|
||||||
@ -214,7 +213,8 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"ignore_user_permissions": 1,
|
"ignore_user_permissions": 1,
|
||||||
"label": "Represents Company",
|
"label": "Represents Company",
|
||||||
"options": "Company"
|
"options": "Company",
|
||||||
|
"unique": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "represents_company",
|
"depends_on": "represents_company",
|
||||||
@ -497,14 +497,6 @@
|
|||||||
"label": "Tax Withholding Category",
|
"label": "Tax Withholding Category",
|
||||||
"options": "Tax Withholding Category"
|
"options": "Tax Withholding Category"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "prospect",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Prospect",
|
|
||||||
"no_copy": 1,
|
|
||||||
"options": "Prospect",
|
|
||||||
"print_hide": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "opportunity_name",
|
"fieldname": "opportunity_name",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -518,8 +510,14 @@
|
|||||||
"idx": 363,
|
"idx": 363,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [
|
||||||
"modified": "2021-08-25 18:56:09.929905",
|
{
|
||||||
|
"group": "Allowed Items",
|
||||||
|
"link_doctype": "Party Specific Item",
|
||||||
|
"link_fieldname": "party"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2021-09-06 17:38:54.196663",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Customer",
|
"name": "Customer",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on('Supplier Item Group', {
|
frappe.ui.form.on('Party Specific Item', {
|
||||||
// refresh: function(frm) {
|
// refresh: function(frm) {
|
||||||
|
|
||||||
// }
|
// }
|
@ -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
|
||||||
|
}
|
@ -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))
|
@ -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)
|
@ -73,7 +73,7 @@ def get_data(conditions, filters):
|
|||||||
`tabSales Order` so,
|
`tabSales Order` so,
|
||||||
`tabSales Order Item` soi
|
`tabSales Order Item` soi
|
||||||
LEFT JOIN `tabSales Invoice Item` sii
|
LEFT JOIN `tabSales Invoice Item` sii
|
||||||
ON sii.so_detail = soi.name
|
ON sii.so_detail = soi.name and sii.docstatus = 1
|
||||||
WHERE
|
WHERE
|
||||||
soi.parent = so.name
|
soi.parent = so.name
|
||||||
and so.status not in ('Stopped', 'Closed', 'On Hold')
|
and so.status not in ('Stopped', 'Closed', 'On Hold')
|
||||||
|
@ -63,7 +63,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
|
|||||||
this.frm.set_query("item_code", "items", function() {
|
this.frm.set_query("item_code", "items", function() {
|
||||||
return {
|
return {
|
||||||
query: "erpnext.controllers.queries.item_query",
|
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 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
|
|||||||
var editable_price_list_rate = cint(frappe.defaults.get_default("editable_price_list_rate"));
|
var editable_price_list_rate = cint(frappe.defaults.get_default("editable_price_list_rate"));
|
||||||
|
|
||||||
if(df && 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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ class ItemGroup(NestedSet, WebsiteGenerator):
|
|||||||
filter_engine = ProductFiltersBuilder(self.name)
|
filter_engine = ProductFiltersBuilder(self.name)
|
||||||
|
|
||||||
context.field_filters = filter_engine.get_field_filters()
|
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({
|
context.update({
|
||||||
"parents": get_parent_item_groups(self.parent_item_group),
|
"parents": get_parent_item_groups(self.parent_item_group),
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _dict
|
|
||||||
|
|
||||||
|
|
||||||
class ProductFiltersBuilder:
|
class ProductFiltersBuilder:
|
||||||
@ -57,37 +56,31 @@ class ProductFiltersBuilder:
|
|||||||
|
|
||||||
return filter_data
|
return filter_data
|
||||||
|
|
||||||
def get_attribute_fitlers(self):
|
def get_attribute_filters(self):
|
||||||
attributes = [row.attribute for row in self.doc.filter_attributes]
|
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:
|
result = frappe.db.sql(
|
||||||
selected_attributes = []
|
"""
|
||||||
for attr in attr_doc.item_attribute_values:
|
select
|
||||||
or_filters = []
|
distinct attribute, attribute_value
|
||||||
filters= [
|
from
|
||||||
["Item Variant Attribute", "attribute", "=", attr.parent],
|
`tabItem Variant Attribute`
|
||||||
["Item Variant Attribute", "attribute_value", "=", attr.attribute_value]
|
where
|
||||||
]
|
attribute in %(attributes)s
|
||||||
if self.item_group:
|
and attribute_value is not null
|
||||||
or_filters.extend([
|
""",
|
||||||
["item_group", "=", self.item_group],
|
{"attributes": attributes},
|
||||||
["Website Item Group", "item_group", "=", self.item_group]
|
as_dict=1,
|
||||||
])
|
|
||||||
|
|
||||||
if frappe.db.get_all("Item", filters, or_filters=or_filters, limit=1):
|
|
||||||
selected_attributes.append(attr)
|
|
||||||
|
|
||||||
if selected_attributes:
|
|
||||||
valid_attributes.append(
|
|
||||||
_dict(
|
|
||||||
item_attribute_values=selected_attributes,
|
|
||||||
name=attr_doc.name
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return valid_attributes
|
attribute_value_map = {}
|
||||||
|
for d in result:
|
||||||
|
attribute_value_map.setdefault(d.attribute, []).append(d.attribute_value)
|
||||||
|
|
||||||
|
out = []
|
||||||
|
for name, values in attribute_value_map.items():
|
||||||
|
out.append(frappe._dict(name=name, item_attribute_values=values))
|
||||||
|
return out
|
||||||
|
@ -6,10 +6,13 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, msgprint
|
from frappe import _, msgprint
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.utils import cstr, flt, get_link_to_form, getdate, new_line_sep, nowdate
|
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.buying.utils import check_on_hold_or_closed_status, validate_for_items
|
||||||
from erpnext.controllers.buying_controller import BuyingController
|
from erpnext.controllers.buying_controller import BuyingController
|
||||||
@ -269,7 +272,10 @@ def update_status(name, status):
|
|||||||
material_request.update_status(status)
|
material_request.update_status(status)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@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):
|
def postprocess(source, target_doc):
|
||||||
if frappe.flags.args and frappe.flags.args.default_supplier:
|
if frappe.flags.args and frappe.flags.args.default_supplier:
|
||||||
@ -284,7 +290,10 @@ def make_purchase_order(source_name, target_doc=None):
|
|||||||
set_missing_values(source, target_doc)
|
set_missing_values(source, target_doc)
|
||||||
|
|
||||||
def select_item(d):
|
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, {
|
doclist = get_mapped_doc("Material Request", source_name, {
|
||||||
"Material Request": {
|
"Material Request": {
|
||||||
|
138
erpnext/tests/test_webform.py
Normal file
138
erpnext/tests/test_webform.py
Normal file
@ -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)
|
@ -98,14 +98,14 @@
|
|||||||
<div class="filter-options">
|
<div class="filter-options">
|
||||||
{% for attr_value in attribute.item_attribute_values %}
|
{% for attr_value in attribute.item_attribute_values %}
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label data-value="{{ value }}">
|
<label>
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
class="product-filter attribute-filter"
|
class="product-filter attribute-filter"
|
||||||
id="{{attr_value.name}}"
|
id="{{attr_value}}"
|
||||||
data-attribute-name="{{ attribute.name }}"
|
data-attribute-name="{{ attribute.name }}"
|
||||||
data-attribute-value="{{ attr_value.attribute_value }}"
|
data-attribute-value="{{ attr_value }}"
|
||||||
{% if attr_value.checked %} checked {% endif %}>
|
{% if attr_value.checked %} checked {% endif %}>
|
||||||
<span class="label-area">{{ attr_value.attribute_value }}</span>
|
<span class="label-area">{{ attr_value }}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -27,7 +27,7 @@ def get_context(context):
|
|||||||
filter_engine = ProductFiltersBuilder()
|
filter_engine = ProductFiltersBuilder()
|
||||||
|
|
||||||
context.field_filters = filter_engine.get_field_filters()
|
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.product_settings = product_settings
|
||||||
context.body_class = "product-page"
|
context.body_class = "product-page"
|
||||||
|
Loading…
Reference in New Issue
Block a user