mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-28 11:08:32 +00:00
Merge branch 'version-13-hotfix' into fix/delivery-note/billed-amount
This commit is contained in:
@@ -28,14 +28,14 @@
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "single_threshold",
|
||||
"fieldtype": "Currency",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Single Transaction Threshold"
|
||||
},
|
||||
{
|
||||
"columns": 3,
|
||||
"fieldname": "cumulative_threshold",
|
||||
"fieldtype": "Currency",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Cumulative Transaction Threshold"
|
||||
},
|
||||
@@ -59,7 +59,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-31 11:42:12.213977",
|
||||
"modified": "2022-01-13 12:04:42.904263",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Withholding Rate",
|
||||
@@ -68,5 +68,6 @@
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -121,20 +121,21 @@ class Deferred_Item(object):
|
||||
"""
|
||||
simulate future posting by creating dummy gl entries. starts from the last posting date.
|
||||
"""
|
||||
if add_days(self.last_entry_date, 1) < self.period_list[-1].to_date:
|
||||
self.estimate_for_period_list = get_period_list(
|
||||
self.filters.from_fiscal_year,
|
||||
self.filters.to_fiscal_year,
|
||||
add_days(self.last_entry_date, 1),
|
||||
self.period_list[-1].to_date,
|
||||
"Date Range",
|
||||
"Monthly",
|
||||
company=self.filters.company,
|
||||
)
|
||||
for period in self.estimate_for_period_list:
|
||||
amount = self.calculate_amount(period.from_date, period.to_date)
|
||||
gle = self.make_dummy_gle(period.key, period.to_date, amount)
|
||||
self.gle_entries.append(gle)
|
||||
if self.service_start_date != self.service_end_date:
|
||||
if add_days(self.last_entry_date, 1) < self.period_list[-1].to_date:
|
||||
self.estimate_for_period_list = get_period_list(
|
||||
self.filters.from_fiscal_year,
|
||||
self.filters.to_fiscal_year,
|
||||
add_days(self.last_entry_date, 1),
|
||||
self.period_list[-1].to_date,
|
||||
"Date Range",
|
||||
"Monthly",
|
||||
company=self.filters.company,
|
||||
)
|
||||
for period in self.estimate_for_period_list:
|
||||
amount = self.calculate_amount(period.from_date, period.to_date)
|
||||
gle = self.make_dummy_gle(period.key, period.to_date, amount)
|
||||
self.gle_entries.append(gle)
|
||||
|
||||
def calculate_item_revenue_expense_for_period(self):
|
||||
"""
|
||||
|
||||
@@ -4,19 +4,72 @@ import frappe
|
||||
|
||||
|
||||
class TestUtils(unittest.TestCase):
|
||||
def test_reset_default_field_value(self):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Purchase Receipt",
|
||||
"set_warehouse": "Warehouse 1",
|
||||
})
|
||||
def test_reset_default_field_value(self):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Purchase Receipt",
|
||||
"set_warehouse": "Warehouse 1",
|
||||
})
|
||||
|
||||
# Same values
|
||||
doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}]
|
||||
doc.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
self.assertEqual(doc.set_warehouse, "Warehouse 1")
|
||||
# Same values
|
||||
doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}]
|
||||
doc.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
self.assertEqual(doc.set_warehouse, "Warehouse 1")
|
||||
|
||||
# Mixed values
|
||||
doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 2"}, {"warehouse": "Warehouse 1"}]
|
||||
doc.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
self.assertEqual(doc.set_warehouse, None)
|
||||
# Mixed values
|
||||
doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 2"}, {"warehouse": "Warehouse 1"}]
|
||||
doc.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
self.assertEqual(doc.set_warehouse, None)
|
||||
|
||||
def test_reset_default_field_value_in_mfg_stock_entry(self):
|
||||
# manufacture stock entry with rows having blank source/target wh
|
||||
se = frappe.get_doc(
|
||||
doctype="Stock Entry",
|
||||
purpose="Manufacture",
|
||||
stock_entry_type="Manufacture",
|
||||
company="_Test Company",
|
||||
from_warehouse="_Test Warehouse - _TC",
|
||||
to_warehouse="_Test Warehouse 1 - _TC",
|
||||
items=[
|
||||
frappe._dict(item_code="_Test Item", qty=1, basic_rate=200, s_warehouse="_Test Warehouse - _TC"),
|
||||
frappe._dict(item_code="_Test FG Item", qty=4, t_warehouse="_Test Warehouse 1 - _TC", is_finished_item=1)
|
||||
]
|
||||
)
|
||||
se.save()
|
||||
|
||||
# default fields must be untouched
|
||||
self.assertEqual(se.from_warehouse, "_Test Warehouse - _TC")
|
||||
self.assertEqual(se.to_warehouse, "_Test Warehouse 1 - _TC")
|
||||
|
||||
se.delete()
|
||||
|
||||
def test_reset_default_field_value_in_transfer_stock_entry(self):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Stock Entry",
|
||||
"purpose": "Material Receipt",
|
||||
"from_warehouse": "Warehouse 1",
|
||||
"to_warehouse": "Warehouse 2",
|
||||
})
|
||||
|
||||
# Same values
|
||||
doc.items = [
|
||||
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
|
||||
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
|
||||
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"}
|
||||
]
|
||||
|
||||
doc.reset_default_field_value("from_warehouse", "items", "s_warehouse")
|
||||
doc.reset_default_field_value("to_warehouse", "items", "t_warehouse")
|
||||
self.assertEqual(doc.from_warehouse, "Warehouse 1")
|
||||
self.assertEqual(doc.to_warehouse, "Warehouse 2")
|
||||
|
||||
# Mixed values in source wh
|
||||
doc.items = [
|
||||
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
|
||||
{"s_warehouse": "Warehouse 3", "t_warehouse": "Warehouse 2"},
|
||||
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"}
|
||||
]
|
||||
|
||||
doc.reset_default_field_value("from_warehouse", "items", "s_warehouse")
|
||||
doc.reset_default_field_value("to_warehouse", "items", "t_warehouse")
|
||||
self.assertEqual(doc.from_warehouse, None)
|
||||
self.assertEqual(doc.to_warehouse, "Warehouse 2")
|
||||
@@ -17,7 +17,10 @@ class TestEmployeeOnboarding(unittest.TestCase):
|
||||
def test_employee_onboarding_incomplete_task(self):
|
||||
if frappe.db.exists('Employee Onboarding', {'employee_name': 'Test Researcher'}):
|
||||
frappe.delete_doc('Employee Onboarding', {'employee_name': 'Test Researcher'})
|
||||
_set_up()
|
||||
frappe.db.sql("delete from `tabEmployee Onboarding`")
|
||||
project = "Employee Onboarding : test@researcher.com"
|
||||
frappe.db.sql("delete from tabProject where name=%s", project)
|
||||
frappe.db.sql("delete from tabTask where project=%s", project)
|
||||
applicant = get_job_applicant()
|
||||
|
||||
job_offer = create_job_offer(job_applicant=applicant.name)
|
||||
@@ -42,7 +45,7 @@ class TestEmployeeOnboarding(unittest.TestCase):
|
||||
onboarding.submit()
|
||||
|
||||
project_name = frappe.db.get_value("Project", onboarding.project, "project_name")
|
||||
self.assertEqual(project_name, 'Employee Onboarding : Test Researcher - test@researcher.com')
|
||||
self.assertEqual(project_name, 'Employee Onboarding : test@researcher.com')
|
||||
|
||||
# don't allow making employee if onboarding is not complete
|
||||
self.assertRaises(IncompleteTaskError, make_employee, onboarding.name)
|
||||
@@ -65,8 +68,8 @@ class TestEmployeeOnboarding(unittest.TestCase):
|
||||
self.assertEqual(employee.employee_name, 'Test Researcher')
|
||||
|
||||
def get_job_applicant():
|
||||
if frappe.db.exists('Job Applicant', 'Test Researcher - test@researcher.com'):
|
||||
return frappe.get_doc('Job Applicant', 'Test Researcher - test@researcher.com')
|
||||
if frappe.db.exists('Job Applicant', 'test@researcher.com'):
|
||||
return frappe.get_doc('Job Applicant', 'test@researcher.com')
|
||||
applicant = frappe.new_doc('Job Applicant')
|
||||
applicant.applicant_name = 'Test Researcher'
|
||||
applicant.email_id = 'test@researcher.com'
|
||||
|
||||
@@ -192,10 +192,11 @@
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-09-29 23:06:10.904260",
|
||||
"modified": "2022-01-12 16:28:53.196881",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Job Applicant",
|
||||
"naming_rule": "Expression (old style)",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -210,10 +211,11 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"search_fields": "applicant_name",
|
||||
"search_fields": "applicant_name, email_id, job_title, phone_number",
|
||||
"sender_field": "email_id",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"states": [],
|
||||
"subject_field": "notes",
|
||||
"title_field": "applicant_name"
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.naming import append_number_if_name_exists
|
||||
from frappe.utils import validate_email_address
|
||||
|
||||
from erpnext.hr.doctype.interview.interview import get_interviewers
|
||||
@@ -21,10 +22,11 @@ class JobApplicant(Document):
|
||||
self.get("__onload").job_offer = job_offer[0].name
|
||||
|
||||
def autoname(self):
|
||||
keys = filter(None, (self.applicant_name, self.email_id, self.job_title))
|
||||
if not keys:
|
||||
frappe.throw(_("Name or Email is mandatory"), frappe.NameError)
|
||||
self.name = " - ".join(keys)
|
||||
self.name = self.email_id
|
||||
|
||||
# applicant can apply more than once for a different job title or reapply
|
||||
if frappe.db.exists("Job Applicant", self.name):
|
||||
self.name = append_number_if_name_exists("Job Applicant", self.name)
|
||||
|
||||
def validate(self):
|
||||
if self.email_id:
|
||||
|
||||
@@ -9,7 +9,26 @@ from erpnext.hr.doctype.designation.test_designation import create_designation
|
||||
|
||||
|
||||
class TestJobApplicant(unittest.TestCase):
|
||||
pass
|
||||
def test_job_applicant_naming(self):
|
||||
applicant = frappe.get_doc({
|
||||
"doctype": "Job Applicant",
|
||||
"status": "Open",
|
||||
"applicant_name": "_Test Applicant",
|
||||
"email_id": "job_applicant_naming@example.com"
|
||||
}).insert()
|
||||
self.assertEqual(applicant.name, 'job_applicant_naming@example.com')
|
||||
|
||||
applicant = frappe.get_doc({
|
||||
"doctype": "Job Applicant",
|
||||
"status": "Open",
|
||||
"applicant_name": "_Test Applicant",
|
||||
"email_id": "job_applicant_naming@example.com"
|
||||
}).insert()
|
||||
self.assertEqual(applicant.name, 'job_applicant_naming@example.com-1')
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
|
||||
def create_job_applicant(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -1,294 +1,108 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "HR-LPR-.YYYY.-.#####",
|
||||
"beta": 0,
|
||||
"creation": "2018-04-13 15:20:52.864288",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"from_date",
|
||||
"to_date",
|
||||
"is_active",
|
||||
"column_break_3",
|
||||
"company",
|
||||
"optional_holiday_list"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "from_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "From Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "to_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "To Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "is_active",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Is Active",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Is Active"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Company",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "optional_holiday_list",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Holiday List for Optional Leave",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Holiday List",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"options": "Holiday List"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-05-30 16:15:43.305502",
|
||||
"links": [],
|
||||
"modified": "2022-01-13 13:28:12.951025",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Period",
|
||||
"name_case": "",
|
||||
"naming_rule": "Expression (old style)",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"search_fields": "from_date, to_date, company",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -113,10 +113,11 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-01 17:54:01.014509",
|
||||
"modified": "2022-01-13 13:37:11.218882",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Policy Assignment",
|
||||
"naming_rule": "Expression (old style)",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -164,5 +165,7 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "employee_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -48,7 +48,16 @@ frappe.listview_settings['Leave Policy Assignment'] = {
|
||||
if (cur_dialog.fields_dict.leave_period.value) {
|
||||
me.set_effective_date();
|
||||
}
|
||||
}
|
||||
},
|
||||
get_query() {
|
||||
let filters = {"is_active": 1};
|
||||
if (cur_dialog.fields_dict.company.value)
|
||||
filters["company"] = cur_dialog.fields_dict.company.value;
|
||||
|
||||
return {
|
||||
filters: filters
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldtype: "Column Break"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.utils import add_months, cint, flt, now, today
|
||||
from frappe.utils import add_days, add_months, cint, flt, now, today
|
||||
|
||||
from erpnext.manufacturing.doctype.job_card.job_card import JobCardCancelError
|
||||
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||
@@ -12,6 +12,7 @@ from erpnext.manufacturing.doctype.work_order.work_order import (
|
||||
OverProductionError,
|
||||
StockOverProductionError,
|
||||
close_work_order,
|
||||
make_job_card,
|
||||
make_stock_entry,
|
||||
stop_unstop,
|
||||
)
|
||||
@@ -801,6 +802,34 @@ class TestWorkOrder(ERPNextTestCase):
|
||||
if row.is_scrap_item:
|
||||
self.assertEqual(row.qty, 1)
|
||||
|
||||
# Partial Job Card 1 with qty 10
|
||||
wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=add_days(now(), 60), qty=20, skip_transfer=1)
|
||||
job_card = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name')
|
||||
update_job_card(job_card, 10)
|
||||
|
||||
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
|
||||
for row in stock_entry.items:
|
||||
if row.is_scrap_item:
|
||||
self.assertEqual(row.qty, 2)
|
||||
|
||||
# Partial Job Card 2 with qty 10
|
||||
operations = []
|
||||
wo_order.load_from_db()
|
||||
for row in wo_order.operations:
|
||||
n_dict = row.as_dict()
|
||||
n_dict['qty'] = 10
|
||||
n_dict['pending_qty'] = 10
|
||||
operations.append(n_dict)
|
||||
|
||||
make_job_card(wo_order.name, operations)
|
||||
job_card = frappe.db.get_value('Job Card', {'work_order': wo_order.name, 'docstatus': 0}, 'name')
|
||||
update_job_card(job_card, 10)
|
||||
|
||||
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
|
||||
for row in stock_entry.items:
|
||||
if row.is_scrap_item:
|
||||
self.assertEqual(row.qty, 2)
|
||||
|
||||
def test_close_work_order(self):
|
||||
items = ['Test FG Item for Closed WO', 'Test RM Item 1 for Closed WO',
|
||||
'Test RM Item 2 for Closed WO']
|
||||
@@ -841,7 +870,9 @@ class TestWorkOrder(ERPNextTestCase):
|
||||
close_work_order(wo_order, "Closed")
|
||||
self.assertEqual(wo_order.get('status'), "Closed")
|
||||
|
||||
def update_job_card(job_card):
|
||||
def update_job_card(job_card, jc_qty=None):
|
||||
employee = frappe.db.get_value('Employee', {'status': 'Active'}, 'name')
|
||||
|
||||
job_card_doc = frappe.get_doc('Job Card', job_card)
|
||||
job_card_doc.set('scrap_items', [
|
||||
{
|
||||
@@ -854,15 +885,18 @@ def update_job_card(job_card):
|
||||
},
|
||||
])
|
||||
|
||||
if jc_qty:
|
||||
job_card_doc.for_quantity = jc_qty
|
||||
|
||||
job_card_doc.append('time_logs', {
|
||||
'from_time': now(),
|
||||
'employee': employee,
|
||||
'time_in_mins': 60,
|
||||
'completed_qty': job_card_doc.for_quantity
|
||||
})
|
||||
|
||||
job_card_doc.submit()
|
||||
|
||||
|
||||
def get_scrap_item_details(bom_no):
|
||||
scrap_items = {}
|
||||
for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`
|
||||
|
||||
@@ -60,6 +60,8 @@ class PayrollEntry(Document):
|
||||
def on_cancel(self):
|
||||
frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip`
|
||||
where payroll_entry=%s """, (self.name)))
|
||||
self.db_set("salary_slips_created", 0)
|
||||
self.db_set("salary_slips_submitted", 0)
|
||||
|
||||
def get_emp_list(self):
|
||||
"""
|
||||
|
||||
@@ -213,6 +213,9 @@ erpnext.company.setup_queries = function(frm) {
|
||||
["default_payroll_payable_account", {"root_type": "Liability"}],
|
||||
["round_off_account", {"root_type": "Expense"}],
|
||||
["write_off_account", {"root_type": "Expense"}],
|
||||
["default_deferred_expense_account", {}],
|
||||
["default_deferred_revenue_account", {}],
|
||||
["default_expense_claim_payable_account", {}],
|
||||
["default_discount_account", {}],
|
||||
["discount_allowed_account", {"root_type": "Expense"}],
|
||||
["discount_received_account", {"root_type": "Income"}],
|
||||
|
||||
@@ -8,6 +8,7 @@ from collections import defaultdict
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate
|
||||
from six import iteritems, itervalues, string_types
|
||||
|
||||
@@ -86,8 +87,11 @@ class StockEntry(StockController):
|
||||
self.validate_warehouse()
|
||||
self.validate_work_order()
|
||||
self.validate_bom()
|
||||
self.mark_finished_and_scrap_items()
|
||||
self.validate_finished_goods()
|
||||
|
||||
if self.purpose in ("Manufacture", "Repack"):
|
||||
self.mark_finished_and_scrap_items()
|
||||
self.validate_finished_goods()
|
||||
|
||||
self.validate_with_material_request()
|
||||
self.validate_batch()
|
||||
self.validate_inspection()
|
||||
@@ -110,8 +114,12 @@ class StockEntry(StockController):
|
||||
self.set_actual_qty()
|
||||
self.calculate_rate_and_amount()
|
||||
self.validate_putaway_capacity()
|
||||
self.reset_default_field_value("from_warehouse", "items", "s_warehouse")
|
||||
self.reset_default_field_value("to_warehouse", "items", "t_warehouse")
|
||||
|
||||
if not self.get("purpose") == "Manufacture":
|
||||
# ignore scrap item wh difference and empty source/target wh
|
||||
# in Manufacture Entry
|
||||
self.reset_default_field_value("from_warehouse", "items", "s_warehouse")
|
||||
self.reset_default_field_value("to_warehouse", "items", "t_warehouse")
|
||||
|
||||
def on_submit(self):
|
||||
self.update_stock_ledger()
|
||||
@@ -702,26 +710,25 @@ class StockEntry(StockController):
|
||||
validate_bom_no(item_code, d.bom_no)
|
||||
|
||||
def mark_finished_and_scrap_items(self):
|
||||
if self.purpose in ("Repack", "Manufacture"):
|
||||
if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]):
|
||||
return
|
||||
if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]):
|
||||
return
|
||||
|
||||
finished_item = self.get_finished_item()
|
||||
finished_item = self.get_finished_item()
|
||||
|
||||
if not finished_item and self.purpose == "Manufacture":
|
||||
# In case of independent Manufacture entry, don't auto set
|
||||
# user must decide and set
|
||||
return
|
||||
if not finished_item and self.purpose == "Manufacture":
|
||||
# In case of independent Manufacture entry, don't auto set
|
||||
# user must decide and set
|
||||
return
|
||||
|
||||
for d in self.items:
|
||||
if d.t_warehouse and not d.s_warehouse:
|
||||
if self.purpose=="Repack" or d.item_code == finished_item:
|
||||
d.is_finished_item = 1
|
||||
else:
|
||||
d.is_scrap_item = 1
|
||||
for d in self.items:
|
||||
if d.t_warehouse and not d.s_warehouse:
|
||||
if self.purpose=="Repack" or d.item_code == finished_item:
|
||||
d.is_finished_item = 1
|
||||
else:
|
||||
d.is_finished_item = 0
|
||||
d.is_scrap_item = 0
|
||||
d.is_scrap_item = 1
|
||||
else:
|
||||
d.is_finished_item = 0
|
||||
d.is_scrap_item = 0
|
||||
|
||||
def get_finished_item(self):
|
||||
finished_item = None
|
||||
@@ -734,9 +741,9 @@ class StockEntry(StockController):
|
||||
|
||||
def validate_finished_goods(self):
|
||||
"""
|
||||
1. Check if FG exists
|
||||
2. Check if Multiple FG Items are present
|
||||
3. Check FG Item and Qty against WO if present
|
||||
1. Check if FG exists (mfg, repack)
|
||||
2. Check if Multiple FG Items are present (mfg)
|
||||
3. Check FG Item and Qty against WO if present (mfg)
|
||||
"""
|
||||
production_item, wo_qty, finished_items = None, 0, []
|
||||
|
||||
@@ -749,8 +756,9 @@ class StockEntry(StockController):
|
||||
for d in self.get('items'):
|
||||
if d.is_finished_item:
|
||||
if not self.work_order:
|
||||
# Independent MFG Entry/ Repack Entry, no WO to match against
|
||||
finished_items.append(d.item_code)
|
||||
continue # Independent Manufacture Entry, no WO to match against
|
||||
continue
|
||||
|
||||
if d.item_code != production_item:
|
||||
frappe.throw(_("Finished Item {0} does not match with Work Order {1}")
|
||||
@@ -763,19 +771,17 @@ class StockEntry(StockController):
|
||||
|
||||
finished_items.append(d.item_code)
|
||||
|
||||
if len(set(finished_items)) > 1:
|
||||
if not finished_items:
|
||||
frappe.throw(
|
||||
msg=_("Multiple items cannot be marked as finished item"),
|
||||
title=_("Note"),
|
||||
exc=FinishedGoodError
|
||||
msg=_("There must be atleast 1 Finished Good in this Stock Entry").format(self.name),
|
||||
title=_("Missing Finished Good"), exc=FinishedGoodError
|
||||
)
|
||||
|
||||
if self.purpose == "Manufacture":
|
||||
if not finished_items:
|
||||
if len(set(finished_items)) > 1:
|
||||
frappe.throw(
|
||||
msg=_("There must be atleast 1 Finished Good in this Stock Entry").format(self.name),
|
||||
title=_("Missing Finished Good"),
|
||||
exc=FinishedGoodError
|
||||
msg=_("Multiple items cannot be marked as finished item"),
|
||||
title=_("Note"), exc=FinishedGoodError
|
||||
)
|
||||
|
||||
allowance_percentage = flt(
|
||||
@@ -1276,22 +1282,29 @@ class StockEntry(StockController):
|
||||
if not self.pro_doc:
|
||||
self.set_work_order_details()
|
||||
|
||||
scrap_items = frappe.db.sql('''
|
||||
SELECT
|
||||
JCSI.item_code, JCSI.item_name, SUM(JCSI.stock_qty) as stock_qty, JCSI.stock_uom, JCSI.description
|
||||
FROM
|
||||
`tabJob Card` JC, `tabJob Card Scrap Item` JCSI
|
||||
WHERE
|
||||
JCSI.parent = JC.name AND JC.docstatus = 1
|
||||
AND JCSI.item_code IS NOT NULL AND JC.work_order = %s
|
||||
GROUP BY
|
||||
JCSI.item_code
|
||||
''', self.work_order, as_dict=1)
|
||||
|
||||
pending_qty = flt(self.pro_doc.qty) - flt(self.pro_doc.produced_qty)
|
||||
if pending_qty <=0:
|
||||
if not self.pro_doc.operations:
|
||||
return []
|
||||
|
||||
job_card = frappe.qb.DocType('Job Card')
|
||||
job_card_scrap_item = frappe.qb.DocType('Job Card Scrap Item')
|
||||
|
||||
scrap_items = (
|
||||
frappe.qb.from_(job_card)
|
||||
.select(
|
||||
Sum(job_card_scrap_item.stock_qty).as_('stock_qty'),
|
||||
job_card_scrap_item.item_code, job_card_scrap_item.item_name,
|
||||
job_card_scrap_item.description, job_card_scrap_item.stock_uom)
|
||||
.join(job_card_scrap_item)
|
||||
.on(job_card_scrap_item.parent == job_card.name)
|
||||
.where(
|
||||
(job_card_scrap_item.item_code.isnotnull())
|
||||
& (job_card.work_order == self.work_order)
|
||||
& (job_card.docstatus == 1))
|
||||
.groupby(job_card_scrap_item.item_code)
|
||||
).run(as_dict=1)
|
||||
|
||||
pending_qty = flt(self.get_completed_job_card_qty()) - flt(self.pro_doc.produced_qty)
|
||||
|
||||
used_scrap_items = self.get_used_scrap_items()
|
||||
for row in scrap_items:
|
||||
row.stock_qty -= flt(used_scrap_items.get(row.item_code))
|
||||
@@ -1305,6 +1318,9 @@ class StockEntry(StockController):
|
||||
|
||||
return scrap_items
|
||||
|
||||
def get_completed_job_card_qty(self):
|
||||
return flt(min([d.completed_qty for d in self.pro_doc.operations]))
|
||||
|
||||
def get_used_scrap_items(self):
|
||||
used_scrap_items = defaultdict(float)
|
||||
data = frappe.get_all(
|
||||
|
||||
@@ -227,9 +227,47 @@ class TestStockEntry(ERPNextTestCase):
|
||||
|
||||
mtn.cancel()
|
||||
|
||||
def test_repack_no_change_in_valuation(self):
|
||||
company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company')
|
||||
def test_repack_multiple_fg(self):
|
||||
"Test `is_finished_item` for one item repacked into two items."
|
||||
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=100, basic_rate=100)
|
||||
|
||||
repack = frappe.copy_doc(test_records[3])
|
||||
repack.posting_date = nowdate()
|
||||
repack.posting_time = nowtime()
|
||||
|
||||
repack.items[0].qty = 100.0
|
||||
repack.items[0].transfer_qty = 100.0
|
||||
repack.items[1].qty = 50.0
|
||||
|
||||
repack.append("items", {
|
||||
"conversion_factor": 1.0,
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"doctype": "Stock Entry Detail",
|
||||
"expense_account": "Stock Adjustment - _TC",
|
||||
"basic_rate": 150,
|
||||
"item_code": "_Test Item 2",
|
||||
"parentfield": "items",
|
||||
"qty": 50.0,
|
||||
"stock_uom": "_Test UOM",
|
||||
"t_warehouse": "_Test Warehouse - _TC",
|
||||
"transfer_qty": 50.0,
|
||||
"uom": "_Test UOM"
|
||||
})
|
||||
repack.set_stock_entry_type()
|
||||
repack.insert()
|
||||
|
||||
self.assertEqual(repack.items[1].is_finished_item, 1)
|
||||
self.assertEqual(repack.items[2].is_finished_item, 1)
|
||||
|
||||
repack.items[1].is_finished_item = 0
|
||||
repack.items[2].is_finished_item = 0
|
||||
|
||||
# must raise error if 0 fg in repack entry
|
||||
self.assertRaises(FinishedGoodError, repack.validate_finished_goods)
|
||||
|
||||
repack.delete() # teardown
|
||||
|
||||
def test_repack_no_change_in_valuation(self):
|
||||
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
|
||||
make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC",
|
||||
qty=50, basic_rate=100)
|
||||
|
||||
@@ -182,8 +182,6 @@ class TransactionBase(StatusUpdater):
|
||||
|
||||
if len(child_table_values) > 1:
|
||||
self.set(default_field, None)
|
||||
else:
|
||||
self.set(default_field, list(child_table_values)[0])
|
||||
|
||||
def delete_events(ref_type, ref_name):
|
||||
events = frappe.db.sql_list(""" SELECT
|
||||
|
||||
Reference in New Issue
Block a user