Merge branch 'version-13-hotfix' into fix/delivery-note/billed-amount

This commit is contained in:
Sagar Sharma
2022-01-15 11:53:42 +05:30
committed by GitHub
16 changed files with 302 additions and 304 deletions

View File

@@ -28,14 +28,14 @@
{ {
"columns": 2, "columns": 2,
"fieldname": "single_threshold", "fieldname": "single_threshold",
"fieldtype": "Currency", "fieldtype": "Float",
"in_list_view": 1, "in_list_view": 1,
"label": "Single Transaction Threshold" "label": "Single Transaction Threshold"
}, },
{ {
"columns": 3, "columns": 3,
"fieldname": "cumulative_threshold", "fieldname": "cumulative_threshold",
"fieldtype": "Currency", "fieldtype": "Float",
"in_list_view": 1, "in_list_view": 1,
"label": "Cumulative Transaction Threshold" "label": "Cumulative Transaction Threshold"
}, },
@@ -59,7 +59,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-08-31 11:42:12.213977", "modified": "2022-01-13 12:04:42.904263",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Tax Withholding Rate", "name": "Tax Withholding Rate",
@@ -68,5 +68,6 @@
"quick_entry": 1, "quick_entry": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -121,6 +121,7 @@ class Deferred_Item(object):
""" """
simulate future posting by creating dummy gl entries. starts from the last posting date. simulate future posting by creating dummy gl entries. starts from the last posting date.
""" """
if self.service_start_date != self.service_end_date:
if add_days(self.last_entry_date, 1) < self.period_list[-1].to_date: if add_days(self.last_entry_date, 1) < self.period_list[-1].to_date:
self.estimate_for_period_list = get_period_list( self.estimate_for_period_list = get_period_list(
self.filters.from_fiscal_year, self.filters.from_fiscal_year,

View File

@@ -20,3 +20,56 @@ class TestUtils(unittest.TestCase):
doc.reset_default_field_value("set_warehouse", "items", "warehouse") doc.reset_default_field_value("set_warehouse", "items", "warehouse")
self.assertEqual(doc.set_warehouse, None) 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")

View File

@@ -17,7 +17,10 @@ class TestEmployeeOnboarding(unittest.TestCase):
def test_employee_onboarding_incomplete_task(self): def test_employee_onboarding_incomplete_task(self):
if frappe.db.exists('Employee Onboarding', {'employee_name': 'Test Researcher'}): if frappe.db.exists('Employee Onboarding', {'employee_name': 'Test Researcher'}):
frappe.delete_doc('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() applicant = get_job_applicant()
job_offer = create_job_offer(job_applicant=applicant.name) job_offer = create_job_offer(job_applicant=applicant.name)
@@ -42,7 +45,7 @@ class TestEmployeeOnboarding(unittest.TestCase):
onboarding.submit() onboarding.submit()
project_name = frappe.db.get_value("Project", onboarding.project, "project_name") 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 # don't allow making employee if onboarding is not complete
self.assertRaises(IncompleteTaskError, make_employee, onboarding.name) self.assertRaises(IncompleteTaskError, make_employee, onboarding.name)
@@ -65,8 +68,8 @@ class TestEmployeeOnboarding(unittest.TestCase):
self.assertEqual(employee.employee_name, 'Test Researcher') self.assertEqual(employee.employee_name, 'Test Researcher')
def get_job_applicant(): def get_job_applicant():
if frappe.db.exists('Job Applicant', 'Test Researcher - test@researcher.com'): if frappe.db.exists('Job Applicant', 'test@researcher.com'):
return frappe.get_doc('Job Applicant', 'Test Researcher - test@researcher.com') return frappe.get_doc('Job Applicant', 'test@researcher.com')
applicant = frappe.new_doc('Job Applicant') applicant = frappe.new_doc('Job Applicant')
applicant.applicant_name = 'Test Researcher' applicant.applicant_name = 'Test Researcher'
applicant.email_id = 'test@researcher.com' applicant.email_id = 'test@researcher.com'

View File

@@ -192,10 +192,11 @@
"idx": 1, "idx": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2021-09-29 23:06:10.904260", "modified": "2022-01-12 16:28:53.196881",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Job Applicant", "name": "Job Applicant",
"naming_rule": "Expression (old style)",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
@@ -210,10 +211,11 @@
"write": 1 "write": 1
} }
], ],
"search_fields": "applicant_name", "search_fields": "applicant_name, email_id, job_title, phone_number",
"sender_field": "email_id", "sender_field": "email_id",
"sort_field": "modified", "sort_field": "modified",
"sort_order": "ASC", "sort_order": "ASC",
"states": [],
"subject_field": "notes", "subject_field": "notes",
"title_field": "applicant_name" "title_field": "applicant_name"
} }

View File

@@ -7,6 +7,7 @@
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.model.naming import append_number_if_name_exists
from frappe.utils import validate_email_address from frappe.utils import validate_email_address
from erpnext.hr.doctype.interview.interview import get_interviewers 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 self.get("__onload").job_offer = job_offer[0].name
def autoname(self): def autoname(self):
keys = filter(None, (self.applicant_name, self.email_id, self.job_title)) self.name = self.email_id
if not keys:
frappe.throw(_("Name or Email is mandatory"), frappe.NameError) # applicant can apply more than once for a different job title or reapply
self.name = " - ".join(keys) if frappe.db.exists("Job Applicant", self.name):
self.name = append_number_if_name_exists("Job Applicant", self.name)
def validate(self): def validate(self):
if self.email_id: if self.email_id:

View File

@@ -9,7 +9,26 @@ from erpnext.hr.doctype.designation.test_designation import create_designation
class TestJobApplicant(unittest.TestCase): 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): def create_job_applicant(**args):
args = frappe._dict(args) args = frappe._dict(args)

View File

@@ -1,294 +1,108 @@
{ {
"allow_copy": 0, "actions": [],
"allow_guest_to_view": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"autoname": "HR-LPR-.YYYY.-.#####", "autoname": "HR-LPR-.YYYY.-.#####",
"beta": 0,
"creation": "2018-04-13 15:20:52.864288", "creation": "2018-04-13 15:20:52.864288",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"from_date",
"to_date",
"is_active",
"column_break_3",
"company",
"optional_holiday_list"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "from_date", "fieldname": "from_date",
"fieldtype": "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_list_view": 1,
"in_standard_filter": 0,
"label": "From Date", "label": "From Date",
"length": 0, "reqd": 1
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "to_date", "fieldname": "to_date",
"fieldtype": "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_list_view": 1,
"in_standard_filter": 0,
"label": "To Date", "label": "To Date",
"length": 0, "reqd": 1
"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
}, },
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "is_active", "fieldname": "is_active",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "label": "Is Active"
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3", "fieldname": "column_break_3",
"fieldtype": "Column Break", "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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Company", "label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company", "options": "Company",
"permlevel": 0, "reqd": 1
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "optional_holiday_list", "fieldname": "optional_holiday_list",
"fieldtype": "Link", "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", "label": "Holiday List for Optional Leave",
"length": 0, "options": "Holiday List"
"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
} }
], ],
"has_web_view": 0, "links": [],
"hide_heading": 0, "modified": "2022-01-13 13:28:12.951025",
"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",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Leave Period", "name": "Leave Period",
"name_case": "", "naming_rule": "Expression (old style)",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "System Manager", "role": "System Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "HR Manager", "role": "HR Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "HR User", "role": "HR User",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 0, "search_fields": "from_date, to_date, company",
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1, "states": [],
"track_seen": 0, "track_changes": 1
"track_views": 0
} }

View File

@@ -113,10 +113,11 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-03-01 17:54:01.014509", "modified": "2022-01-13 13:37:11.218882",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Leave Policy Assignment", "name": "Leave Policy Assignment",
"naming_rule": "Expression (old style)",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
@@ -164,5 +165,7 @@
], ],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"title_field": "employee_name",
"track_changes": 1 "track_changes": 1
} }

View File

@@ -48,7 +48,16 @@ frappe.listview_settings['Leave Policy Assignment'] = {
if (cur_dialog.fields_dict.leave_period.value) { if (cur_dialog.fields_dict.leave_period.value) {
me.set_effective_date(); 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" fieldtype: "Column Break"

View File

@@ -2,7 +2,7 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
import frappe 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.job_card.job_card import JobCardCancelError
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom 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, OverProductionError,
StockOverProductionError, StockOverProductionError,
close_work_order, close_work_order,
make_job_card,
make_stock_entry, make_stock_entry,
stop_unstop, stop_unstop,
) )
@@ -801,6 +802,34 @@ class TestWorkOrder(ERPNextTestCase):
if row.is_scrap_item: if row.is_scrap_item:
self.assertEqual(row.qty, 1) 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): def test_close_work_order(self):
items = ['Test FG Item for Closed WO', 'Test RM Item 1 for Closed WO', items = ['Test FG Item for Closed WO', 'Test RM Item 1 for Closed WO',
'Test RM Item 2 for Closed WO'] 'Test RM Item 2 for Closed WO']
@@ -841,7 +870,9 @@ class TestWorkOrder(ERPNextTestCase):
close_work_order(wo_order, "Closed") close_work_order(wo_order, "Closed")
self.assertEqual(wo_order.get('status'), "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 = frappe.get_doc('Job Card', job_card)
job_card_doc.set('scrap_items', [ 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', { job_card_doc.append('time_logs', {
'from_time': now(), 'from_time': now(),
'employee': employee,
'time_in_mins': 60, 'time_in_mins': 60,
'completed_qty': job_card_doc.for_quantity 'completed_qty': job_card_doc.for_quantity
}) })
job_card_doc.submit() job_card_doc.submit()
def get_scrap_item_details(bom_no): def get_scrap_item_details(bom_no):
scrap_items = {} scrap_items = {}
for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item` for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`

View File

@@ -60,6 +60,8 @@ class PayrollEntry(Document):
def on_cancel(self): def on_cancel(self):
frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip` frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip`
where payroll_entry=%s """, (self.name))) 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): def get_emp_list(self):
""" """

View File

@@ -213,6 +213,9 @@ erpnext.company.setup_queries = function(frm) {
["default_payroll_payable_account", {"root_type": "Liability"}], ["default_payroll_payable_account", {"root_type": "Liability"}],
["round_off_account", {"root_type": "Expense"}], ["round_off_account", {"root_type": "Expense"}],
["write_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", {}], ["default_discount_account", {}],
["discount_allowed_account", {"root_type": "Expense"}], ["discount_allowed_account", {"root_type": "Expense"}],
["discount_received_account", {"root_type": "Income"}], ["discount_received_account", {"root_type": "Income"}],

View File

@@ -8,6 +8,7 @@ from collections import defaultdict
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.mapper import get_mapped_doc 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 frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate
from six import iteritems, itervalues, string_types from six import iteritems, itervalues, string_types
@@ -86,8 +87,11 @@ class StockEntry(StockController):
self.validate_warehouse() self.validate_warehouse()
self.validate_work_order() self.validate_work_order()
self.validate_bom() self.validate_bom()
if self.purpose in ("Manufacture", "Repack"):
self.mark_finished_and_scrap_items() self.mark_finished_and_scrap_items()
self.validate_finished_goods() self.validate_finished_goods()
self.validate_with_material_request() self.validate_with_material_request()
self.validate_batch() self.validate_batch()
self.validate_inspection() self.validate_inspection()
@@ -110,6 +114,10 @@ class StockEntry(StockController):
self.set_actual_qty() self.set_actual_qty()
self.calculate_rate_and_amount() self.calculate_rate_and_amount()
self.validate_putaway_capacity() self.validate_putaway_capacity()
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("from_warehouse", "items", "s_warehouse")
self.reset_default_field_value("to_warehouse", "items", "t_warehouse") self.reset_default_field_value("to_warehouse", "items", "t_warehouse")
@@ -702,7 +710,6 @@ class StockEntry(StockController):
validate_bom_no(item_code, d.bom_no) validate_bom_no(item_code, d.bom_no)
def mark_finished_and_scrap_items(self): 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)]): if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]):
return return
@@ -734,9 +741,9 @@ class StockEntry(StockController):
def validate_finished_goods(self): def validate_finished_goods(self):
""" """
1. Check if FG exists 1. Check if FG exists (mfg, repack)
2. Check if Multiple FG Items are present 2. Check if Multiple FG Items are present (mfg)
3. Check FG Item and Qty against WO if present 3. Check FG Item and Qty against WO if present (mfg)
""" """
production_item, wo_qty, finished_items = None, 0, [] production_item, wo_qty, finished_items = None, 0, []
@@ -749,8 +756,9 @@ class StockEntry(StockController):
for d in self.get('items'): for d in self.get('items'):
if d.is_finished_item: if d.is_finished_item:
if not self.work_order: if not self.work_order:
# Independent MFG Entry/ Repack Entry, no WO to match against
finished_items.append(d.item_code) finished_items.append(d.item_code)
continue # Independent Manufacture Entry, no WO to match against continue
if d.item_code != production_item: if d.item_code != production_item:
frappe.throw(_("Finished Item {0} does not match with Work Order {1}") 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) finished_items.append(d.item_code)
if len(set(finished_items)) > 1:
frappe.throw(
msg=_("Multiple items cannot be marked as finished item"),
title=_("Note"),
exc=FinishedGoodError
)
if self.purpose == "Manufacture":
if not finished_items: if not finished_items:
frappe.throw( frappe.throw(
msg=_("There must be atleast 1 Finished Good in this Stock Entry").format(self.name), msg=_("There must be atleast 1 Finished Good in this Stock Entry").format(self.name),
title=_("Missing Finished Good"), title=_("Missing Finished Good"), exc=FinishedGoodError
exc=FinishedGoodError )
if self.purpose == "Manufacture":
if len(set(finished_items)) > 1:
frappe.throw(
msg=_("Multiple items cannot be marked as finished item"),
title=_("Note"), exc=FinishedGoodError
) )
allowance_percentage = flt( allowance_percentage = flt(
@@ -1276,22 +1282,29 @@ class StockEntry(StockController):
if not self.pro_doc: if not self.pro_doc:
self.set_work_order_details() self.set_work_order_details()
scrap_items = frappe.db.sql(''' if not self.pro_doc.operations:
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:
return [] 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() used_scrap_items = self.get_used_scrap_items()
for row in scrap_items: for row in scrap_items:
row.stock_qty -= flt(used_scrap_items.get(row.item_code)) row.stock_qty -= flt(used_scrap_items.get(row.item_code))
@@ -1305,6 +1318,9 @@ class StockEntry(StockController):
return scrap_items 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): def get_used_scrap_items(self):
used_scrap_items = defaultdict(float) used_scrap_items = defaultdict(float)
data = frappe.get_all( data = frappe.get_all(

View File

@@ -227,9 +227,47 @@ class TestStockEntry(ERPNextTestCase):
mtn.cancel() mtn.cancel()
def test_repack_no_change_in_valuation(self): def test_repack_multiple_fg(self):
company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') "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", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC", make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC",
qty=50, basic_rate=100) qty=50, basic_rate=100)

View File

@@ -182,8 +182,6 @@ class TransactionBase(StatusUpdater):
if len(child_table_values) > 1: if len(child_table_values) > 1:
self.set(default_field, None) self.set(default_field, None)
else:
self.set(default_field, list(child_table_values)[0])
def delete_events(ref_type, ref_name): def delete_events(ref_type, ref_name):
events = frappe.db.sql_list(""" SELECT events = frappe.db.sql_list(""" SELECT