Merge branch 'rebrand-ui' of https://github.com/frappe/erpnext into rebrand-ui

This commit is contained in:
prssanna 2020-10-16 11:32:10 +05:30
commit 5ee7bca185
207 changed files with 104049 additions and 17471 deletions

View File

@ -53,7 +53,7 @@
{
"hidden": 0,
"label": "Goods and Services Tax (GST India)",
"links": "[\n {\n \"label\": \"GST Settings\",\n \"name\": \"GST Settings\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"GST HSN Code\",\n \"name\": \"GST HSN Code\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GSTR-1\",\n \"name\": \"GSTR-1\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GSTR-2\",\n \"name\": \"GSTR-2\",\n \"type\": \"report\"\n },\n {\n \"label\": \"GSTR 3B Report\",\n \"name\": \"GSTR 3B Report\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Sales Register\",\n \"name\": \"GST Sales Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Purchase Register\",\n \"name\": \"GST Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Itemised Sales Register\",\n \"name\": \"GST Itemised Sales Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Itemised Purchase Register\",\n \"name\": \"GST Itemised Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"country\": \"India\",\n \"description\": \"C-Form records\",\n \"label\": \"C-Form\",\n \"name\": \"C-Form\",\n \"type\": \"doctype\"\n }\n]"
"links": "[\n {\n \"label\": \"GST Settings\",\n \"name\": \"GST Settings\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"GST HSN Code\",\n \"name\": \"GST HSN Code\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GSTR-1\",\n \"name\": \"GSTR-1\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GSTR-2\",\n \"name\": \"GSTR-2\",\n \"type\": \"report\"\n },\n {\n \"label\": \"GSTR 3B Report\",\n \"name\": \"GSTR 3B Report\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Sales Register\",\n \"name\": \"GST Sales Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Purchase Register\",\n \"name\": \"GST Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Itemised Sales Register\",\n \"name\": \"GST Itemised Sales Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Itemised Purchase Register\",\n \"name\": \"GST Itemised Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"country\": \"India\",\n \"description\": \"C-Form records\",\n \"label\": \"C-Form\",\n \"name\": \"C-Form\",\n \"type\": \"doctype\"\n },\n {\n \"country\": \"India\",\n \"label\": \"Lower Deduction Certificate\",\n \"name\": \"Lower Deduction Certificate\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
@ -99,7 +99,7 @@
"idx": 0,
"is_standard": 1,
"label": "Accounting",
"modified": "2020-09-09 11:45:33.766400",
"modified": "2020-10-08 20:31:46.022470",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",

View File

@ -117,7 +117,9 @@ class Account(NestedSet):
for d in frappe.db.get_values('Account', filters=filters, fieldname=["company", "name"], as_dict=True):
parent_acc_name_map[d["company"]] = d["name"]
if not parent_acc_name_map: return
self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)
def validate_group_or_ledger(self):
@ -289,10 +291,30 @@ def validate_account_number(name, account_number, company):
.format(account_number, account_with_same_number))
@frappe.whitelist()
def update_account_number(name, account_name, account_number=None):
def update_account_number(name, account_name, account_number=None, from_descendant=False):
account = frappe.db.get_value("Account", name, "company", as_dict=True)
if not account: return
old_acc_name, old_acc_number = frappe.db.get_value('Account', name, \
["account_name", "account_number"])
# check if account exists in parent company
ancestors = get_ancestors_of("Company", account.company)
allow_independent_account_creation = frappe.get_value("Company", account.company, "allow_account_creation_against_child_company")
if ancestors and not allow_independent_account_creation:
for ancestor in ancestors:
if frappe.db.get_value("Account", {'account_name': old_acc_name, 'company': ancestor}, 'name'):
# same account in parent company exists
allow_child_account_creation = _("Allow Account Creation Against Child Company")
message = _("Account {0} exists in parent company {1}.").format(frappe.bold(old_acc_name), frappe.bold(ancestor))
message += "<br>" + _("Renaming it is only allowed via parent company {0}, \
to avoid mismatch.").format(frappe.bold(ancestor)) + "<br><br>"
message += _("To overrule this, enable '{0}' in company {1}").format(allow_child_account_creation, frappe.bold(account.company))
frappe.throw(message, title=_("Rename Not Allowed"))
validate_account_number(name, account_number, account.company)
if account_number:
frappe.db.set_value("Account", name, "account_number", account_number.strip())
@ -300,6 +322,12 @@ def update_account_number(name, account_name, account_number=None):
frappe.db.set_value("Account", name, "account_number", "")
frappe.db.set_value("Account", name, "account_name", account_name.strip())
if not from_descendant:
# Update and rename in child company accounts as well
descendants = get_descendants_of('Company', account.company)
if descendants:
sync_update_account_number_in_child(descendants, old_acc_name, account_name, account_number, old_acc_number)
new_name = get_account_autoname(account_number, account_name, account.company)
if name != new_name:
frappe.rename_doc("Account", name, new_name, force=1)
@ -330,3 +358,14 @@ def get_root_company(company):
# return the topmost company in the hierarchy
ancestors = get_ancestors_of('Company', company, "lft asc")
return [ancestors[0]] if ancestors else []
def sync_update_account_number_in_child(descendants, old_acc_name, account_name, account_number=None, old_acc_number=None):
filters = {
"company": ["in", descendants],
"account_name": old_acc_name,
}
if old_acc_number:
filters["account_number"] = old_acc_number
for d in frappe.db.get_values('Account', filters=filters, fieldname=["company", "name"], as_dict=True):
update_account_number(d["name"], account_name, account_number, from_descendant=True)

View File

@ -5,8 +5,7 @@ from __future__ import unicode_literals
import unittest
import frappe
from erpnext.stock import get_warehouse_account, get_company_default_inventory_account
from erpnext.accounts.doctype.account.account import update_account_number
from erpnext.accounts.doctype.account.account import merge_account
from erpnext.accounts.doctype.account.account import update_account_number, merge_account
class TestAccount(unittest.TestCase):
def test_rename_account(self):
@ -99,7 +98,8 @@ class TestAccount(unittest.TestCase):
"Softwares - _TC", doc.is_group, doc.root_type, doc.company)
def test_account_sync(self):
del frappe.local.flags["ignore_root_company_validation"]
frappe.local.flags.pop("ignore_root_company_validation", None)
acc = frappe.new_doc("Account")
acc.account_name = "Test Sync Account"
acc.parent_account = "Temporary Accounts - _TC3"
@ -111,6 +111,55 @@ class TestAccount(unittest.TestCase):
self.assertEqual(acc_tc_4, "Test Sync Account - _TC4")
self.assertEqual(acc_tc_5, "Test Sync Account - _TC5")
def test_account_rename_sync(self):
frappe.local.flags.pop("ignore_root_company_validation", None)
acc = frappe.new_doc("Account")
acc.account_name = "Test Rename Account"
acc.parent_account = "Temporary Accounts - _TC3"
acc.company = "_Test Company 3"
acc.insert()
# Rename account in parent company
update_account_number(acc.name, "Test Rename Sync Account", "1234")
# Check if renamed in children
self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Rename Sync Account", "company": "_Test Company 4", "account_number": "1234"}))
self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Rename Sync Account", "company": "_Test Company 5", "account_number": "1234"}))
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC3")
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC4")
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC5")
def test_child_company_account_rename_sync(self):
frappe.local.flags.pop("ignore_root_company_validation", None)
acc = frappe.new_doc("Account")
acc.account_name = "Test Group Account"
acc.parent_account = "Temporary Accounts - _TC3"
acc.is_group = 1
acc.company = "_Test Company 3"
acc.insert()
self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Group Account", "company": "_Test Company 4"}))
self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Group Account", "company": "_Test Company 5"}))
# Try renaming child company account
acc_tc_5 = frappe.db.get_value('Account', {'account_name': "Test Group Account", "company": "_Test Company 5"})
self.assertRaises(frappe.ValidationError, update_account_number, acc_tc_5, "Test Modified Account")
# Rename child company account with allow_account_creation_against_child_company enabled
frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 1)
update_account_number(acc_tc_5, "Test Modified Account")
self.assertTrue(frappe.db.exists("Account", {'name': "Test Modified Account - _TC5", "company": "_Test Company 5"}))
frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 0)
to_delete = ["Test Group Account - _TC3", "Test Group Account - _TC4", "Test Modified Account - _TC5"]
for doc in to_delete:
frappe.delete_doc("Account", doc)
def _make_test_records(verbose):
from frappe.test_runner import make_test_objects

View File

@ -104,7 +104,7 @@
"default": "1",
"fieldname": "unlink_advance_payment_on_cancelation_of_order",
"fieldtype": "Check",
"label": "Unlink Advance Payment on Cancelation of Order"
"label": "Unlink Advance Payment on Cancellation of Order"
},
{
"default": "1",
@ -223,9 +223,10 @@
],
"icon": "icon-cog",
"idx": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2020-08-03 20:13:26.043092",
"modified": "2020-10-07 14:58:50.325577",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@ -1,785 +1,204 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"actions": [],
"allow_import": 1,
"allow_rename": 0,
"beta": 0,
"creation": "2016-05-16 11:42:29.632528",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"budget_against",
"company",
"cost_center",
"project",
"fiscal_year",
"column_break_3",
"monthly_distribution",
"amended_from",
"section_break_6",
"applicable_on_material_request",
"action_if_annual_budget_exceeded_on_mr",
"action_if_accumulated_monthly_budget_exceeded_on_mr",
"column_break_13",
"applicable_on_purchase_order",
"action_if_annual_budget_exceeded_on_po",
"action_if_accumulated_monthly_budget_exceeded_on_po",
"section_break_16",
"applicable_on_booking_actual_expenses",
"action_if_annual_budget_exceeded",
"action_if_accumulated_monthly_budget_exceeded",
"section_break_21",
"accounts"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Cost Center",
"fetch_if_empty": 0,
"fieldname": "budget_against",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Budget Against",
"length": 0,
"no_copy": 0,
"options": "\nCost Center\nProject",
"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,
"fetch_if_empty": 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": 1,
"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,
"depends_on": "eval:doc.budget_against == 'Cost Center'",
"fetch_if_empty": 0,
"fieldname": "cost_center",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Cost Center",
"length": 0,
"no_copy": 0,
"options": "Cost Center",
"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": "Cost Center"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.budget_against == 'Project'",
"fetch_if_empty": 0,
"fieldname": "project",
"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": 1,
"label": "Project",
"length": 0,
"no_copy": 0,
"options": "Project",
"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": "Project"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "fiscal_year",
"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": 1,
"label": "Fiscal Year",
"length": 0,
"no_copy": 0,
"options": "Fiscal Year",
"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,
"fetch_if_empty": 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,
"depends_on": "eval:in_list([\"Stop\", \"Warn\"], doc.action_if_accumulated_monthly_budget_exceeded_on_po || doc.action_if_accumulated_monthly_budget_exceeded_on_mr || doc.action_if_accumulated_monthly_budget_exceeded_on_actual)",
"fetch_if_empty": 0,
"fieldname": "monthly_distribution",
"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": "Monthly Distribution",
"length": 0,
"no_copy": 0,
"options": "Monthly Distribution",
"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": "Monthly Distribution"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "amended_from",
"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": "Amended From",
"length": 0,
"no_copy": 1,
"options": "Budget",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_6",
"fieldtype": "Section 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,
"label": "Control Action",
"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": "Control Action"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"default": "0",
"fieldname": "applicable_on_material_request",
"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": "Applicable on Material Request",
"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": "Applicable on Material Request"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Stop",
"depends_on": "eval:doc.applicable_on_material_request == 1",
"fetch_if_empty": 0,
"fieldname": "action_if_annual_budget_exceeded_on_mr",
"fieldtype": "Select",
"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": "Action if Annual Budget Exceeded on MR",
"length": 0,
"no_copy": 0,
"options": "\nStop\nWarn\nIgnore",
"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": "\nStop\nWarn\nIgnore"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Warn",
"depends_on": "eval:doc.applicable_on_material_request == 1",
"fetch_if_empty": 0,
"fieldname": "action_if_accumulated_monthly_budget_exceeded_on_mr",
"fieldtype": "Select",
"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": "Action if Accumulated Monthly Budget Exceeded on MR",
"length": 0,
"no_copy": 0,
"options": "\nStop\nWarn\nIgnore",
"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": "\nStop\nWarn\nIgnore"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_13",
"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,
"fetch_if_empty": 0,
"default": "0",
"fieldname": "applicable_on_purchase_order",
"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": "Applicable on Purchase Order",
"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": "Applicable on Purchase Order"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Stop",
"depends_on": "eval:doc.applicable_on_purchase_order == 1",
"fetch_if_empty": 0,
"fieldname": "action_if_annual_budget_exceeded_on_po",
"fieldtype": "Select",
"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": "Action if Annual Budget Exceeded on PO",
"length": 0,
"no_copy": 0,
"options": "\nStop\nWarn\nIgnore",
"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": "\nStop\nWarn\nIgnore"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Warn",
"depends_on": "eval:doc.applicable_on_purchase_order == 1",
"fetch_if_empty": 0,
"fieldname": "action_if_accumulated_monthly_budget_exceeded_on_po",
"fieldtype": "Select",
"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": "Action if Accumulated Monthly Budget Exceeded on PO",
"length": 0,
"no_copy": 0,
"options": "\nStop\nWarn\nIgnore",
"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": "\nStop\nWarn\nIgnore"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_16",
"fieldtype": "Section 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": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"default": "0",
"fieldname": "applicable_on_booking_actual_expenses",
"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": "Applicable on booking actual expenses",
"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": "Applicable on booking actual expenses"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Stop",
"depends_on": "eval:doc.applicable_on_booking_actual_expenses == 1",
"fetch_if_empty": 0,
"fieldname": "action_if_annual_budget_exceeded",
"fieldtype": "Select",
"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": "Action if Annual Budget Exceeded on Actual",
"length": 0,
"no_copy": 0,
"options": "\nStop\nWarn\nIgnore",
"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": "\nStop\nWarn\nIgnore"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Warn",
"depends_on": "eval:doc.applicable_on_booking_actual_expenses == 1",
"fetch_if_empty": 0,
"fieldname": "action_if_accumulated_monthly_budget_exceeded",
"fieldtype": "Select",
"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": "Action if Accumulated Monthly Budget Exceeded on Actual",
"length": 0,
"no_copy": 0,
"options": "\nStop\nWarn\nIgnore",
"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": "\nStop\nWarn\nIgnore"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_21",
"fieldtype": "Section 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": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "accounts",
"fieldtype": "Table",
"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": "Budget Accounts",
"length": 0,
"no_copy": 0,
"options": "Budget Account",
"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
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"index_web_pages_for_search": 1,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-03-22 12:06:02.323099",
"links": [],
"modified": "2020-10-06 15:13:54.055854",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Budget",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
@ -789,26 +208,17 @@
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
"track_changes": 1
}

View File

@ -22,8 +22,12 @@ class JournalEntry(AccountsController):
return self.voucher_type
def validate(self):
if self.voucher_type == 'Opening Entry':
self.is_opening = 'Yes'
if not self.is_opening:
self.is_opening='No'
self.clearance_date = None
self.validate_party()

View File

@ -195,88 +195,91 @@ def create_sales_invoice_record(qty=1):
def create_records():
# create a new loyalty Account
if frappe.db.exists("Account", "Loyalty - _TC"):
return
frappe.get_doc({
"doctype": "Account",
"account_name": "Loyalty",
"parent_account": "Direct Expenses - _TC",
"company": "_Test Company",
"is_group": 0,
"account_type": "Expense Account",
}).insert()
if not frappe.db.exists("Account", "Loyalty - _TC"):
frappe.get_doc({
"doctype": "Account",
"account_name": "Loyalty",
"parent_account": "Direct Expenses - _TC",
"company": "_Test Company",
"is_group": 0,
"account_type": "Expense Account",
}).insert()
# create a new loyalty program Single tier
frappe.get_doc({
"doctype": "Loyalty Program",
"loyalty_program_name": "Test Single Loyalty",
"auto_opt_in": 1,
"from_date": today(),
"loyalty_program_type": "Single Tier Program",
"conversion_factor": 1,
"expiry_duration": 10,
"company": "_Test Company",
"cost_center": "Main - _TC",
"expense_account": "Loyalty - _TC",
"collection_rules": [{
'tier_name': 'Silver',
'collection_factor': 1000,
'min_spent': 1000
}]
}).insert()
# create a new customer
frappe.get_doc({
"customer_group": "_Test Customer Group",
"customer_name": "Test Loyalty Customer",
"customer_type": "Individual",
"doctype": "Customer",
"territory": "_Test Territory"
}).insert()
# create a new loyalty program Multiple tier
frappe.get_doc({
"doctype": "Loyalty Program",
"loyalty_program_name": "Test Multiple Loyalty",
"auto_opt_in": 1,
"from_date": today(),
"loyalty_program_type": "Multiple Tier Program",
"conversion_factor": 1,
"expiry_duration": 10,
"company": "_Test Company",
"cost_center": "Main - _TC",
"expense_account": "Loyalty - _TC",
"collection_rules": [
{
if not frappe.db.exists("Loyalty Program","Test Single Loyalty"):
frappe.get_doc({
"doctype": "Loyalty Program",
"loyalty_program_name": "Test Single Loyalty",
"auto_opt_in": 1,
"from_date": today(),
"loyalty_program_type": "Single Tier Program",
"conversion_factor": 1,
"expiry_duration": 10,
"company": "_Test Company",
"cost_center": "Main - _TC",
"expense_account": "Loyalty - _TC",
"collection_rules": [{
'tier_name': 'Silver',
'collection_factor': 1000,
'min_spent': 10000
},
{
'tier_name': 'Gold',
'collection_factor': 1000,
'min_spent': 19000
}
]
}).insert()
'min_spent': 1000
}]
}).insert()
# create a new customer
if not frappe.db.exists("Customer","Test Loyalty Customer"):
frappe.get_doc({
"customer_group": "_Test Customer Group",
"customer_name": "Test Loyalty Customer",
"customer_type": "Individual",
"doctype": "Customer",
"territory": "_Test Territory"
}).insert()
# create a new loyalty program Multiple tier
if not frappe.db.exists("Loyalty Program","Test Multiple Loyalty"):
frappe.get_doc({
"doctype": "Loyalty Program",
"loyalty_program_name": "Test Multiple Loyalty",
"auto_opt_in": 1,
"from_date": today(),
"loyalty_program_type": "Multiple Tier Program",
"conversion_factor": 1,
"expiry_duration": 10,
"company": "_Test Company",
"cost_center": "Main - _TC",
"expense_account": "Loyalty - _TC",
"collection_rules": [
{
'tier_name': 'Silver',
'collection_factor': 1000,
'min_spent': 10000
},
{
'tier_name': 'Gold',
'collection_factor': 1000,
'min_spent': 19000
}
]
}).insert()
# create an item
item = frappe.get_doc({
"doctype": "Item",
"item_code": "Loyal Item",
"item_name": "Loyal Item",
"item_group": "All Item Groups",
"company": "_Test Company",
"is_stock_item": 1,
"opening_stock": 100,
"valuation_rate": 10000,
}).insert()
if not frappe.db.exists("Item", "Loyal Item"):
frappe.get_doc({
"doctype": "Item",
"item_code": "Loyal Item",
"item_name": "Loyal Item",
"item_group": "All Item Groups",
"company": "_Test Company",
"is_stock_item": 1,
"opening_stock": 100,
"valuation_rate": 10000,
}).insert()
# create item price
frappe.get_doc({
"doctype": "Item Price",
"price_list": "Standard Selling",
"item_code": item.item_code,
"price_list_rate": 10000
}).insert()
if not frappe.db.exists("Item Price", {"price_list": "Standard Selling", "item_code": "Loyal Item"}):
frappe.get_doc({
"doctype": "Item Price",
"price_list": "Standard Selling",
"item_code": "Loyal Item",
"price_list_rate": 10000
}).insert()

View File

@ -1,12 +1,14 @@
frappe.listview_settings['Payment Entry'] = {
onload: function(listview) {
listview.page.fields_dict.party_type.get_query = function() {
return {
"filters": {
"name": ["in", Object.keys(frappe.boot.party_account_types)],
}
if (listview.page.fields_dict.party_type) {
listview.page.fields_dict.party_type.get_query = function() {
return {
"filters": {
"name": ["in", Object.keys(frappe.boot.party_account_types)],
}
};
};
};
}
}
};

View File

@ -45,7 +45,7 @@ class TestPOSClosingEntry(unittest.TestCase):
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
def init_user_and_profile():
def init_user_and_profile(**args):
user = 'test@example.com'
test_user = frappe.get_doc('User', user)
@ -53,7 +53,7 @@ def init_user_and_profile():
test_user.add_roles(*roles)
frappe.set_user(user)
pos_profile = make_pos_profile()
pos_profile = make_pos_profile(**args)
pos_profile.append('applicable_for_users', {
'default': 1,
'user': user

View File

@ -139,7 +139,8 @@ class POSInvoice(SalesInvoice):
frappe.throw(_("At least one mode of payment is required for POS invoice."))
def validate_change_account(self):
if frappe.db.get_value("Account", self.account_for_change_amount, "company") != self.company:
if self.change_amount and self.account_for_change_amount and \
frappe.db.get_value("Account", self.account_for_change_amount, "company") != self.company:
frappe.throw(_("The selected change account {} doesn't belongs to Company {}.").format(self.account_for_change_amount, self.company))
def validate_change_amount(self):

View File

@ -7,6 +7,8 @@ import frappe
import unittest, copy, time
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
class TestPOSInvoice(unittest.TestCase):
def test_timestamp_change(self):
@ -221,29 +223,29 @@ class TestPOSInvoice(unittest.TestCase):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
se = make_serialized_item(company='_Test Company with perpetual inventory',
target_warehouse="Stores - TCP1", cost_center='Main - TCP1', expense_account='Cost of Goods Sold - TCP1')
se = make_serialized_item(company='_Test Company',
target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC')
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
pos = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1',
account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1',
expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1',
pos = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
pos.get("items")[0].serial_no = serial_nos[0]
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 1000})
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
pos.insert()
pos.submit()
pos2 = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1',
account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1',
expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1',
pos2 = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
pos2.get("items")[0].serial_no = serial_nos[0]
pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 1000})
pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
self.assertRaises(frappe.ValidationError, pos2.insert)
@ -286,6 +288,119 @@ class TestPOSInvoice(unittest.TestCase):
after_redeem_lp_details = get_loyalty_program_details_with_points(inv.customer, company=inv.company, loyalty_program=inv.loyalty_program)
self.assertEqual(after_redeem_lp_details.loyalty_points, 9)
def test_merging_into_sales_invoice_with_discount(self):
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(rate=300, additional_discount_percentage=10, do_not_submit=1)
pos_inv.append('payments', {
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 270
})
pos_inv.submit()
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
pos_inv2.append('payments', {
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
})
pos_inv2.submit()
merge_pos_invoices()
pos_inv.load_from_db()
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
self.assertEqual(rounded_total, 3470)
frappe.set_user("Administrator")
def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
pos_inv.append('payments', {
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
})
pos_inv.append('taxes', {
"charge_type": "On Net Total",
"account_head": "_Test Account Service Tax - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "Service Tax",
"rate": 14,
'included_in_print_rate': 1
})
pos_inv.submit()
pos_inv2 = create_pos_invoice(rate=300, qty=2, do_not_submit=1)
pos_inv2.additional_discount_percentage = 10
pos_inv2.append('payments', {
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 540
})
pos_inv2.append('taxes', {
"charge_type": "On Net Total",
"account_head": "_Test Account Service Tax - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "Service Tax",
"rate": 14,
'included_in_print_rate': 1
})
pos_inv2.submit()
merge_pos_invoices()
pos_inv.load_from_db()
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
self.assertEqual(rounded_total, 840)
frappe.set_user("Administrator")
def test_merging_with_validate_selling_price(self):
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1)
make_purchase_receipt(item_code="_Test Item", warehouse="_Test Warehouse - _TC", qty=1, rate=300)
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
pos_inv.append('payments', {
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
})
pos_inv.append('taxes', {
"charge_type": "On Net Total",
"account_head": "_Test Account Service Tax - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "Service Tax",
"rate": 14,
'included_in_print_rate': 1
})
self.assertRaises(frappe.ValidationError, pos_inv.submit)
pos_inv2 = create_pos_invoice(rate=400, do_not_submit=1)
pos_inv2.append('payments', {
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 400
})
pos_inv2.append('taxes', {
"charge_type": "On Net Total",
"account_head": "_Test Account Service Tax - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "Service Tax",
"rate": 14,
'included_in_print_rate': 1
})
pos_inv2.submit()
merge_pos_invoices()
pos_inv2.load_from_db()
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total")
self.assertEqual(rounded_total, 400)
frappe.set_user("Administrator")
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 0)
def create_pos_invoice(**args):
args = frappe._dict(args)
pos_profile = None
@ -294,12 +409,11 @@ def create_pos_invoice(**args):
pos_profile.save()
pos_inv = frappe.new_doc("POS Invoice")
pos_inv.update(args)
pos_inv.update_stock = 1
pos_inv.is_pos = 1
pos_inv.pos_profile = args.pos_profile or pos_profile.name
pos_inv.set_missing_values()
if args.posting_date:
pos_inv.set_posting_time = 1
pos_inv.posting_date = args.posting_date or frappe.utils.nowdate()
@ -313,6 +427,8 @@ def create_pos_invoice(**args):
pos_inv.conversion_rate = args.conversion_rate or 1
pos_inv.account_for_change_amount = args.account_for_change_amount or "Cash - _TC"
pos_inv.set_missing_values()
pos_inv.append("items", {
"item_code": args.item or args.item_code or "_Test Item",
"warehouse": args.warehouse or "_Test Warehouse - _TC",

View File

@ -96,17 +96,28 @@ class POSInvoiceMergeLog(Document):
loyalty_amount_sum += doc.loyalty_amount
for item in doc.get('items'):
items.append(item)
found = False
for i in items:
if (i.item_code == item.item_code and not i.serial_no and not i.batch_no and
i.uom == item.uom and i.net_rate == item.net_rate):
found = True
i.qty = i.qty + item.qty
if not found:
item.rate = item.net_rate
items.append(item)
for tax in doc.get('taxes'):
found = False
for t in taxes:
if t.account_head == tax.account_head and t.cost_center == tax.cost_center and t.rate == tax.rate:
t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount)
t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount)
if t.account_head == tax.account_head and t.cost_center == tax.cost_center:
t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount)
t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount)
found = True
if not found:
tax.charge_type = 'Actual'
tax.included_in_print_rate = 0
tax.tax_amount = tax.tax_amount_after_discount_amount
tax.base_tax_amount = tax.base_tax_amount_after_discount_amount
taxes.append(tax)
for payment in doc.get('payments'):
@ -127,6 +138,8 @@ class POSInvoiceMergeLog(Document):
invoice.set('items', items)
invoice.set('payments', payments)
invoice.set('taxes', taxes)
invoice.additional_discount_percentage = 0
invoice.discount_amount = 0.0
return invoice

View File

@ -15,15 +15,6 @@ frappe.ui.form.on("POS Profile", "onload", function(frm) {
erpnext.queries.setup_queries(frm, "Warehouse", function() {
return erpnext.queries.warehouse(frm.doc);
});
frm.call({
method: "erpnext.accounts.doctype.pos_profile.pos_profile.get_series",
callback: function(r) {
if(!r.exc) {
set_field_options("naming_series", r.message);
}
}
});
});
frappe.ui.form.on('POS Profile', {

View File

@ -8,7 +8,6 @@
"field_order": [
"disabled",
"section_break_2",
"naming_series",
"customer",
"company",
"country",
@ -59,17 +58,6 @@
"fieldname": "section_break_2",
"fieldtype": "Section Break"
},
{
"fieldname": "naming_series",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Series",
"no_copy": 1,
"oldfieldname": "naming_series",
"oldfieldtype": "Select",
"options": "[Select]",
"reqd": 1
},
{
"fieldname": "customer",
"fieldtype": "Link",
@ -323,7 +311,7 @@
"icon": "icon-cog",
"idx": 1,
"links": [],
"modified": "2020-06-29 12:20:30.977272",
"modified": "2020-10-01 17:29:27.759088",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",

View File

@ -109,10 +109,6 @@ def get_child_nodes(group_type, root):
return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where
lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1)
@frappe.whitelist()
def get_series():
return frappe.get_meta("POS Invoice").get_field("naming_series").options or "s"
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):

View File

@ -1,4 +1,5 @@
{
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:title",
@ -71,6 +72,7 @@
"section_break_13",
"threshold_percentage",
"priority",
"condition",
"column_break_66",
"apply_multiple_pricing_rules",
"apply_discount_on_rate",
@ -550,11 +552,18 @@
"fieldtype": "Link",
"label": "Promotional Scheme",
"options": "Promotional Scheme"
},
{
"description": "Simple Python Expression, Example: territory != 'All Territories'",
"fieldname": "condition",
"fieldtype": "Code",
"label": "Condition"
}
],
"icon": "fa fa-gift",
"idx": 1,
"modified": "2019-12-18 17:29:22.957077",
"links": [],
"modified": "2020-08-26 12:24:44.740734",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",

View File

@ -6,9 +6,10 @@ from __future__ import unicode_literals
import frappe
import json
import copy
import re
from frappe import throw, _
from frappe.utils import flt, cint, getdate
from frappe.model.document import Document
from six import string_types
@ -30,6 +31,7 @@ class PricingRule(Document):
self.validate_max_discount()
self.validate_price_list_with_currency()
self.validate_dates()
self.validate_condition()
if not self.margin_type: self.margin_rate_or_amount = 0.0
@ -140,6 +142,10 @@ class PricingRule(Document):
if self.valid_from and self.valid_upto and getdate(self.valid_from) > getdate(self.valid_upto):
frappe.throw(_("Valid from date must be less than valid upto date"))
def validate_condition(self):
if self.condition and ("=" in self.condition) and re.match("""[\w\.:_]+\s*={1}\s*[\w\.@'"]+""", self.condition):
frappe.throw(_("Invalid condition expression"))
#--------------------------------------------------------------------------------
@frappe.whitelist()

View File

@ -430,6 +430,33 @@ class TestPricingRule(unittest.TestCase):
self.assertTrue(details)
def test_pricing_rule_for_condition(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
make_pricing_rule(selling=1, margin_type="Percentage", \
condition="customer=='_Test Customer 1' and is_return==0", discount_percentage=10)
# Incorrect Customer and Correct is_return value
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 2", is_return=0)
si.items[0].price_list_rate = 1000
si.submit()
item = si.items[0]
self.assertEquals(item.rate, 100)
# Correct Customer and Incorrect is_return value
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=1, qty=-1)
si.items[0].price_list_rate = 1000
si.submit()
item = si.items[0]
self.assertEquals(item.rate, 100)
# Correct Customer and correct is_return value
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=0)
si.items[0].price_list_rate = 1000
si.submit()
item = si.items[0]
self.assertEquals(item.rate, 900)
def make_pricing_rule(**args):
args = frappe._dict(args)
@ -448,7 +475,8 @@ def make_pricing_rule(**args):
"discount_percentage": args.discount_percentage or 0.0,
"rate": args.rate or 0.0,
"margin_type": args.margin_type,
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
"condition": args.condition or ''
})
apply_on = doc.apply_on.replace(' ', '_').lower()

View File

@ -37,6 +37,8 @@ def get_pricing_rules(args, doc=None):
rules = []
pricing_rules = filter_pricing_rule_based_on_condition(pricing_rules, doc)
if not pricing_rules: return []
if apply_multiple_pricing_rules(pricing_rules):
@ -51,6 +53,23 @@ def get_pricing_rules(args, doc=None):
return rules
def filter_pricing_rule_based_on_condition(pricing_rules, doc=None):
filtered_pricing_rules = []
if doc:
for pricing_rule in pricing_rules:
if pricing_rule.condition:
try:
if frappe.safe_eval(pricing_rule.condition, None, doc.as_dict()):
filtered_pricing_rules.append(pricing_rule)
except:
pass
else:
filtered_pricing_rules.append(pricing_rule)
else:
filtered_pricing_rules = pricing_rules
return filtered_pricing_rules
def _get_pricing_rules(apply_on, args, values):
apply_on_field = frappe.scrub(apply_on)

View File

@ -711,7 +711,8 @@ class PurchaseInvoice(BuyingController):
item.item_tax_amount / self.conversion_rate)
}, item=item))
else:
cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company)
cwip_account = get_asset_account("capital_work_in_progress_account",
asset_category=item.asset_category,company=self.company)
cwip_account_currency = get_account_currency(cwip_account)
gl_entries.append(self.get_gl_dict({

View File

@ -1002,7 +1002,8 @@ def make_purchase_invoice(**args):
"cost_center": args.cost_center or "_Test Cost Center - _TC",
"project": args.project,
"rejected_warehouse": args.rejected_warehouse or "",
"rejected_serial_no": args.rejected_serial_no or ""
"rejected_serial_no": args.rejected_serial_no or "",
"asset_location": args.location or ""
})
if args.get_taxes_and_charges:

View File

@ -19,6 +19,7 @@
"is_return",
"column_break1",
"company",
"company_tax_id",
"posting_date",
"posting_time",
"set_posting_time",
@ -1825,7 +1826,7 @@
"fieldtype": "Table",
"hide_days": 1,
"hide_seconds": 1,
"label": "Sales Team1",
"label": "Sales Contributions and Incentives",
"oldfieldname": "sales_team",
"oldfieldtype": "Table",
"options": "Sales Team",
@ -1926,6 +1927,7 @@
},
{
"default": "0",
"depends_on": "eval:(doc.is_pos && doc.is_consolidated)",
"fieldname": "is_consolidated",
"fieldtype": "Check",
"label": "Is Consolidated",
@ -1940,13 +1942,20 @@
"hide_seconds": 1,
"label": "Is Internal Customer",
"read_only": 1
},
{
"fetch_from": "company.tax_id",
"fieldname": "company_tax_id",
"fieldtype": "Data",
"label": "Company Tax ID",
"read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 181,
"is_submittable": 1,
"links": [],
"modified": "2020-08-27 01:56:28.532140",
"modified": "2020-10-09 15:59:57.544736",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@ -428,7 +428,7 @@ class SalesInvoice(SellingController):
if pos.get('account_for_change_amount'):
self.account_for_change_amount = pos.get('account_for_change_amount')
for fieldname in ('naming_series', 'currency', 'letter_head', 'tc_name',
for fieldname in ('currency', 'letter_head', 'tc_name',
'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges',
'write_off_cost_center', 'apply_discount_on', 'cost_center'):
if (not for_validate) or (for_validate and not self.get(fieldname)):

View File

@ -13,8 +13,7 @@ def get_data():
'Auto Repeat': 'reference_document',
},
'internal_links': {
'Sales Order': ['items', 'sales_order'],
'Delivery Note': ['items', 'delivery_note']
'Sales Order': ['items', 'sales_order']
},
'transactions': [
{

View File

@ -345,13 +345,14 @@ class Subscription(Document):
invoice.set_taxes()
# Due date
invoice.append(
'payment_schedule',
{
'due_date': add_days(invoice.posting_date, cint(self.days_until_due)),
'invoice_portion': 100
}
)
if self.days_until_due:
invoice.append(
'payment_schedule',
{
'due_date': add_days(invoice.posting_date, cint(self.days_until_due)),
'invoice_portion': 100
}
)
# Discounts
if self.additional_discount_percentage:

View File

@ -6,6 +6,8 @@ from __future__ import unicode_literals
import frappe
import unittest
from erpnext.accounts.doctype.tax_rule.tax_rule import IncorrectCustomerGroup, IncorrectSupplierType, ConflictingTaxRule, get_tax_template
from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity
from erpnext.crm.doctype.opportunity.opportunity import make_quotation
test_records = frappe.get_test_records('Tax Rule')
@ -144,6 +146,23 @@ class TestTaxRule(unittest.TestCase):
self.assertEqual(get_tax_template("2015-01-01", {"customer":"_Test Customer", "billing_city": "Test City 1"}),
"_Test Sales Taxes and Charges Template 1 - _TC")
def test_taxes_fetch_via_tax_rule(self):
make_tax_rule(customer= "_Test Customer", billing_city = "_Test City",
sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", save=1)
# create opportunity for customer
opportunity = make_opportunity(with_items=1)
# make quotation from opportunity
quotation = make_quotation(opportunity.name)
quotation.save()
self.assertEqual(quotation.taxes_and_charges, "_Test Sales Taxes and Charges Template - _TC")
# Check if accounts heads and rate fetched are also fetched from tax template or not
self.assertTrue(len(quotation.taxes) > 0)
def make_tax_rule(**args):
args = frappe._dict(args)

View File

@ -106,6 +106,7 @@ def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_detai
from `tabGL Entry`
where company = %s and
party in %s and fiscal_year=%s and credit > 0
and is_opening = 'No'
""", (company, tuple(suppliers), fiscal_year), as_dict=1)
vouchers = [d.voucher_no for d in entries]
@ -192,6 +193,7 @@ def get_advance_vouchers(suppliers, fiscal_year=None, company=None, from_date=No
select distinct voucher_no
from `tabGL Entry`
where party in %s and %s and debit > 0
and is_opening = 'No'
""", (tuple(suppliers), condition)) or []
def get_debit_note_amount(suppliers, year_start_date, year_end_date, company=None):

View File

@ -13,7 +13,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/accounts",
"idx": 0,
"is_complete": 0,
"modified": "2020-07-08 14:06:09.033880",
"modified": "2020-10-15 13:52:40.068450",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts",

View File

@ -1,14 +1,18 @@
{
"action": "Go to Page",
"callback_message": "Great! Let's move to the next step!",
"callback_title": "Awesome Work",
"creation": "2020-05-13 19:58:20.928127",
"description": "# Chart Of Accounts\n\n**The Chart of Accounts is the blueprint of the accounts in your organization.**\n\nThe overall structure of your Chart of Accounts is based on a system of double entry\naccounting that has become a standard all over the world to quantify how a\ncompany is doing financially.\n\nChart of Accounts is a tree view of the names of the Accounts (Ledgers and\nGroups) that a Company requires to manage its books of accounts. ERPNext sets\nup a simple chart of accounts for each Company you create, but you can\nmodify it according to your needs and legal requirements.\n\nFor each company, Chart of Accounts signifies the way to classify the accounting entries, mostly\nbased on statutory (tax, compliance to government regulations) requirements.\n\nThe Chart of Accounts helps you to answer questions like:\n\n * What is your organization worth?\n * How much debt have you taken?\n * How much profit are you making (and hence paying tax)?\n * How much are you selling?\n * What is your expense break-up?\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"intro_video_url": "https://www.youtube.com/embed/AcfMCT7wLLo",
"is_complete": 0,
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-14 17:40:28.410447",
"modified": "2020-10-15 13:12:04.771355",
"modified_by": "Administrator",
"name": "Chart Of Accounts",
"owner": "Administrator",

View File

@ -1,6 +1,7 @@
{
"action": "Create Entry",
"creation": "2020-05-14 17:53:00.876946",
"description": "# Account Settings\n\nThis is a crucial piece of configuration. There are various account settings in ERPNext to restrict and configure actions in the Accounting module.\n\nThe following settings are avaialble for you to configure\n\n1. Account Freezing \n2. Credit and Overbilling\n3. Invoicing and Tax Automations\n4. Balance Sheet configurations\n\nThere's much more, you can check it all out in this step",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
@ -8,7 +9,7 @@
"is_mandatory": 0,
"is_single": 1,
"is_skipped": 0,
"modified": "2020-05-14 18:06:25.212923",
"modified": "2020-10-15 13:45:14.687458",
"modified_by": "Administrator",
"name": "Configure Account Settings",
"owner": "Administrator",

View File

@ -1,14 +1,16 @@
{
"action": "Create Entry",
"creation": "2020-05-14 17:46:41.831517",
"description": "# Customer\n\nA customer, who is sometimes known as a client, buyer, or purchaser is the one who receives goods, services, products, or ideas, from a seller for a monetary consideration.\n\n### Creating a customer is easy and can be done in the following steps\n\n1. Go to the Customer list and click on New.\n2. Enter Full Name of the customer.\n3. Select **Company** if the customer represents a company or **Individual** otherwise in Type field.\n4. Select a Customer Group. A few groups are included by default, you can create additional groups if you need.\n5. Select the Territory.\n6. If the customer is being created against a lead, you can select the same in From Lead field.\n7. Save.\n\nUp next is a video about customers and suppliers that will give you more clarity on these concepts in ERPNext",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"intro_video_url": "https://www.youtube.com/watch?v=zsrrVDk6VBs",
"is_complete": 0,
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-06-01 13:16:19.731719",
"modified": "2020-10-15 13:37:59.699228",
"modified_by": "Administrator",
"name": "Create a Customer",
"owner": "Administrator",

View File

@ -1,6 +1,7 @@
{
"action": "Create Entry",
"creation": "2020-05-12 18:16:06.624554",
"description": "## Creating Products\n\nIn ERPNext, any product or a service offered by your company is called an Item. The term Item is also applicable to raw materials or components of products yet to be produced (before they can be sold to customers). ERPNext allows you to manage all sorts of items like raw-materials, sub-assemblies, finished goods, item variants, and service items.\n\nERPNext is optimized for itemized management of your sales and purchase. If you are in services, you can create an Item for each service that you offer. Completing the Item Master is very essential for the successful implementation of ERPNext.\n\nYou can access the Item section\n\n`Desk > Stock > Item`\n\n\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
@ -8,7 +9,7 @@
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-12 18:30:02.489949",
"modified": "2020-10-15 13:30:46.817174",
"modified_by": "Administrator",
"name": "Create a Product",
"owner": "Administrator",

View File

@ -1,6 +1,7 @@
{
"action": "Create Entry",
"creation": "2020-05-14 22:09:10.043554",
"description": "## Let's add your Suppliers\n\nSuppliers are companies or individuals who provide you with products or services. A supplier may be distinguished from a contractor or subcontractor, who commonly adds specialized input to deliverables. A supplier is also known as a vendor. There are different types of suppliers based on the goods and products they supply.\n\nERPNext allows you to create your own categories of suppliers. These categories are known as Supplier Groups. For example, if your suppliers are mainly pharmaceutical companies and FMCG distributors, you can create a new Supplier Groups for them and name the groups accordingly.",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
@ -8,7 +9,7 @@
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-14 22:09:10.043554",
"modified": "2020-10-15 13:32:39.651700",
"modified_by": "Administrator",
"name": "Create a Supplier",
"owner": "Administrator",

View File

@ -1,6 +1,7 @@
{
"action": "Create Entry",
"creation": "2020-05-14 22:10:07.049704",
"description": "# What's a Purchase Invoice?\n\nA Purchase Invoice is a bill you receive from your Suppliers against which you need to make the payment.\nPurchase Invoice is the exact opposite of your Sales Invoice. Here you accrue expenses to your Supplier. Making a Purchase Invoice is very similar to making a Purchase Order.\n\n![Purchase Flow](https://docs.erpnext.com/docs/assets/img/accounts/pi-flow.png)\n\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
@ -8,7 +9,7 @@
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-14 22:10:07.049704",
"modified": "2020-10-15 13:33:56.079882",
"modified_by": "Administrator",
"name": "Create Your First Purchase Invoice",
"owner": "Administrator",

View File

@ -1,6 +1,7 @@
{
"action": "Create Entry",
"creation": "2020-05-14 17:48:21.019019",
"description": "# All about sales invoice\n\nA Sales Invoice is a bill that you send to your Customers against which the Customer makes the payment. Sales Invoice is an accounting transaction. On submission of Sales Invoice, the system updates the receivable and books income against a Customer Account.\n\nHere's the flow of how a sales invoice is generally created\n\n\n![Sales Flow](https://docs.erpnext.com/docs/assets/img/accounts/so-flow.png)",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
@ -8,7 +9,7 @@
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-14 17:48:21.019019",
"modified": "2020-10-15 13:39:43.970254",
"modified_by": "Administrator",
"name": "Create Your First Sales Invoice",
"owner": "Administrator",

View File

@ -1,6 +1,7 @@
{
"action": "Create Entry",
"creation": "2020-05-13 19:29:43.844463",
"description": "# Setting up Taxes\n\nOne of the primary motivators for compulsory use of accounting tools is the calculation of taxes. ERPNext allows you to make configurable tax templates that you can apply to your sales or purchase transactions.\n\nThe templates created from this form can be used in Sales Orders and Sales Invoices. The way ERPNext sets up taxes is via templates. Other types of charges that may apply to your invoices (like shipping, insurance etc.) can also be configured as taxes.\n\nFor Tax Accounts that you want to use in the tax templates, go to:\n\n`> Home > Accounting > Chart of Accounts`\n\nSelect an account and click on edit. Select the 'Account Type' as 'Tax' for the account.\nIn this step we will guide you towards making the sales and taxes template.",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
@ -8,7 +9,7 @@
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-14 17:40:16.014413",
"modified": "2020-10-15 13:21:17.333438",
"modified_by": "Administrator",
"name": "Setup Taxes",
"owner": "Administrator",

View File

@ -3,6 +3,14 @@
frappe.query_reports["Bank Reconciliation Statement"] = {
"filters": [
{
"fieldname":"company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
"reqd": 1,
"default": frappe.defaults.get_user_default("Company")
},
{
"fieldname":"account",
"label": __("Bank Account"),
@ -12,11 +20,14 @@ frappe.query_reports["Bank Reconciliation Statement"] = {
locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "",
"reqd": 1,
"get_query": function() {
var company = frappe.query_report.get_filter_value('company')
return {
"query": "erpnext.controllers.queries.get_account_list",
"filters": [
['Account', 'account_type', 'in', 'Bank, Cash'],
['Account', 'is_group', '=', 0],
['Account', 'disabled', '=', 0],
['Account', 'company', '=', company],
]
}
}

View File

@ -0,0 +1,76 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["POS Register"] = {
"filters": [
{
"fieldname":"company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
"default": frappe.defaults.get_user_default("Company"),
"reqd": 1
},
{
"fieldname":"from_date",
"label": __("From Date"),
"fieldtype": "Date",
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
"reqd": 1,
"width": "60px"
},
{
"fieldname":"to_date",
"label": __("To Date"),
"fieldtype": "Date",
"default": frappe.datetime.get_today(),
"reqd": 1,
"width": "60px"
},
{
"fieldname":"pos_profile",
"label": __("POS Profile"),
"fieldtype": "Link",
"options": "POS Profile"
},
{
"fieldname":"cashier",
"label": __("Cashier"),
"fieldtype": "Link",
"options": "User"
},
{
"fieldname":"customer",
"label": __("Customer"),
"fieldtype": "Link",
"options": "Customer"
},
{
"fieldname":"mode_of_payment",
"label": __("Payment Method"),
"fieldtype": "Link",
"options": "Mode of Payment"
},
{
"fieldname":"group_by",
"label": __("Group by"),
"fieldtype": "Select",
"options": ["", "POS Profile", "Cashier", "Payment Method", "Customer"],
"default": "POS Profile"
},
{
"fieldname":"is_return",
"label": __("Is Return"),
"fieldtype": "Check"
},
],
"formatter": function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (data && data.bold) {
value = value.bold();
}
return value;
}
};

View File

@ -0,0 +1,30 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2020-09-10 19:25:03.766871",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"json": "{}",
"modified": "2020-09-10 19:25:15.851331",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Register",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "POS Invoice",
"report_name": "POS Register",
"report_type": "Script Report",
"roles": [
{
"role": "Accounts Manager"
},
{
"role": "Accounts User"
}
]
}

View File

@ -0,0 +1,222 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _, _dict
from erpnext import get_company_currency, get_default_company
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
def execute(filters=None):
if not filters:
return [], []
validate_filters(filters)
columns = get_columns(filters)
group_by_field = get_group_by_field(filters.get("group_by"))
pos_entries = get_pos_entries(filters, group_by_field)
if group_by_field != "mode_of_payment":
concat_mode_of_payments(pos_entries)
# return only entries if group by is unselected
if not group_by_field:
return columns, pos_entries
# handle grouping
invoice_map, grouped_data = {}, []
for d in pos_entries:
invoice_map.setdefault(d[group_by_field], []).append(d)
for key in invoice_map:
invoices = invoice_map[key]
grouped_data += invoices
add_subtotal_row(grouped_data, invoices, group_by_field, key)
# move group by column to first position
column_index = next((index for (index, d) in enumerate(columns) if d["fieldname"] == group_by_field), None)
columns.insert(0, columns.pop(column_index))
return columns, grouped_data
def get_pos_entries(filters, group_by_field):
conditions = get_conditions(filters)
order_by = "p.posting_date"
select_mop_field, from_sales_invoice_payment, group_by_mop_condition = "", "", ""
if group_by_field == "mode_of_payment":
select_mop_field = ", sip.mode_of_payment"
from_sales_invoice_payment = ", `tabSales Invoice Payment` sip"
group_by_mop_condition = "sip.parent = p.name AND ifnull(sip.base_amount, 0) != 0 AND"
order_by += ", sip.mode_of_payment"
elif group_by_field:
order_by += ", p.{}".format(group_by_field)
return frappe.db.sql(
"""
SELECT
p.posting_date, p.name as pos_invoice, p.pos_profile,
p.owner, p.base_grand_total as grand_total, p.base_paid_amount as paid_amount,
p.customer, p.is_return {select_mop_field}
FROM
`tabPOS Invoice` p {from_sales_invoice_payment}
WHERE
{group_by_mop_condition}
{conditions}
ORDER BY
{order_by}
""".format(
select_mop_field=select_mop_field,
from_sales_invoice_payment=from_sales_invoice_payment,
group_by_mop_condition=group_by_mop_condition,
conditions=conditions,
order_by=order_by
), filters, as_dict=1)
def concat_mode_of_payments(pos_entries):
mode_of_payments = get_mode_of_payments(set([d.pos_invoice for d in pos_entries]))
for entry in pos_entries:
if mode_of_payments.get(entry.pos_invoice):
entry.mode_of_payment = ", ".join(mode_of_payments.get(entry.pos_invoice, []))
def add_subtotal_row(data, group_invoices, group_by_field, group_by_value):
grand_total = sum([d.grand_total for d in group_invoices])
paid_amount = sum([d.paid_amount for d in group_invoices])
data.append({
group_by_field: group_by_value,
"grand_total": grand_total,
"paid_amount": paid_amount,
"bold": 1
})
data.append({})
def validate_filters(filters):
if not filters.get("company"):
frappe.throw(_("{0} is mandatory").format(_("Company")))
if not filters.get("from_date") and not filters.get("to_date"):
frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date"))))
if filters.from_date > filters.to_date:
frappe.throw(_("From Date must be before To Date"))
if (filters.get("pos_profile") and filters.get("group_by") == _('POS Profile')):
frappe.throw(_("Can not filter based on POS Profile, if grouped by POS Profile"))
if (filters.get("customer") and filters.get("group_by") == _('Customer')):
frappe.throw(_("Can not filter based on Customer, if grouped by Customer"))
if (filters.get("owner") and filters.get("group_by") == _('Cashier')):
frappe.throw(_("Can not filter based on Cashier, if grouped by Cashier"))
if (filters.get("mode_of_payment") and filters.get("group_by") == _('Payment Method')):
frappe.throw(_("Can not filter based on Payment Method, if grouped by Payment Method"))
def get_conditions(filters):
conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s".format(
company=filters.get("company"),
from_date=filters.get("from_date"),
to_date=filters.get("to_date"))
if filters.get("pos_profile"):
conditions += " AND pos_profile = %(pos_profile)s".format(pos_profile=filters.get("pos_profile"))
if filters.get("owner"):
conditions += " AND owner = %(owner)s".format(owner=filters.get("owner"))
if filters.get("customer"):
conditions += " AND customer = %(customer)s".format(customer=filters.get("customer"))
if filters.get("is_return"):
conditions += " AND is_return = %(is_return)s".format(is_return=filters.get("is_return"))
if filters.get("mode_of_payment"):
conditions += """
AND EXISTS(
SELECT name FROM `tabSales Invoice Payment` sip
WHERE parent=p.name AND ifnull(sip.mode_of_payment, '') = %(mode_of_payment)s
)"""
return conditions
def get_group_by_field(group_by):
group_by_field = ""
if group_by == "POS Profile":
group_by_field = "pos_profile"
elif group_by == "Cashier":
group_by_field = "owner"
elif group_by == "Customer":
group_by_field = "customer"
elif group_by == "Payment Method":
group_by_field = "mode_of_payment"
return group_by_field
def get_columns(filters):
columns = [
{
"label": _("Posting Date"),
"fieldname": "posting_date",
"fieldtype": "Date",
"width": 90
},
{
"label": _("POS Invoice"),
"fieldname": "pos_invoice",
"fieldtype": "Link",
"options": "POS Invoice",
"width": 120
},
{
"label": _("Customer"),
"fieldname": "customer",
"fieldtype": "Link",
"options": "Customer",
"width": 120
},
{
"label": _("POS Profile"),
"fieldname": "pos_profile",
"fieldtype": "Link",
"options": "POS Profile",
"width": 160
},
{
"label": _("Cashier"),
"fieldname": "owner",
"fieldtype": "Link",
"options": "User",
"width": 140
},
{
"label": _("Grand Total"),
"fieldname": "grand_total",
"fieldtype": "Currency",
"options": "company:currency",
"width": 120
},
{
"label": _("Paid Amount"),
"fieldname": "paid_amount",
"fieldtype": "Currency",
"options": "company:currency",
"width": 120
},
{
"label": _("Payment Method"),
"fieldname": "mode_of_payment",
"fieldtype": "Data",
"width": 150
},
{
"label": _("Is Return"),
"fieldname": "is_return",
"fieldtype": "Data",
"width": 80
},
]
return columns

View File

@ -32,12 +32,12 @@ def execute(filters=None):
chart = get_chart_data(filters, columns, income, expense, net_profit_loss)
default_currency = frappe.get_cached_value('Company', filters.company, "default_currency")
report_summary = get_report_summary(period_list, filters.periodicity, income, expense, net_profit_loss, default_currency)
currency = filters.presentation_currency or frappe.get_cached_value('Company', filters.company, "default_currency")
report_summary = get_report_summary(period_list, filters.periodicity, income, expense, net_profit_loss, currency)
return columns, data, None, chart, report_summary
def get_report_summary(period_list, periodicity, income, expense, net_profit_loss, default_currency, consolidated=False):
def get_report_summary(period_list, periodicity, income, expense, net_profit_loss, currency, consolidated=False):
net_income, net_expense, net_profit = 0.0, 0.0, 0.0
for period in period_list:
@ -63,14 +63,14 @@ def get_report_summary(period_list, periodicity, income, expense, net_profit_los
"value": net_income,
"label": income_label,
"datatype": "Currency",
"currency": income[-1].get('currency') if income else default_currency
"currency": currency
},
{ "type": "separator", "value": "-"},
{
"value": net_expense,
"label": expense_label,
"datatype": "Currency",
"currency": expense[-1].get('currency') if expense else default_currency
"currency": currency
},
{ "type": "separator", "value": "=", "color": "blue"},
{
@ -78,7 +78,7 @@ def get_report_summary(period_list, periodicity, income, expense, net_profit_los
"indicator": "Green" if net_profit > 0 else "Red",
"label": profit_label,
"datatype": "Currency",
"currency": net_profit_loss.get("currency") if net_profit_loss else default_currency
"currency": currency
}
]

View File

@ -72,6 +72,12 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"fieldtype": "Link",
"options": "Finance Book",
},
{
"fieldname": "presentation_currency",
"label": __("Currency"),
"fieldtype": "Select",
"options": erpnext.get_presentation_currency_list()
},
{
"fieldname": "with_period_closing_entry",
"label": __("Period Closing Entry"),

View File

@ -56,7 +56,7 @@ def get_data(filters):
accounts = frappe.db.sql("""select name, account_number, parent_account, account_name, root_type, report_type, lft, rgt
from `tabAccount` where company=%s order by lft""", filters.company, as_dict=True)
company_currency = erpnext.get_company_currency(filters.company)
company_currency = filters.presentation_currency or erpnext.get_company_currency(filters.company)
if not accounts:
return None

View File

@ -683,6 +683,7 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters
where
party_type = %(party_type)s and party = %(party)s
and account = %(account)s and {dr_or_cr} > 0
and is_cancelled=0
{condition}
and ((voucher_type = 'Journal Entry'
and (against_voucher = '' or against_voucher is null))
@ -705,6 +706,7 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters
and account = %(account)s
and {payment_dr_or_cr} > 0
and against_voucher is not null and against_voucher != ''
and is_cancelled=0
group by against_voucher_type, against_voucher
""".format(payment_dr_or_cr=payment_dr_or_cr), {
"party_type": party_type,

View File

@ -466,29 +466,37 @@ class Asset(AccountsController):
def validate_make_gl_entry(self):
purchase_document = self.get_purchase_document()
asset_bought_with_invoice = purchase_document == self.purchase_invoice
fixed_asset_account, cwip_account = self.get_asset_accounts()
cwip_enabled = is_cwip_accounting_enabled(self.asset_category)
# check if expense already has been booked in case of cwip was enabled after purchasing asset
expense_booked = False
cwip_booked = False
if asset_bought_with_invoice:
expense_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""",
(purchase_document, fixed_asset_account), as_dict=1)
else:
cwip_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""",
(purchase_document, cwip_account), as_dict=1)
if cwip_enabled and (expense_booked or not cwip_booked):
# if expense has already booked from invoice or cwip is booked from receipt
if not purchase_document:
return False
elif not cwip_enabled and (not expense_booked or cwip_booked):
# if cwip is disabled but expense hasn't been booked yet
return True
elif cwip_enabled:
# default condition
return True
asset_bought_with_invoice = (purchase_document == self.purchase_invoice)
fixed_asset_account = self.get_fixed_asset_account()
cwip_enabled = is_cwip_accounting_enabled(self.asset_category)
cwip_account = self.get_cwip_account(cwip_enabled=cwip_enabled)
query = """SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s"""
if asset_bought_with_invoice:
# with invoice purchase either expense or cwip has been booked
expense_booked = frappe.db.sql(query, (purchase_document, fixed_asset_account), as_dict=1)
if expense_booked:
# if expense is already booked from invoice then do not make gl entries regardless of cwip enabled/disabled
return False
cwip_booked = frappe.db.sql(query, (purchase_document, cwip_account), as_dict=1)
if cwip_booked:
# if cwip is booked from invoice then make gl entries regardless of cwip enabled/disabled
return True
else:
# with receipt purchase either cwip has been booked or no entries have been made
if not cwip_account:
# if cwip account isn't available do not make gl entries
return False
cwip_booked = frappe.db.sql(query, (purchase_document, cwip_account), as_dict=1)
# if cwip is not booked from receipt then do not make gl entries
# if cwip is booked from receipt then make gl entries
return cwip_booked
def get_purchase_document(self):
asset_bought_with_invoice = self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')
@ -496,20 +504,25 @@ class Asset(AccountsController):
return purchase_document
def get_asset_accounts(self):
fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name,
asset_category = self.asset_category, company = self.company)
def get_fixed_asset_account(self):
return get_asset_category_account('fixed_asset_account', None, self.name, None, self.asset_category, self.company)
cwip_account = get_asset_account("capital_work_in_progress_account",
self.name, self.asset_category, self.company)
def get_cwip_account(self, cwip_enabled=False):
cwip_account = None
try:
cwip_account = get_asset_account("capital_work_in_progress_account", self.name, self.asset_category, self.company)
except:
# if no cwip account found in category or company and "cwip is enabled" then raise else silently pass
if cwip_enabled:
raise
return fixed_asset_account, cwip_account
return cwip_account
def make_gl_entries(self):
gl_entries = []
purchase_document = self.get_purchase_document()
fixed_asset_account, cwip_account = self.get_asset_accounts()
fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account()
if (purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()):
@ -561,14 +574,18 @@ class Asset(AccountsController):
return 100 * (1 - flt(depreciation_rate, float_precision))
def update_maintenance_status():
assets = frappe.get_all('Asset', filters = {'docstatus': 1, 'maintenance_required': 1})
assets = frappe.get_all(
"Asset", filters={"docstatus": 1, "maintenance_required": 1}
)
for asset in assets:
asset = frappe.get_doc("Asset", asset.name)
if frappe.db.exists('Asset Maintenance Task', {'parent': asset.name, 'next_due_date': today()}):
asset.set_status('In Maintenance')
if frappe.db.exists('Asset Repair', {'asset_name': asset.name, 'repair_status': 'Pending'}):
asset.set_status('Out of Order')
if frappe.db.exists("Asset Repair", {"asset_name": asset.name, "repair_status": "Pending"}):
asset.set_status("Out of Order")
elif frappe.db.exists("Asset Maintenance Task", {"parent": asset.name, "next_due_date": today()}):
asset.set_status("In Maintenance")
else:
asset.set_status()
def make_post_gl_entry():

View File

@ -9,6 +9,7 @@ from frappe.utils import cstr, nowdate, getdate, flt, get_last_day, add_days, ad
from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset, restore_asset
from erpnext.assets.doctype.asset.asset import make_sales_invoice
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice as make_invoice
class TestAsset(unittest.TestCase):
@ -558,81 +559,6 @@ class TestAsset(unittest.TestCase):
self.assertEqual(gle, expected_gle)
def test_gle_with_cwip_toggling(self):
# TEST: purchase an asset with cwip enabled and then disable cwip and try submitting the asset
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=5000, do_not_submit=True, location="Test Location")
pr.set('taxes', [{
'category': 'Total',
'add_deduct_tax': 'Add',
'charge_type': 'On Net Total',
'account_head': '_Test Account Service Tax - _TC',
'description': '_Test Account Service Tax',
'cost_center': 'Main - _TC',
'rate': 5.0
}, {
'category': 'Valuation and Total',
'add_deduct_tax': 'Add',
'charge_type': 'On Net Total',
'account_head': '_Test Account Shipping Charges - _TC',
'description': '_Test Account Shipping Charges',
'cost_center': 'Main - _TC',
'rate': 5.0
}])
pr.submit()
expected_gle = (
("Asset Received But Not Billed - _TC", 0.0, 5250.0),
("CWIP Account - _TC", 5250.0, 0.0)
)
pr_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
where voucher_type='Purchase Receipt' and voucher_no = %s
order by account""", pr.name)
self.assertEqual(pr_gle, expected_gle)
pi = make_invoice(pr.name)
pi.submit()
expected_gle = (
("_Test Account Service Tax - _TC", 250.0, 0.0),
("_Test Account Shipping Charges - _TC", 250.0, 0.0),
("Asset Received But Not Billed - _TC", 5250.0, 0.0),
("Creditors - _TC", 0.0, 5500.0),
("Expenses Included In Asset Valuation - _TC", 0.0, 250.0),
)
pi_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
where voucher_type='Purchase Invoice' and voucher_no = %s
order by account""", pi.name)
self.assertEqual(pi_gle, expected_gle)
asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
asset_doc = frappe.get_doc('Asset', asset)
month_end_date = get_last_day(nowdate())
asset_doc.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
self.assertEqual(asset_doc.gross_purchase_amount, 5250.0)
asset_doc.append("finance_books", {
"expected_value_after_useful_life": 200,
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 10,
"depreciation_start_date": month_end_date
})
# disable cwip and try submitting
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
asset_doc.submit()
# asset should have gl entries even if cwip is disabled
expected_gle = (
("_Test Fixed Asset - _TC", 5250.0, 0.0),
("CWIP Account - _TC", 0.0, 5250.0)
)
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
where voucher_type='Asset' and voucher_no = %s
order by account""", asset_doc.name)
self.assertEqual(gle, expected_gle)
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
def test_expense_head(self):
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=2, rate=200000.0, location="Test Location")
@ -641,6 +567,74 @@ class TestAsset(unittest.TestCase):
self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
def test_asset_cwip_toggling_cases(self):
cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting")
name = frappe.db.get_value("Asset Category Account", filters={"parent": "Computers"}, fieldname=["name"])
cwip_acc = "CWIP Account - _TC"
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", "")
frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", "")
# case 0 -- PI with cwip disable, Asset with cwip disabled, No cwip account set
pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1)
asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name')
asset_doc = frappe.get_doc('Asset', asset)
asset_doc.available_for_use_date = nowdate()
asset_doc.calculate_depreciation = 0
asset_doc.submit()
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
self.assertFalse(gle)
# case 1 -- PR with cwip disabled, Asset with cwip enabled
pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location")
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc)
asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
asset_doc = frappe.get_doc('Asset', asset)
asset_doc.available_for_use_date = nowdate()
asset_doc.calculate_depreciation = 0
asset_doc.submit()
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
self.assertFalse(gle)
# case 2 -- PR with cwip enabled, Asset with cwip disabled
pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location")
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
asset_doc = frappe.get_doc('Asset', asset)
asset_doc.available_for_use_date = nowdate()
asset_doc.calculate_depreciation = 0
asset_doc.submit()
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
self.assertTrue(gle)
# case 3 -- PI with cwip disabled, Asset with cwip enabled
pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1)
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name')
asset_doc = frappe.get_doc('Asset', asset)
asset_doc.available_for_use_date = nowdate()
asset_doc.calculate_depreciation = 0
asset_doc.submit()
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
self.assertFalse(gle)
# case 4 -- PI with cwip enabled, Asset with cwip disabled
pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1)
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name')
asset_doc = frappe.get_doc('Asset', asset)
asset_doc.available_for_use_date = nowdate()
asset_doc.calculate_depreciation = 0
asset_doc.submit()
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
self.assertTrue(gle)
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", cwip)
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc)
frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc)
def create_asset_data():
if not frappe.db.exists("Asset Category", "Computers"):
create_asset_category()

View File

@ -5,7 +5,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import cint
from frappe.utils import cint, get_link_to_form
from frappe.model.document import Document
class AssetCategory(Document):
@ -13,6 +13,7 @@ class AssetCategory(Document):
self.validate_finance_books()
self.validate_account_types()
self.validate_account_currency()
self.valide_cwip_account()
def validate_finance_books(self):
for d in self.finance_books:
@ -59,6 +60,21 @@ class AssetCategory(Document):
.format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_type)),
title=_("Invalid Account"))
def valide_cwip_account(self):
if self.enable_cwip_accounting:
missing_cwip_accounts_for_company = []
for d in self.accounts:
if (not d.capital_work_in_progress_account and
not frappe.db.get_value("Company", d.company_name, "capital_work_in_progress_account")):
missing_cwip_accounts_for_company.append(get_link_to_form("Company", d.company_name))
if missing_cwip_accounts_for_company:
msg = _("""To enable Capital Work in Progress Accounting, """)
msg += _("""you must select Capital Work in Progress Account in accounts table""")
msg += "<br><br>"
msg += _("You can also set default CWIP account in Company {}").format(", ".join(missing_cwip_accounts_for_company))
frappe.throw(msg, title=_("Missing Account"))
@frappe.whitelist()
def get_asset_category_account(fieldname, item=None, asset=None, account=None, asset_category = None, company = None):

View File

@ -27,3 +27,21 @@ class TestAssetCategory(unittest.TestCase):
except frappe.DuplicateEntryError:
pass
def test_cwip_accounting(self):
company_cwip_acc = frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account")
frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "")
asset_category = frappe.new_doc("Asset Category")
asset_category.asset_category_name = "Computers"
asset_category.enable_cwip_accounting = 1
asset_category.total_number_of_depreciations = 3
asset_category.frequency_of_depreciation = 3
asset_category.append("accounts", {
"company_name": "_Test Company",
"fixed_asset_account": "_Test Fixed Asset - _TC",
"accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
"depreciation_expense_account": "_Test Depreciations - _TC"
})
self.assertRaises(frappe.ValidationError, asset_category.insert)

View File

@ -33,7 +33,7 @@
{
"hidden": 0,
"label": "Other Reports",
"links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Items To Be Requested\",\n \"name\": \"Items To Be Requested\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase History\",\n \"name\": \"Item-wise Purchase History\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Receipt Trends\",\n \"name\": \"Purchase Receipt Trends\",\n \"reference_doctype\": \"Purchase Receipt\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Invoice Trends\",\n \"name\": \"Purchase Invoice Trends\",\n \"reference_doctype\": \"Purchase Invoice\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Item To Be Received\",\n \"name\": \"Subcontracted Item To Be Received\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Quoted Item Comparison\",\n \"name\": \"Quoted Item Comparison\",\n \"onboard\": 1,\n \"reference_doctype\": \"Supplier Quotation\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Material Requests for which Supplier Quotations are not created\",\n \"name\": \"Material Requests for which Supplier Quotations are not created\",\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"reference_doctype\": \"Address\",\n \"route_options\": {\n \"party_type\": \"Supplier\"\n },\n \"type\": \"report\"\n }\n]"
"links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Items To Be Requested\",\n \"name\": \"Items To Be Requested\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase History\",\n \"name\": \"Item-wise Purchase History\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Receipt Trends\",\n \"name\": \"Purchase Receipt Trends\",\n \"reference_doctype\": \"Purchase Receipt\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Invoice Trends\",\n \"name\": \"Purchase Invoice Trends\",\n \"reference_doctype\": \"Purchase Invoice\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Item To Be Received\",\n \"name\": \"Subcontracted Item To Be Received\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Quotation Comparison\",\n \"name\": \"Supplier Quotation Comparison\",\n \"onboard\": 1,\n \"reference_doctype\": \"Supplier Quotation\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Material Requests for which Supplier Quotations are not created\",\n \"name\": \"Material Requests for which Supplier Quotations are not created\",\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"reference_doctype\": \"Address\",\n \"route_options\": {\n \"party_type\": \"Supplier\"\n },\n \"type\": \"report\"\n }\n]"
},
{
"hidden": 0,
@ -61,7 +61,7 @@
"idx": 0,
"is_standard": 1,
"label": "Buying",
"modified": "2020-06-30 18:36:53.390498",
"modified": "2020-09-30 14:40:55.638458",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying",

View File

@ -1084,7 +1084,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2020-09-14 14:36:12.418690",
"modified": "2020-10-07 14:31:57.661221",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
@ -1130,11 +1130,11 @@
"write": 1
}
],
"search_fields": "status, transaction_date, supplier,grand_total",
"search_fields": "status, transaction_date, supplier, grand_total",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"timeline_field": "supplier",
"title_field": "supplier",
"title_field": "supplier_name",
"track_changes": 1
}

View File

@ -651,12 +651,12 @@ class TestPurchaseOrder(unittest.TestCase):
make_subcontracted_item(item_code)
po = create_purchase_order(item_code=item_code, qty=1,
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=1)
name = frappe.db.get_value('BOM', {'item': item_code}, 'name')
bom = frappe.get_doc('BOM', name)
exploded_items = sorted([d.item_code for d in bom.exploded_items])
exploded_items = sorted([d.item_code for d in bom.exploded_items if not d.get('sourced_by_supplier')])
supplied_items = sorted([d.rm_item_code for d in po.supplied_items])
self.assertEquals(exploded_items, supplied_items)
@ -664,7 +664,7 @@ class TestPurchaseOrder(unittest.TestCase):
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=0)
supplied_items1 = sorted([d.rm_item_code for d in po1.supplied_items])
bom_items = sorted([d.item_code for d in bom.items])
bom_items = sorted([d.item_code for d in bom.items if not d.get('sourced_by_supplier')])
self.assertEquals(supplied_items1, bom_items)

View File

@ -179,7 +179,7 @@ frappe.ui.form.on("Request for Quotation",{
dialog.hide();
return frappe.call({
type: "GET",
method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation",
method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation_from_rfq",
args: {
"source_name": doc.name,
"for_supplier": args.supplier

View File

@ -214,14 +214,14 @@ def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters):
and `tabDynamic Link`.link_name like %(txt)s) and `tabContact`.name = `tabDynamic Link`.parent
limit %(start)s, %(page_len)s""", {"start": start, "page_len":page_len, "txt": "%%%s%%" % txt, "name": filters.get('supplier')})
# This method is used to make supplier quotation from material request form.
@frappe.whitelist()
def make_supplier_quotation(source_name, for_supplier, target_doc=None):
def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=None):
def postprocess(source, target_doc):
target_doc.supplier = for_supplier
args = get_party_details(for_supplier, party_type="Supplier", ignore_permissions=True)
target_doc.currency = args.currency or get_party_account_currency('Supplier', for_supplier, source.company)
target_doc.buying_price_list = args.buying_price_list or frappe.db.get_value('Buying Settings', None, 'buying_price_list')
if for_supplier:
target_doc.supplier = for_supplier
args = get_party_details(for_supplier, party_type="Supplier", ignore_permissions=True)
target_doc.currency = args.currency or get_party_account_currency('Supplier', for_supplier, source.company)
target_doc.buying_price_list = args.buying_price_list or frappe.db.get_value('Buying Settings', None, 'buying_price_list')
set_missing_values(source, target_doc)
doclist = get_mapped_doc("Request for Quotation", source_name, {
@ -354,3 +354,32 @@ def get_supplier_tag():
frappe.cache().hset("Supplier", "Tags", tags)
return frappe.cache().hget("Supplier", "Tags")
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filters):
conditions = ""
if txt:
conditions += "and rfq.name like '%%"+txt+"%%' "
if filters.get("transaction_date"):
conditions += "and rfq.transaction_date = '{0}'".format(filters.get("transaction_date"))
rfq_data = frappe.db.sql("""
select
distinct rfq.name, rfq.transaction_date,
rfq.company
from
`tabRequest for Quotation` rfq, `tabRequest for Quotation Supplier` rfq_supplier
where
rfq.name = rfq_supplier.parent
and rfq_supplier.supplier = '{0}'
and rfq.docstatus = 1
and rfq.company = '{1}'
{2}
order by rfq.transaction_date ASC
limit %(page_len)s offset %(start)s """ \
.format(filters.get("supplier"), filters.get("company"), conditions),
{"page_len": page_len, "start": start}, as_dict=1)
return rfq_data

View File

@ -9,7 +9,7 @@ import frappe
from frappe.utils import nowdate
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.templates.pages.rfq import check_supplier_has_docname_access
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation_from_rfq
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import create_supplier_quotation
from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity
from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq
@ -22,7 +22,7 @@ class TestRequestforQuotation(unittest.TestCase):
self.assertEqual(rfq.get('suppliers')[1].quote_status, 'Pending')
# Submit the first supplier quotation
sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier)
sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[0].supplier)
sq.submit()
# No Quote first supplier quotation
@ -37,10 +37,10 @@ class TestRequestforQuotation(unittest.TestCase):
def test_make_supplier_quotation(self):
rfq = make_request_for_quotation()
sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier)
sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[0].supplier)
sq.submit()
sq1 = make_supplier_quotation(rfq.name, rfq.get('suppliers')[1].supplier)
sq1 = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[1].supplier)
sq1.submit()
self.assertEqual(sq.supplier, rfq.get('suppliers')[0].supplier)
@ -62,7 +62,7 @@ class TestRequestforQuotation(unittest.TestCase):
rfq = make_request_for_quotation(supplier_data=supplier_wt_appos)
sq = make_supplier_quotation(rfq.name, supplier_wt_appos[0].get("supplier"))
sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=supplier_wt_appos[0].get("supplier"))
sq.submit()
frappe.form_dict = frappe.local("form_dict")

View File

@ -8,8 +8,7 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
setup: function() {
this.frm.custom_make_buttons = {
'Purchase Order': 'Purchase Order',
'Quotation': 'Quotation',
'Subscription': 'Subscription'
'Quotation': 'Quotation'
}
this._super();
@ -28,12 +27,6 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
cur_frm.add_custom_button(__("Quotation"), this.make_quotation,
__('Create'));
if(!this.frm.doc.auto_repeat) {
cur_frm.add_custom_button(__('Subscription'), function() {
erpnext.utils.make_subscription(me.frm.doc.doctype, me.frm.doc.name)
}, __('Create'))
}
}
else if (this.frm.doc.docstatus===0) {
@ -54,6 +47,27 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
}
})
}, __("Get items from"));
this.frm.add_custom_button(__("Request for Quotation"),
function() {
if (!me.frm.doc.supplier) {
frappe.throw({message:__("Please select a Supplier"), title:__("Mandatory")})
}
erpnext.utils.map_current_doc({
method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation_from_rfq",
source_doctype: "Request for Quotation",
target: me.frm,
setters: {
company: me.frm.doc.company,
transaction_date: null
},
get_query_filters: {
supplier: me.frm.doc.supplier
},
get_query_method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_rfq_containing_supplier"
})
}, __("Get items from"));
}
},

View File

@ -159,6 +159,7 @@
"default": "Today",
"fieldname": "transaction_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Date",
"oldfieldname": "transaction_date",
"oldfieldtype": "Date",
@ -798,6 +799,7 @@
{
"fieldname": "valid_till",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Valid Till"
}
],
@ -805,7 +807,7 @@
"idx": 29,
"is_submittable": 1,
"links": [],
"modified": "2020-07-18 05:10:45.556792",
"modified": "2020-10-01 20:56:17.932007",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation",

View File

@ -12,6 +12,8 @@
"item_name",
"column_break_3",
"lead_time_days",
"expected_delivery_date",
"is_free_item",
"section_break_5",
"description",
"item_group",
@ -19,20 +21,18 @@
"col_break1",
"image",
"image_view",
"manufacture_details",
"manufacturer",
"column_break_15",
"manufacturer_part_no",
"quantity_and_rate",
"qty",
"stock_uom",
"price_list_rate",
"discount_percentage",
"discount_amount",
"col_break2",
"uom",
"conversion_factor",
"stock_qty",
"sec_break_price_list",
"price_list_rate",
"discount_percentage",
"discount_amount",
"col_break_price_list",
"base_price_list_rate",
"sec_break1",
"rate",
@ -42,7 +42,6 @@
"base_rate",
"base_amount",
"pricing_rules",
"is_free_item",
"section_break_24",
"net_rate",
"net_amount",
@ -56,7 +55,6 @@
"weight_uom",
"warehouse_and_reference",
"warehouse",
"project",
"prevdoc_doctype",
"material_request",
"sales_order",
@ -65,13 +63,19 @@
"material_request_item",
"request_for_quotation_item",
"item_tax_rate",
"manufacture_details",
"manufacturer",
"column_break_15",
"manufacturer_part_no",
"ad_sec_break",
"project",
"section_break_44",
"page_break"
],
"fields": [
{
"bold": 1,
"columns": 4,
"columns": 2,
"fieldname": "item_code",
"fieldtype": "Link",
"in_list_view": 1,
@ -107,7 +111,7 @@
{
"fieldname": "lead_time_days",
"fieldtype": "Int",
"label": "Lead Time in days"
"label": "Supplier Lead Time (days)"
},
{
"collapsible": 1,
@ -162,7 +166,6 @@
{
"fieldname": "stock_uom",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Stock UOM",
"options": "UOM",
"print_hide": 1,
@ -196,6 +199,7 @@
{
"fieldname": "uom",
"fieldtype": "Link",
"in_list_view": 1,
"label": "UOM",
"options": "UOM",
"print_hide": 1,
@ -289,14 +293,6 @@
"print_hide": 1,
"read_only": 1
},
{
"default": "0",
"fieldname": "is_free_item",
"fieldtype": "Check",
"label": "Is Free Item",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "section_break_24",
"fieldtype": "Section Break"
@ -528,12 +524,43 @@
{
"fieldname": "column_break_15",
"fieldtype": "Column Break"
},
{
"fieldname": "sec_break_price_list",
"fieldtype": "Section Break"
},
{
"fieldname": "col_break_price_list",
"fieldtype": "Column Break"
},
{
"collapsible": 1,
"fieldname": "ad_sec_break",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"default": "0",
"depends_on": "is_free_item",
"fieldname": "is_free_item",
"fieldtype": "Check",
"label": "Is Free Item",
"print_hide": 1,
"read_only": 1
},
{
"allow_on_submit": 1,
"bold": 1,
"fieldname": "expected_delivery_date",
"fieldtype": "Date",
"label": "Expected Delivery Date"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-04-07 18:35:51.175947",
"modified": "2020-10-01 16:34:39.703033",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation Item",

View File

@ -1,32 +0,0 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2016-07-21 08:31:05.890362",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 2,
"is_standard": "Yes",
"modified": "2017-02-24 20:04:58.784351",
"modified_by": "Administrator",
"module": "Buying",
"name": "Quoted Item Comparison",
"owner": "Administrator",
"ref_doctype": "Supplier Quotation",
"report_name": "Quoted Item Comparison",
"report_type": "Script Report",
"roles": [
{
"role": "Manufacturing Manager"
},
{
"role": "Purchase Manager"
},
{
"role": "Purchase User"
},
{
"role": "Stock User"
}
]
}

View File

@ -1,7 +1,7 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.query_reports["Quoted Item Comparison"] = {
frappe.query_reports["Supplier Quotation Comparison"] = {
filters: [
{
fieldtype: "Link",
@ -78,6 +78,13 @@ frappe.query_reports["Quoted Item Comparison"] = {
return { filters: { "docstatus": ["<", 2] } }
}
},
{
"fieldname":"group_by",
"label": __("Group by"),
"fieldtype": "Select",
"options": [__("Group by Supplier"), __("Group by Item")],
"default": __("Group by Supplier")
},
{
fieldtype: "Check",
label: __("Include Expired"),
@ -98,6 +105,9 @@ frappe.query_reports["Quoted Item Comparison"] = {
}
}
if(column.fieldname === "price_per_unit" && data.price_per_unit && data.min && data.min === 1){
value = `<div style="color:green">${value}</div>`;
}
return value;
},

View File

@ -0,0 +1,32 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2016-07-21 08:31:05.890362",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 2,
"is_standard": "Yes",
"modified": "2017-02-24 20:04:58.784351",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation Comparison",
"owner": "Administrator",
"ref_doctype": "Supplier Quotation",
"report_name": "Supplier Quotation Comparison",
"report_type": "Script Report",
"roles": [
{
"role": "Manufacturing Manager"
},
{
"role": "Purchase Manager"
},
{
"role": "Purchase User"
},
{
"role": "Stock User"
}
]
}

View File

@ -12,9 +12,9 @@ def execute(filters=None):
if not filters:
return [], []
columns = get_columns(filters)
conditions = get_conditions(filters)
supplier_quotation_data = get_data(filters, conditions)
columns = get_columns()
data, chart_data = prepare_data(supplier_quotation_data, filters)
message = get_message()
@ -41,9 +41,13 @@ def get_conditions(filters):
return conditions
def get_data(filters, conditions):
supplier_quotation_data = frappe.db.sql("""SELECT
sqi.parent, sqi.item_code, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation,
sqi.lead_time_days, sq.supplier, sq.valid_till
supplier_quotation_data = frappe.db.sql("""
SELECT
sqi.parent, sqi.item_code,
sqi.qty, sqi.stock_qty, sqi.amount,
sqi.uom, sqi.stock_uom,
sqi.request_for_quotation,
sqi.lead_time_days, sq.supplier as supplier_name, sq.valid_till
FROM
`tabSupplier Quotation Item` sqi,
`tabSupplier Quotation` sq
@ -58,16 +62,18 @@ def get_data(filters, conditions):
return supplier_quotation_data
def prepare_data(supplier_quotation_data, filters):
out, suppliers, qty_list, chart_data = [], [], [], []
supplier_wise_map = defaultdict(list)
out, groups, qty_list, suppliers, chart_data = [], [], [], [], []
group_wise_map = defaultdict(list)
supplier_qty_price_map = {}
group_by_field = "supplier_name" if filters.get("group_by") == "Group by Supplier" else "item_code"
company_currency = frappe.db.get_default("currency")
float_precision = cint(frappe.db.get_default("float_precision")) or 2
for data in supplier_quotation_data:
supplier = data.get("supplier")
supplier_currency = frappe.db.get_value("Supplier", data.get("supplier"), "default_currency")
group = data.get(group_by_field) # get item or supplier value for this row
supplier_currency = frappe.db.get_value("Supplier", data.get("supplier_name"), "default_currency")
if supplier_currency:
exchange_rate = get_exchange_rate(supplier_currency, company_currency)
@ -75,38 +81,55 @@ def prepare_data(supplier_quotation_data, filters):
exchange_rate = 1
row = {
"item_code": data.get('item_code'),
"item_code": "" if group_by_field=="item_code" else data.get("item_code"), # leave blank if group by field
"supplier_name": "" if group_by_field=="supplier_name" else data.get("supplier_name"),
"quotation": data.get("parent"),
"qty": data.get("qty"),
"price": flt(data.get("rate") * exchange_rate, float_precision),
"price": flt(data.get("amount") * exchange_rate, float_precision),
"uom": data.get("uom"),
"stock_uom": data.get('stock_uom'),
"request_for_quotation": data.get("request_for_quotation"),
"valid_till": data.get('valid_till'),
"lead_time_days": data.get('lead_time_days')
}
row["price_per_unit"] = flt(row["price"]) / (flt(data.get("stock_qty")) or 1)
# map for report view of form {'supplier1':[{},{},...]}
supplier_wise_map[supplier].append(row)
# map for report view of form {'supplier1'/'item1':[{},{},...]}
group_wise_map[group].append(row)
# map for chart preparation of the form {'supplier1': {'qty': 'price'}}
supplier = data.get("supplier_name")
if filters.get("item_code"):
if not supplier in supplier_qty_price_map:
supplier_qty_price_map[supplier] = {}
supplier_qty_price_map[supplier][row["qty"]] = row["price"]
groups.append(group)
suppliers.append(supplier)
qty_list.append(data.get("qty"))
groups = list(set(groups))
suppliers = list(set(suppliers))
qty_list = list(set(qty_list))
highlight_min_price = group_by_field == "item_code" or filters.get("item_code")
# final data format for report view
for supplier in suppliers:
supplier_wise_map[supplier][0].update({"supplier_name": supplier})
for entry in supplier_wise_map[supplier]:
for group in groups:
group_entries = group_wise_map[group] # all entries pertaining to item/supplier
group_entries[0].update({group_by_field : group}) # Add item/supplier name in first group row
if highlight_min_price:
prices = [group_entry["price_per_unit"] for group_entry in group_entries]
min_price = min(prices)
for entry in group_entries:
if highlight_min_price and entry["price_per_unit"] == min_price:
entry["min"] = 1
out.append(entry)
if filters.get("item_code"):
# render chart only for one item comparison
chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map)
return out, chart_data
@ -145,8 +168,9 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map):
return chart_data
def get_columns():
columns = [{
def get_columns(filters):
group_by_columns = [
{
"fieldname": "supplier_name",
"label": _("Supplier"),
"fieldtype": "Link",
@ -158,8 +182,10 @@ def get_columns():
"label": _("Item"),
"fieldtype": "Link",
"options": "Item",
"width": 200
},
"width": 150
}]
columns = [
{
"fieldname": "uom",
"label": _("UOM"),
@ -180,6 +206,20 @@ def get_columns():
"options": "Company:company:default_currency",
"width": 110
},
{
"fieldname": "stock_uom",
"label": _("Stock UOM"),
"fieldtype": "Link",
"options": "UOM",
"width": 90
},
{
"fieldname": "price_per_unit",
"label": _("Price per Unit (Stock UOM)"),
"fieldtype": "Currency",
"options": "Company:company:default_currency",
"width": 120
},
{
"fieldname": "quotation",
"label": _("Supplier Quotation"),
@ -205,9 +245,12 @@ def get_columns():
"fieldtype": "Link",
"options": "Request for Quotation",
"width": 150
}
]
}]
if filters.get("group_by") == "Group by Item":
group_by_columns.reverse()
columns[0:0] = group_by_columns # add positioned group by columns to the report
return columns
def get_message():

View File

@ -1242,7 +1242,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
try:
doc.check_permission(perm_type)
except frappe.PermissionError:
actions = { 'create': 'add', 'write': 'update', 'cancel': 'remove' }
actions = { 'create': 'add', 'write': 'update'}
frappe.throw(_("You do not have permissions to {} items in a {}.")
.format(actions[perm_type], parent_doctype), title=_("Insufficient Permissions"))
@ -1285,7 +1285,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation']
parent = frappe.get_doc(parent_doctype, parent_doctype_name)
check_doc_permissions(parent, 'cancel')
check_doc_permissions(parent, 'write')
validate_and_delete_children(parent, data)
for d in data:
@ -1319,24 +1319,26 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
validate_quantity(child_item, d)
child_item.qty = flt(d.get("qty"))
precision = child_item.precision("rate") or 2
rate_precision = child_item.precision("rate") or 2
conv_fac_precision = child_item.precision("conversion_factor") or 2
qty_precision = child_item.precision("qty") or 2
if flt(child_item.billed_amt, precision) > flt(flt(d.get("rate")) * flt(d.get("qty")), precision):
if flt(child_item.billed_amt, rate_precision) > flt(flt(d.get("rate"), rate_precision) * flt(d.get("qty"), qty_precision), rate_precision):
frappe.throw(_("Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.")
.format(child_item.idx, child_item.item_code))
else:
child_item.rate = flt(d.get("rate"))
child_item.rate = flt(d.get("rate"), rate_precision)
if d.get("conversion_factor"):
if child_item.stock_uom == child_item.uom:
child_item.conversion_factor = 1
else:
child_item.conversion_factor = flt(d.get('conversion_factor'))
child_item.conversion_factor = flt(d.get('conversion_factor'), conv_fac_precision)
if d.get("uom"):
child_item.uom = d.get("uom")
conversion_factor = flt(get_conversion_factor(child_item.item_code, child_item.uom).get("conversion_factor"))
child_item.conversion_factor = flt(d.get('conversion_factor')) or conversion_factor
child_item.conversion_factor = flt(d.get('conversion_factor'), conv_fac_precision) or conversion_factor
if d.get("delivery_date") and parent_doctype == 'Sales Order':
child_item.delivery_date = d.get('delivery_date')

View File

@ -847,6 +847,7 @@ def get_items_from_bom(item_code, bom, exploded_item=1):
where
t2.parent = t1.name and t1.item = %s
and t1.docstatus = 1 and t1.is_active = 1 and t1.name = %s
and t2.sourced_by_supplier = 0
and t2.item_code = t3.name""".format(doctype),
(item_code, bom), as_dict=1)

View File

@ -368,13 +368,17 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
searchfields = meta.get_search_fields()
search_columns = ''
search_cond = ''
if searchfields:
search_columns = ", " + ", ".join(searchfields)
search_cond = " or " + " or ".join([field + " like %(txt)s" for field in searchfields])
if args.get('warehouse'):
searchfields = ['batch.' + field for field in searchfields]
if searchfields:
search_columns = ", " + ", ".join(searchfields)
search_cond = " or " + " or ".join([field + " like %(txt)s" for field in searchfields])
batch_nos = frappe.db.sql("""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom,
concat('MFG-',batch.manufacturing_date), concat('EXP-',batch.expiry_date)
@ -387,7 +391,8 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
and sle.warehouse = %(warehouse)s
and (sle.batch_no like %(txt)s
or batch.expiry_date like %(txt)s
or batch.manufacturing_date like %(txt)s)
or batch.manufacturing_date like %(txt)s
{search_cond})
and batch.docstatus < 2
{cond}
{match_conditions}
@ -397,7 +402,8 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
search_columns = search_columns,
cond=cond,
match_conditions=get_match_cond(doctype),
having_clause = having_clause
having_clause = having_clause,
search_cond = search_cond
), args)
return batch_nos
@ -409,12 +415,15 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
and item = %(item_code)s
and (name like %(txt)s
or expiry_date like %(txt)s
or manufacturing_date like %(txt)s)
or manufacturing_date like %(txt)s
{search_cond})
and docstatus < 2
{0}
{match_conditions}
order by expiry_date, name desc
limit %(start)s, %(page_len)s""".format(cond, search_columns = search_columns, match_conditions=get_match_cond(doctype)), args)
limit %(start)s, %(page_len)s""".format(cond, search_columns = search_columns,
search_cond = search_cond, match_conditions=get_match_cond(doctype)), args)
@frappe.whitelist()

View File

@ -3,13 +3,14 @@
from __future__ import unicode_literals
import frappe
from frappe.utils import cint, flt, cstr, comma_or
from frappe.utils import cint, flt, cstr, comma_or, get_link_to_form
from frappe import _, throw
from erpnext.stock.get_item_details import get_bin_details
from erpnext.stock.utils import get_incoming_rate
from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.stock.doctype.item.item import set_item_default
from frappe.contacts.doctype.address.address import get_address_display
from erpnext.controllers.accounts_controller import get_taxes_and_charges
from erpnext.controllers.stock_controller import StockController
@ -53,10 +54,10 @@ class SellingController(StockController):
super(SellingController, self).set_missing_values(for_validate)
# set contact and address details for customer, if they are not mentioned
self.set_missing_lead_customer_details()
self.set_missing_lead_customer_details(for_validate=for_validate)
self.set_price_list_and_item_details(for_validate=for_validate)
def set_missing_lead_customer_details(self):
def set_missing_lead_customer_details(self, for_validate=False):
customer, lead = None, None
if getattr(self, "customer", None):
customer = self.customer
@ -94,6 +95,11 @@ class SellingController(StockController):
posting_date=self.get('transaction_date') or self.get('posting_date'),
company=self.company))
if self.get('taxes_and_charges') and not self.get('taxes') and not for_validate:
taxes = get_taxes_and_charges('Sales Taxes and Charges Template', self.taxes_and_charges)
for tax in taxes:
self.append('taxes', tax)
def set_price_list_and_item_details(self, for_validate=False):
self.set_price_list_currency("Selling")
self.set_missing_item_details(for_validate=for_validate)
@ -167,12 +173,16 @@ class SellingController(StockController):
def validate_selling_price(self):
def throw_message(idx, item_name, rate, ref_rate_field):
frappe.throw(_("""Row #{}: Selling rate for item {} is lower than its {}. Selling rate should be atleast {}""")
.format(idx, item_name, ref_rate_field, rate))
bold_net_rate = frappe.bold("net rate")
msg = (_("""Row #{}: Selling rate for item {} is lower than its {}. Selling {} should be atleast {}""")
.format(idx, frappe.bold(item_name), frappe.bold(ref_rate_field), bold_net_rate, frappe.bold(rate)))
msg += "<br><br>"
msg += (_("""You can alternatively disable selling price validation in {} to bypass this validation.""")
.format(get_link_to_form("Selling Settings", "Selling Settings")))
frappe.throw(msg, title=_("Invalid Selling Price"))
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
return
if hasattr(self, "is_return") and self.is_return:
return
@ -181,8 +191,8 @@ class SellingController(StockController):
continue
last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"])
last_purchase_rate_in_sales_uom = last_purchase_rate / (it.conversion_factor or 1)
if flt(it.base_rate) < flt(last_purchase_rate_in_sales_uom):
last_purchase_rate_in_sales_uom = last_purchase_rate * (it.conversion_factor or 1)
if flt(it.base_net_rate) < flt(last_purchase_rate_in_sales_uom):
throw_message(it.idx, frappe.bold(it.item_name), last_purchase_rate_in_sales_uom, "last purchase rate")
last_valuation_rate = frappe.db.sql("""
@ -191,8 +201,8 @@ class SellingController(StockController):
ORDER BY posting_date DESC, posting_time DESC, creation DESC LIMIT 1
""", (it.item_code, it.warehouse))
if last_valuation_rate:
last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] / (it.conversion_factor or 1)
if is_stock_item and flt(it.base_rate) < flt(last_valuation_rate_in_sales_uom) \
last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] * (it.conversion_factor or 1)
if is_stock_item and flt(it.base_net_rate) < flt(last_valuation_rate_in_sales_uom) \
and not self.get('is_internal_customer'):
throw_message(it.idx, frappe.bold(it.item_name), last_valuation_rate_in_sales_uom, "valuation rate")

View File

@ -25,7 +25,7 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p
if not filters: filters = []
if doctype in ['Supplier Quotation', 'Purchase Invoice']:
if doctype in ['Supplier Quotation', 'Purchase Invoice', 'Quotation']:
filters.append((doctype, 'docstatus', '<', 2))
else:
filters.append((doctype, 'docstatus', '=', 1))

View File

@ -241,6 +241,7 @@
},
{
"depends_on": "eval: doc.__islocal",
"description": "Home, Work, etc.",
"fieldname": "address_title",
"fieldtype": "Data",
"label": "Address Title"
@ -249,7 +250,8 @@
"depends_on": "eval: doc.__islocal",
"fieldname": "address_line1",
"fieldtype": "Data",
"label": "Address Line 1"
"label": "Address Line 1",
"mandatory_depends_on": "eval: doc.address_title && doc.address_type"
},
{
"depends_on": "eval: doc.__islocal",
@ -261,7 +263,8 @@
"depends_on": "eval: doc.__islocal",
"fieldname": "city",
"fieldtype": "Data",
"label": "City/Town"
"label": "City/Town",
"mandatory_depends_on": "eval: doc.address_title && doc.address_type"
},
{
"depends_on": "eval: doc.__islocal",
@ -280,6 +283,7 @@
"fieldname": "country",
"fieldtype": "Link",
"label": "Country",
"mandatory_depends_on": "eval: doc.address_title && doc.address_type",
"options": "Country"
},
{
@ -449,7 +453,7 @@
"idx": 5,
"image_field": "image",
"links": [],
"modified": "2020-06-18 14:39:41.835416",
"modified": "2020-10-13 15:24:00.094811",
"modified_by": "Administrator",
"module": "CRM",
"name": "Lead",

View File

@ -22,14 +22,12 @@ class Lead(SellingController):
load_address_and_contact(self)
def before_insert(self):
self.address_doc = self.create_address()
if self.address_title and self.address_type:
self.address_doc = self.create_address()
self.contact_doc = self.create_contact()
def after_insert(self):
self.update_links()
# after the address and contact are created, flush the field values
# to avoid inconsistent reporting in case the documents are changed
self.flush_address_and_contact_fields()
def validate(self):
self.set_lead_name()
@ -136,15 +134,6 @@ class Lead(SellingController):
# skipping country since the system auto-sets it from system defaults
address = frappe.new_doc("Address")
mandatory_fields = [ df.fieldname for df in address.meta.fields if df.reqd ]
if not all([self.get(field) for field in mandatory_fields]):
frappe.msgprint(_('Missing mandatory fields in address. \
{0} to create address' ).format("<a href='desk#Form/Address/New Address 1' \
> Click here </a>"),
alert=True, indicator='yellow')
return
address.update({addr_field: self.get(addr_field) for addr_field in address_fields})
address.update({info_field: self.get(info_field) for info_field in info_fields})
address.insert()
@ -193,7 +182,7 @@ class Lead(SellingController):
def update_links(self):
# update address links
if self.address_doc:
if hasattr(self, 'address_doc'):
self.address_doc.append("links", {
"link_doctype": "Lead",
"link_name": self.name,
@ -210,14 +199,6 @@ class Lead(SellingController):
})
self.contact_doc.save()
def flush_address_and_contact_fields(self):
fields = ['address_type', 'address_line1', 'address_line2', 'address_title',
'city', 'county', 'country', 'fax', 'pincode', 'state']
for field in fields:
self.set(field, None)
@frappe.whitelist()
def make_customer(source_name, target_doc=None):
return _make_customer(source_name, target_doc)
@ -376,3 +357,8 @@ def get_lead_with_phone_number(number):
lead = leads[0].name if leads else None
return lead
def daily_open_lead():
leads = frappe.get_all("Lead", filters = [["contact_date", "Between", [nowdate(), nowdate()]]])
for lead in leads:
frappe.db.set_value("Lead", lead.name, "status", "Open")

View File

@ -11,7 +11,7 @@ from erpnext.accounts.party import get_party_account_currency
from erpnext.exceptions import InvalidCurrency
from erpnext.stock.doctype.material_request.material_request import make_request_for_quotation
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import \
make_supplier_quotation as make_quotation_from_rfq
make_supplier_quotation_from_rfq
def work():
frappe.set_user(frappe.db.get_global('demo_purchase_user'))
@ -44,7 +44,7 @@ def work():
rfq = frappe.get_doc('Request for Quotation', rfq.name)
for supplier in rfq.suppliers:
supplier_quotation = make_quotation_from_rfq(rfq.name, supplier.supplier)
supplier_quotation = make_supplier_quotation_from_rfq(rfq.name, for_supplier=supplier.supplier)
supplier_quotation.save()
supplier_quotation.submit()

View File

@ -13,7 +13,7 @@
"fields": [
{
"fieldname": "question",
"fieldtype": "Small Text",
"fieldtype": "Text Editor",
"in_list_view": 1,
"label": "Question",
"reqd": 1
@ -34,7 +34,7 @@
"read_only": 1
}
],
"modified": "2019-05-30 18:39:21.880974",
"modified": "2020-09-24 18:39:21.880974",
"modified_by": "Administrator",
"module": "Education",
"name": "Question",

View File

@ -20,14 +20,14 @@
{
"fetch_from": "question_link.question",
"fieldname": "question",
"fieldtype": "Data",
"fieldtype": "Text Editor",
"in_list_view": 1,
"label": "Question",
"read_only": 1
}
],
"istable": 1,
"modified": "2019-06-12 12:24:02.312577",
"modified": "2020-09-24 12:24:02.312577",
"modified_by": "Administrator",
"module": "Education",
"name": "Quiz Question",

View File

@ -8,6 +8,7 @@
"allow_print": 0,
"amount": 0.0,
"amount_based_on_field": 0,
"apply_document_permissions": 0,
"creation": "2016-09-22 13:10:10.792735",
"doc_type": "Student Applicant",
"docstatus": 0,
@ -16,7 +17,7 @@
"is_standard": 1,
"login_required": 1,
"max_attachment_size": 0,
"modified": "2020-06-11 22:53:45.875310",
"modified": "2020-10-07 23:13:07.814941",
"modified_by": "Administrator",
"module": "Education",
"name": "student-applicant",
@ -157,7 +158,7 @@
},
{
"allow_read_on_all_link_options": 0,
"default": "INDIAN",
"default": "",
"fieldname": "nationality",
"fieldtype": "Data",
"hidden": 0,

View File

@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
import json
from frappe.utils import getdate, get_time
from frappe.utils import getdate, get_time, flt
from frappe.model.mapper import get_mapped_doc
from frappe import _
import datetime
@ -45,7 +45,7 @@ class PatientAppointment(Document):
def validate_overlaps(self):
end_time = datetime.datetime.combine(getdate(self.appointment_date), get_time(self.appointment_time)) \
+ datetime.timedelta(minutes=float(self.duration))
+ datetime.timedelta(minutes=flt(self.duration))
overlaps = frappe.db.sql("""
select

View File

@ -23,7 +23,6 @@ web_include_css = "assets/css/erpnext-web.css"
doctype_js = {
"Communication": "public/js/communication.js",
"Event": "public/js/event.js",
"Website Theme": "public/js/website_theme.js",
"Newsletter": "public/js/newsletter.js"
}
@ -336,7 +335,8 @@ scheduler_events = {
"erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation",
"erpnext.hr.utils.generate_leave_encashment",
"erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall.create_process_loan_security_shortfall",
"erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual.process_loan_interest_accrual_for_term_loans"
"erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
"erpnext.crm.doctype.lead.lead.daily_open_lead"
],
"monthly_long": [
"erpnext.accounts.deferred_revenue.process_deferred_accounting",
@ -392,6 +392,9 @@ regional_overrides = {
'Italy': {
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.italy.utils.update_itemised_tax_data',
'erpnext.controllers.accounts_controller.validate_regional': 'erpnext.regional.italy.utils.sales_invoice_validate',
},
'Germany': {
'erpnext.controllers.accounts_controller.validate_regional': 'erpnext.regional.germany.accounts_controller.validate_regional',
}
}
user_privacy_documents = [

View File

@ -10,13 +10,13 @@ frappe.ui.form.on('Appraisal', {
};
},
onload: function(frm) {
onload: function(frm) {
if(!frm.doc.status) {
frm.set_value('status', 'Draft');
}
},
kra_template: function(frm) {
kra_template: function(frm) {
frm.doc.goals = [];
erpnext.utils.map_current_doc({
method: "erpnext.hr.doctype.appraisal.appraisal.fetch_appraisal_template",
@ -25,30 +25,55 @@ frappe.ui.form.on('Appraisal', {
});
},
calculate_total: function(frm) {
let goals = frm.doc.goals || [];
let total =0;
for(let i = 0; i<goals.length; i++){
calculate_total: function(frm) {
let goals = frm.doc.goals || [];
let total = 0;
if (goals == []) {
frm.set_value('total_score', 0);
return;
}
for (let i = 0; i<goals.length; i++) {
total = flt(total)+flt(goals[i].score_earned)
}
frm.set_value('total_score', total);
if (!isNaN(total)) {
frm.set_value('total_score', total);
frm.refresh_field('calculate_total');
}
},
set_score_earned: function(frm) {
let goals = frm.doc.goals || [];
for (let i = 0; i<goals.length; i++) {
var d = locals[goals[i].doctype][goals[i].name];
if (d.score && d.per_weightage) {
d.score_earned = flt(d.per_weightage*d.score, precision("score_earned", d))/100;
}
else {
d.score_earned = 0;
}
refresh_field('score_earned', d.name, 'goals');
}
frm.trigger('calculate_total');
}
});
frappe.ui.form.on('Appraisal Goal', {
score: function(frm, cdt, cdn) {
var d = locals[cdt][cdn];
if (d.score) {
if (flt(d.score) > 5) {
frappe.msgprint(__("Score must be less than or equal to 5"));
d.score = 0;
refresh_field('score', d.name, 'goals');
}
d.score_earned = flt(d.per_weightage*d.score, precision("score_earned", d))/100;
} else {
d.score_earned = 0;
if (flt(d.score) > 5) {
frappe.msgprint(__("Score must be less than or equal to 5"));
d.score = 0;
refresh_field('score', d.name, 'goals');
}
refresh_field('score_earned', d.name, 'goals');
frm.trigger('calculate_total');
else {
frm.trigger('set_score_earned');
}
},
per_weightage: function(frm) {
frm.trigger('set_score_earned');
},
goals_remove: function(frm) {
frm.trigger('set_score_earned');
}
});

View File

@ -1,724 +1,220 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"actions": [],
"autoname": "naming_series:",
"beta": 0,
"creation": "2013-01-10 16:34:12",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 0,
"engine": "InnoDB",
"field_order": [
"employee_details",
"naming_series",
"kra_template",
"employee",
"employee_name",
"column_break0",
"status",
"start_date",
"end_date",
"department",
"section_break0",
"goals",
"total_score",
"section_break1",
"remarks",
"other_details",
"company",
"column_break_17",
"amended_from"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "employee_details",
"fieldtype": "Section 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,
"label": "",
"length": 0,
"no_copy": 0,
"oldfieldtype": "Section Break",
"permlevel": 0,
"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
"oldfieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "naming_series",
"fieldtype": "Select",
"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": "Series",
"length": 0,
"no_copy": 1,
"options": "HR-APR-.YY.-.MM.",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"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": 1,
"translatable": 0,
"unique": 0
"set_only_once": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "",
"fieldname": "kra_template",
"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": 1,
"label": "Appraisal Template",
"length": 0,
"no_copy": 0,
"oldfieldname": "kra_template",
"oldfieldtype": "Link",
"options": "Appraisal Template",
"permlevel": 0,
"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,
"depends_on": "kra_template",
"description": "",
"fieldname": "employee",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "For Employee",
"length": 0,
"no_copy": 0,
"oldfieldname": "employee",
"oldfieldtype": "Link",
"options": "Employee",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"search_index": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "kra_template",
"fieldname": "employee_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "For Employee Name",
"length": 0,
"no_copy": 0,
"oldfieldname": "employee_name",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "kra_template",
"fieldname": "column_break0",
"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,
"oldfieldtype": "Column Break",
"permlevel": 0,
"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,
"width": "50%"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Draft",
"depends_on": "kra_template",
"fieldname": "status",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Status",
"length": 0,
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
"options": "\nDraft\nSubmitted\nCompleted\nCancelled",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"search_index": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "kra_template",
"fieldname": "start_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": "Start Date",
"length": 0,
"no_copy": 0,
"oldfieldname": "start_date",
"oldfieldtype": "Date",
"permlevel": 0,
"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,
"depends_on": "kra_template",
"fieldname": "end_date",
"fieldtype": "Date",
"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": "End Date",
"length": 0,
"no_copy": 0,
"oldfieldname": "end_date",
"oldfieldtype": "Date",
"permlevel": 0,
"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,
"fetch_from": "employee.department",
"fieldname": "department",
"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": "Department",
"length": 0,
"no_copy": 0,
"options": "Department",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "kra_template",
"fieldname": "section_break0",
"fieldtype": "Section 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,
"label": "Goals",
"length": 0,
"no_copy": 0,
"oldfieldtype": "Section Break",
"options": "Simple",
"permlevel": 0,
"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": "Simple"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "goals",
"fieldtype": "Table",
"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": "Goals",
"length": 0,
"no_copy": 0,
"oldfieldname": "appraisal_details",
"oldfieldtype": "Table",
"options": "Appraisal Goal",
"permlevel": 0,
"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": "Appraisal Goal"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "calculate_total_score",
"fieldtype": "Button",
"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": "Calculate Total Score",
"length": 0,
"no_copy": 0,
"oldfieldtype": "Button",
"options": "calculate_total",
"permlevel": 0,
"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": "total_score",
"fieldtype": "Float",
"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": "Total Score (Out of 5)",
"length": 0,
"no_copy": 1,
"oldfieldname": "total_score",
"oldfieldtype": "Currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "kra_template",
"fieldname": "section_break1",
"fieldtype": "Section 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,
"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": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Any other remarks, noteworthy effort that should go in the records.",
"fieldname": "remarks",
"fieldtype": "Text",
"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": "Remarks",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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": "Remarks"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "kra_template",
"fieldname": "other_details",
"fieldtype": "Section 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,
"label": "",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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": "Section 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": 0,
"in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
"oldfieldname": "company",
"oldfieldtype": "Link",
"options": "Company",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 1,
"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": "column_break_17",
"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": "amended_from",
"fieldtype": "Link",
"hidden": 1,
"ignore_user_permissions": 1,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From",
"length": 0,
"no_copy": 1,
"oldfieldname": "amended_from",
"oldfieldtype": "Data",
"options": "Appraisal",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 1,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "150px"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-thumbs-up",
"idx": 1,
"image_view": 0,
"in_create": 0,
"index_web_pages_for_search": 1,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2020-09-18 17:26:09.703215",
"links": [],
"modified": "2020-10-03 21:48:33.297065",
"modified_by": "Administrator",
"module": "HR",
"name": "Appraisal",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Employee",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
@ -727,15 +223,10 @@
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
@ -746,30 +237,18 @@
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "status, employee, employee_name",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"timeline_field": "employee",
"title_field": "employee_name",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
"title_field": "employee_name"
}

View File

@ -50,7 +50,7 @@ class Appraisal(Document):
total_w += flt(d.per_weightage)
if int(total_w) != 100:
frappe.throw(_("Total weightage assigned should be 100%. It is {0}").format(str(total_w) + "%"))
frappe.throw(_("Total weightage assigned should be 100%.<br>It is {0}").format(str(total_w) + "%"))
if frappe.db.get_value("Employee", self.employee, "user_id") != \
frappe.session.user and total == 0:

View File

@ -109,7 +109,6 @@
"encashment_date",
"exit_interview_details",
"held_on",
"reason_for_resignation",
"new_workplace",
"feedback",
"lft",
@ -682,7 +681,7 @@
},
{
"fieldname": "reason_for_leaving",
"fieldtype": "Data",
"fieldtype": "Small Text",
"label": "Reason for Leaving",
"oldfieldname": "reason_for_leaving",
"oldfieldtype": "Data"
@ -696,6 +695,7 @@
"options": "\nYes\nNo"
},
{
"depends_on": "eval:doc.leave_encashed ==\"Yes\"",
"fieldname": "encashment_date",
"fieldtype": "Date",
"label": "Encashment Date",
@ -705,7 +705,6 @@
{
"fieldname": "exit_interview_details",
"fieldtype": "Column Break",
"label": "Exit Interview Details",
"oldfieldname": "col_brk6",
"oldfieldtype": "Column Break",
"width": "50%"
@ -713,18 +712,10 @@
{
"fieldname": "held_on",
"fieldtype": "Date",
"label": "Held On",
"label": "Exit Interview Held On",
"oldfieldname": "held_on",
"oldfieldtype": "Date"
},
{
"fieldname": "reason_for_resignation",
"fieldtype": "Select",
"label": "Reason for Resignation",
"oldfieldname": "reason_for_resignation",
"oldfieldtype": "Select",
"options": "\nBetter Prospects\nHealth Concerns"
},
{
"fieldname": "new_workplace",
"fieldtype": "Data",
@ -809,37 +800,29 @@
"fieldname": "expense_approver",
"fieldtype": "Link",
"label": "Expense Approver",
"options": "User",
"show_days": 1,
"show_seconds": 1
"options": "User"
},
{
"fieldname": "approvers_section",
"fieldtype": "Section Break",
"label": "Approvers",
"show_days": 1,
"show_seconds": 1
"label": "Approvers"
},
{
"fieldname": "column_break_45",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
"fieldtype": "Column Break"
},
{
"fieldname": "shift_request_approver",
"fieldtype": "Link",
"label": "Shift Request Approver",
"options": "User",
"show_days": 1,
"show_seconds": 1
"options": "User"
}
],
"icon": "fa fa-user",
"idx": 24,
"image_field": "image",
"links": [],
"modified": "2020-07-28 01:36:04.109189",
"modified": "2020-10-06 15:58:23.805489",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee",

View File

@ -57,6 +57,9 @@ class Employee(NestedSet):
remove_user_permission(
"Employee", self.name, existing_user_id)
def after_rename(self, old, new, merge):
self.db_set("employee", new)
def set_employee_name(self):
self.employee_name = ' '.join(filter(lambda x: x, [self.first_name, self.middle_name, self.last_name]))

View File

@ -1,3 +1,4 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
@ -32,7 +33,7 @@ class HolidayList(Document):
def validate_days(self):
if self.from_date > self.to_date:
if getdate(self.from_date) > getdate(self.to_date):
throw(_("To Date cannot be before From Date"))
for day in self.get("holidays"):

View File

@ -434,7 +434,8 @@ var get_bom_material_detail= function(doc, cdt, cdn, scrap_items) {
"include_item_in_manufacturing": d.include_item_in_manufacturing,
"uom": d.uom,
"stock_uom": d.stock_uom,
"conversion_factor": d.conversion_factor
"conversion_factor": d.conversion_factor,
"sourced_by_supplier": d.sourced_by_supplier
},
callback: function(r) {
d = locals[cdt][cdn];
@ -616,6 +617,22 @@ frappe.ui.form.on("BOM Item", "item_code", function(frm, cdt, cdn) {
refresh_field("allow_alternative_item", d.name, d.parentfield);
});
frappe.ui.form.on("BOM Item", "sourced_by_supplier", function(frm, cdt, cdn) {
var d = locals[cdt][cdn];
if (d.sourced_by_supplier) {
d.rate = 0;
refresh_field("rate", d.name, d.parentfield);
}
});
frappe.ui.form.on("BOM Item", "rate", function(frm, cdt, cdn) {
var d = locals[cdt][cdn];
if (d.sourced_by_supplier) {
d.rate = 0;
refresh_field("rate", d.name, d.parentfield);
}
});
frappe.ui.form.on("BOM Operation", "operations_remove", function(frm) {
erpnext.bom.calculate_op_cost(frm.doc);
erpnext.bom.calculate_total(frm.doc);

View File

@ -137,7 +137,8 @@ class BOM(WebsiteGenerator):
"qty": item.qty,
"uom": item.uom,
"stock_uom": item.stock_uom,
"conversion_factor": item.conversion_factor
"conversion_factor": item.conversion_factor,
"sourced_by_supplier": item.sourced_by_supplier
})
for r in ret:
if not item.get(r):
@ -172,7 +173,8 @@ class BOM(WebsiteGenerator):
'qty' : args.get("qty") or args.get("stock_qty") or 1,
'stock_qty' : args.get("qty") or args.get("stock_qty") or 1,
'base_rate' : flt(rate) * (flt(self.conversion_rate) or 1),
'include_item_in_manufacturing': cint(args['transfer_for_manufacture']) or 0
'include_item_in_manufacturing': cint(args['transfer_for_manufacture']) or 0,
'sourced_by_supplier' : args['sourced_by_supplier'] or 0
}
return ret_item
@ -191,8 +193,8 @@ class BOM(WebsiteGenerator):
if arg.get('scrap_items'):
rate = get_valuation_rate(arg)
elif arg:
#Customer Provided parts will have zero rate
if not frappe.db.get_value('Item', arg["item_code"], 'is_customer_provided_item'):
#Customer Provided parts and Supplier sourced parts will have zero rate
if not frappe.db.get_value('Item', arg["item_code"], 'is_customer_provided_item') and not arg.get('sourced_by_supplier'):
if arg.get('bom_no') and self.set_rate_of_sub_assembly_item_based_on_bom:
rate = flt(self.get_bom_unitcost(arg['bom_no'])) * (arg.get("conversion_factor") or 1)
else:
@ -205,7 +207,6 @@ class BOM(WebsiteGenerator):
else:
frappe.msgprint(_("{0} not found for item {1}")
.format(self.rm_cost_as_per, arg["item_code"]), alert=True)
return flt(rate) * flt(self.plc_conversion_rate or 1) / (self.conversion_rate or 1)
def update_cost(self, update_parent=True, from_child_bom=False, save=True):
@ -221,7 +222,8 @@ class BOM(WebsiteGenerator):
"qty": d.qty,
"uom": d.uom,
"stock_uom": d.stock_uom,
"conversion_factor": d.conversion_factor
"conversion_factor": d.conversion_factor,
"sourced_by_supplier": d.sourced_by_supplier
})
if rate:
@ -495,7 +497,8 @@ class BOM(WebsiteGenerator):
'stock_uom' : d.stock_uom,
'stock_qty' : flt(d.stock_qty),
'rate' : flt(d.base_rate) / (flt(d.conversion_factor) or 1.0),
'include_item_in_manufacturing': d.include_item_in_manufacturing
'include_item_in_manufacturing': d.include_item_in_manufacturing,
'sourced_by_supplier': d.sourced_by_supplier
}))
def company_currency(self):
@ -521,6 +524,7 @@ class BOM(WebsiteGenerator):
bom_item.stock_qty,
bom_item.rate,
bom_item.include_item_in_manufacturing,
bom_item.sourced_by_supplier,
bom_item.stock_qty / ifnull(bom.quantity, 1) AS qty_consumed_per_unit
FROM `tabBOM Explosion Item` bom_item, tabBOM bom
WHERE
@ -539,7 +543,8 @@ class BOM(WebsiteGenerator):
'stock_uom' : d['stock_uom'],
'stock_qty' : d['qty_consumed_per_unit'] * stock_qty,
'rate' : flt(d['rate']),
'include_item_in_manufacturing': d.get('include_item_in_manufacturing', 0)
'include_item_in_manufacturing': d.get('include_item_in_manufacturing', 0),
'sourced_by_supplier': d.get('sourced_by_supplier', 0)
}))
def add_exploded_items(self):
@ -679,7 +684,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite
is_stock_item=is_stock_item,
qty_field="stock_qty",
select_columns = """, bom_item.source_warehouse, bom_item.operation,
bom_item.include_item_in_manufacturing, bom_item.description, bom_item.rate,
bom_item.include_item_in_manufacturing, bom_item.description, bom_item.rate, bom_item.sourced_by_supplier,
(Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s limit 1) as idx""")
items = frappe.db.sql(query, { "parent": bom, "qty": qty, "bom": bom, "company": company }, as_dict=True)
@ -692,7 +697,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite
query = query.format(table="BOM Item", where_conditions="", is_stock_item=is_stock_item,
qty_field="stock_qty" if fetch_qty_in_stock_uom else "qty",
select_columns = """, bom_item.uom, bom_item.conversion_factor, bom_item.source_warehouse,
bom_item.idx, bom_item.operation, bom_item.include_item_in_manufacturing,
bom_item.idx, bom_item.operation, bom_item.include_item_in_manufacturing, bom_item.sourced_by_supplier,
bom_item.description, bom_item.base_rate as rate """)
items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True)

View File

@ -10,6 +10,8 @@ from frappe.test_runner import make_test_records
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost
from six import string_types
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
test_records = frappe.get_test_records('BOM')
@ -138,6 +140,73 @@ class TestBOM(unittest.TestCase):
self.assertEqual(bom.items[0].rate, 20)
def test_subcontractor_sourced_item(self):
item_code = "_Test Subcontracted FG Item 1"
if not frappe.db.exists('Item', item_code):
make_item(item_code, {
'is_stock_item': 1,
'is_sub_contracted_item': 1,
'stock_uom': 'Nos'
})
if not frappe.db.exists('Item', "Test Extra Item 1"):
make_item("Test Extra Item 1", {
'is_stock_item': 1,
'stock_uom': 'Nos'
})
if not frappe.db.exists('Item', "Test Extra Item 2"):
make_item("Test Extra Item 2", {
'is_stock_item': 1,
'stock_uom': 'Nos'
})
if not frappe.db.exists('Item', "Test Extra Item 3"):
make_item("Test Extra Item 3", {
'is_stock_item': 1,
'stock_uom': 'Nos'
})
bom = frappe.get_doc({
'doctype': 'BOM',
'is_default': 1,
'item': item_code,
'currency': 'USD',
'quantity': 1,
'company': '_Test Company'
})
for item in ["Test Extra Item 1", "Test Extra Item 2"]:
item_doc = frappe.get_doc('Item', item)
bom.append('items', {
'item_code': item,
'qty': 1,
'uom': item_doc.stock_uom,
'stock_uom': item_doc.stock_uom,
'rate': item_doc.valuation_rate
})
bom.append('items', {
'item_code': "Test Extra Item 3",
'qty': 1,
'uom': item_doc.stock_uom,
'stock_uom': item_doc.stock_uom,
'rate': 0,
'sourced_by_supplier': 1
})
bom.insert(ignore_permissions=True)
bom.update_cost()
bom.submit()
# test that sourced_by_supplier rate is zero even after updating cost
self.assertEqual(bom.items[2].rate, 0)
# test in Purchase Order sourced_by_supplier is not added to Supplied Item
po = create_purchase_order(item_code=item_code, qty=1,
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
bom_items = sorted([d.item_code for d in bom.items if d.sourced_by_supplier != 1])
supplied_items = sorted([d.rm_item_code for d in po.supplied_items])
self.assertEquals(bom_items, supplied_items)
def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})

View File

@ -1,626 +1,181 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"actions": [],
"autoname": "hash",
"beta": 0,
"creation": "2013-03-07 11:42:57",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"item_code",
"item_name",
"cb",
"source_warehouse",
"operation",
"section_break_3",
"description",
"column_break_2",
"image",
"image_view",
"section_break_4",
"stock_qty",
"rate",
"qty_consumed_per_unit",
"column_break_8",
"stock_uom",
"amount",
"include_item_in_manufacturing",
"sourced_by_supplier"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "item_code",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Item Code",
"length": 0,
"no_copy": 0,
"oldfieldname": "item_code",
"oldfieldtype": "Link",
"options": "Item",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "item_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Item Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cb",
"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": "source_warehouse",
"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": "Source Warehouse",
"length": 0,
"no_copy": 0,
"options": "Warehouse",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "operation",
"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": "Operation",
"length": 0,
"no_copy": 0,
"options": "Operation",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_3",
"fieldtype": "Section 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": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "description",
"fieldtype": "Text Editor",
"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": "Description",
"length": 0,
"no_copy": 0,
"oldfieldname": "description",
"oldfieldtype": "Text",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "300px",
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "300px"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_2",
"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": "image",
"fieldtype": "Attach",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Image",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"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
"print_hide": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "image_view",
"fieldtype": "Image",
"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": "Image View",
"length": 0,
"no_copy": 0,
"options": "image",
"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": "image"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_4",
"fieldtype": "Section 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": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "stock_qty",
"fieldtype": "Float",
"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": "Stock Qty",
"length": 0,
"no_copy": 0,
"oldfieldname": "qty",
"oldfieldtype": "Currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "rate",
"fieldtype": "Currency",
"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": "Rate",
"length": 0,
"no_copy": 0,
"oldfieldname": "standard_rate",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "qty_consumed_per_unit",
"fieldtype": "Float",
"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": "Qty Consumed Per Unit",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_8",
"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": "stock_uom",
"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": "Stock UOM",
"length": 0,
"no_copy": 0,
"oldfieldname": "stock_uom",
"oldfieldtype": "Link",
"options": "UOM",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amount",
"fieldtype": "Currency",
"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": "Amount",
"length": 0,
"no_copy": 0,
"oldfieldname": "amount_as_per_sr",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "include_item_in_manufacturing",
"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": "Include Item In Manufacturing",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"default": "0",
"fieldname": "sourced_by_supplier",
"fieldtype": "Check",
"label": "Sourced by Supplier",
"read_only": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"index_web_pages_for_search": 1,
"istable": 1,
"max_attachments": 0,
"modified": "2018-11-20 19:04:59.813773",
"links": [],
"modified": "2020-10-08 16:21:29.386212",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Explosion Item",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"track_changes": 1,
"track_seen": 0,
"track_views": 0
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -37,7 +37,9 @@
"section_break_27",
"has_variants",
"include_item_in_manufacturing",
"original_item"
"original_item",
"column_break_33",
"sourced_by_supplier"
],
"fields": [
{
@ -272,12 +274,23 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "column_break_33",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "sourced_by_supplier",
"fieldtype": "Check",
"label": "Sourced by Supplier"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-04-09 14:30:26.535546",
"modified": "2020-10-08 14:19:37.563300",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Item",

View File

@ -381,7 +381,6 @@ class ProductionPlan(Document):
"transaction_date": nowdate(),
"status": "Draft",
"company": self.company,
"requested_by": frappe.session.user,
'material_request_type': material_request_type,
'customer': item_doc.customer or ''
})

View File

@ -434,7 +434,7 @@ class WorkOrder(Document):
elif flt(d.completed_qty) <= max_allowed_qty_for_wo:
d.status = "Completed"
else:
frappe.throw(_("Completed Qty can not be greater than 'Qty to Manufacture'"))
frappe.throw(_("Completed Qty cannot be greater than 'Qty to Manufacture'"))
def set_actual_dates(self):
if self.get("operations"):

View File

@ -224,7 +224,8 @@ def trigger_razorpay_subscription(*args, **kwargs):
member.subscription_activated = 1
member.save(ignore_permissions=True)
except Exception as e:
log = frappe.log_error(e, "Error creating membership entry")
message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), __("Payment ID"), payment.id)
log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name))
notify_failure(log)
return { 'status': 'Failed', 'reason': e}

View File

@ -730,3 +730,4 @@ erpnext.patches.v13_0.rename_issue_doctype_fields
erpnext.patches.v13_0.change_default_pos_print_format
erpnext.patches.v13_0.set_youtube_video_id
erpnext.patches.v13_0.set_app_name
erpnext.patches.v13_0.print_uom_after_quantity_patch

View File

@ -0,0 +1,10 @@
# Copyright (c) 2019, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from erpnext.setup.install import create_print_uom_after_qty_custom_field
def execute():
create_print_uom_after_qty_custom_field()

View File

@ -217,7 +217,7 @@
"fieldname": "help",
"fieldtype": "HTML",
"label": "Help",
"options": "<h3>Help</h3>\n\n<p>Notes:</p>\n\n<ol>\n<li>Use field <code>base</code> for using base salary of the Employee</li>\n<li>Use Salary Component abbreviations in conditions and formulas. <code>BS = Basic Salary</code></li>\n<li>Use field name for employee details in conditions and formulas. <code>Employment Type = employment_type</code><code>Branch = branch</code></li>\n<li>Use field name from Salary Slip in conditions and formulas. <code>Payment Days = payment_days</code><code>Leave without pay = leave_without_pay</code></li>\n<li>Direct Amount can also be entered based on Condtion. See example 3</li></ol>\n\n<h4>Examples</h4>\n<ol>\n<li>Calculating Basic Salary based on <code>base</code>\n<pre><code>Condition: base &lt; 10000</code></pre>\n<pre><code>Formula: base * .2</code></pre></li>\n<li>Calculating HRA based on Basic Salary<code>BS</code> \n<pre><code>Condition: BS &gt; 2000</code></pre>\n<pre><code>Formula: BS * .1</code></pre></li>\n<li>Calculating TDS based on Employment Type<code>employment_type</code> \n<pre><code>Condition: employment_type==\"Intern\"</code></pre>\n<pre><code>Amount: 1000</code></pre></li>\n</ol>"
"options": "<h3>Help</h3>\n\n<p>Notes:</p>\n\n<ol>\n<li>Use field <code>base</code> for using base salary of the Employee</li>\n<li>Use Salary Component abbreviations in conditions and formulas. <code>BS = Basic Salary</code></li>\n<li>Use field name for employee details in conditions and formulas. <code>Employment Type = employment_type</code><code>Branch = branch</code></li>\n<li>Use field name from Salary Slip in conditions and formulas. <code>Payment Days = payment_days</code><code>Leave without pay = leave_without_pay</code></li>\n<li>Direct Amount can also be entered based on Condition. See example 3</li></ol>\n\n<h4>Examples</h4>\n<ol>\n<li>Calculating Basic Salary based on <code>base</code>\n<pre><code>Condition: base &lt; 10000</code></pre>\n<pre><code>Formula: base * .2</code></pre></li>\n<li>Calculating HRA based on Basic Salary<code>BS</code> \n<pre><code>Condition: BS &gt; 2000</code></pre>\n<pre><code>Formula: BS * .1</code></pre></li>\n<li>Calculating TDS based on Employment Type<code>employment_type</code> \n<pre><code>Condition: employment_type==\"Intern\"</code></pre>\n<pre><code>Amount: 1000</code></pre></li>\n</ol>"
},
{
"default": "0",
@ -238,14 +238,13 @@
"depends_on": "eval:doc.type == \"Deduction\"",
"fieldname": "is_income_tax_component",
"fieldtype": "Check",
"label": "Is Income Tax Component",
"show_days": 1,
"show_seconds": 1
"label": "Is Income Tax Component"
}
],
"icon": "fa fa-flag",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-06-22 15:39:20.826565",
"modified": "2020-10-07 20:38:33.795853",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Component",

View File

@ -117,7 +117,7 @@
"depends_on": "eval:doc.is_flexible_benefit != 1",
"fieldname": "section_break_2",
"fieldtype": "Section Break",
"label": "Condtion and formula"
"label": "Condition and formula"
},
{
"allow_on_submit": 1,
@ -206,38 +206,28 @@
"collapsible": 1,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"label": "Component properties and references ",
"show_days": 1,
"show_seconds": 1
"label": "Component properties and references "
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_19",
"fieldtype": "Section Break",
"show_days": 1,
"show_seconds": 1
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_18",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_24",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
"fieldtype": "Column Break"
}
],
"istable": 1,
"links": [],
"modified": "2020-07-01 12:13:41.956495",
"modified": "2020-10-07 20:39:41.619283",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Detail",

View File

@ -347,8 +347,7 @@ class TestSalarySlip(unittest.TestCase):
# create additional salary of 150000
frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""", (employee))
data["additional-1"] = create_additional_salary(employee, payroll_period, 50000)
data["additional-2"] = create_additional_salary(employee, payroll_period, 100000)
data["additional-1"] = create_additional_salary(employee, payroll_period, 150000)
data["deducted_dates"] = create_salary_slips_for_payroll_period(employee,
salary_structure.name, payroll_period)

View File

@ -140,7 +140,7 @@ class Question {
make_question() {
let question_wrapper = document.createElement('h5');
question_wrapper.classList.add('mt-3');
question_wrapper.innerText = this.question;
question_wrapper.innerHTML = this.question;
this.wrapper.appendChild(question_wrapper);
}

View File

@ -452,6 +452,9 @@ erpnext.utils.update_child_items = function(opts) {
const frm = opts.frm;
const cannot_add_row = (typeof opts.cannot_add_row === 'undefined') ? true : opts.cannot_add_row;
const child_docname = (typeof opts.cannot_add_row === 'undefined') ? "items" : opts.child_docname;
const child_meta = frappe.get_meta(`${frm.doc.doctype} Item`);
const get_precision = (fieldname) => child_meta.fields.find(f => f.fieldname == fieldname).precision;
this.data = [];
const fields = [{
fieldtype:'Data',
@ -499,14 +502,16 @@ erpnext.utils.update_child_items = function(opts) {
default: 0,
read_only: 0,
in_list_view: 1,
label: __('Qty')
label: __('Qty'),
precision: get_precision("qty")
}, {
fieldtype:'Currency',
fieldname:"rate",
default: 0,
read_only: 0,
in_list_view: 1,
label: __('Rate')
label: __('Rate'),
precision: get_precision("rate")
}];
if (frm.doc.doctype == 'Sales Order' || frm.doc.doctype == 'Purchase Order' ) {
@ -521,7 +526,8 @@ erpnext.utils.update_child_items = function(opts) {
fieldtype: 'Float',
fieldname: "conversion_factor",
in_list_view: 1,
label: __("Conversion Factor")
label: __("Conversion Factor"),
precision: get_precision('conversion_factor')
})
}
@ -677,6 +683,7 @@ erpnext.utils.map_current_doc = function(opts) {
date_field: opts.date_field || undefined,
setters: opts.setters,
get_query: opts.get_query,
add_filters_group: 1,
action: function(selections, args) {
let values = selections;
if(values.length === 0){

View File

@ -1,14 +0,0 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
frappe.ui.form.on('Website Theme', {
validate(frm) {
let theme_scss = frm.doc.theme_scss;
if (theme_scss && (theme_scss.includes('frappe/public/scss/website')
&& !theme_scss.includes('erpnext/public/scss/website'))
) {
frm.set_value('theme_scss',
`${frm.doc.theme_scss}\n@import "erpnext/public/scss/website";`);
}
}
});

View File

@ -0,0 +1,57 @@
import frappe
from frappe import _
from frappe import msgprint
REQUIRED_FIELDS = {
"Sales Invoice": [
{
"field_name": "company_address",
"regulation": "§ 14 Abs. 4 Nr. 1 UStG"
},
{
"field_name": "company_tax_id",
"regulation": "§ 14 Abs. 4 Nr. 2 UStG"
},
{
"field_name": "taxes",
"regulation": "§ 14 Abs. 4 Nr. 8 UStG",
"condition": "not exempt_from_sales_tax"
},
{
"field_name": "customer_address",
"regulation": "§ 14 Abs. 4 Nr. 1 UStG",
"condition": "base_grand_total > 250"
}
]
}
def validate_regional(doc):
"""Check if required fields for this document are present."""
required_fields = REQUIRED_FIELDS.get(doc.doctype)
if not required_fields:
return
meta = frappe.get_meta(doc.doctype)
field_map = {field.fieldname: field.label for field in meta.fields}
for field in required_fields:
condition = field.get("condition")
if condition and not frappe.safe_eval(condition, doc.as_dict()):
continue
field_name = field.get("field_name")
regulation = field.get("regulation")
if field_name and not doc.get(field_name):
missing(field_map.get(field_name), regulation)
def missing(field_label, regulation):
"""Notify the user that a required field is missing."""
context = 'Specific for Germany. Example: Remember to set Company Tax ID. It is required by § 14 Abs. 4 Nr. 2 UStG.'
msgprint(_('Remember to set {field_label}. It is required by {regulation}.', context=context).format(
field_label=frappe.bold(_(field_label)),
regulation=regulation
)
)

Some files were not shown because too many files have changed in this diff Show More