Merge branch 'develop' into quality-procedure-v13
This commit is contained in:
commit
cb791330f6
@ -16,7 +16,7 @@
|
||||
ERPNext as a monolith includes the following areas for managing businesses:
|
||||
|
||||
1. [Accounting](https://erpnext.com/open-source-accounting)
|
||||
1. [Inventory](https://erpnext.com/distribution/inventory-management-system)
|
||||
1. [Warehouse Management](https://erpnext.com/distribution/warehouse-management-system)
|
||||
1. [CRM](https://erpnext.com/open-source-crm)
|
||||
1. [Sales](https://erpnext.com/open-source-sales-purchase)
|
||||
1. [Purchase](https://erpnext.com/open-source-sales-purchase)
|
||||
|
@ -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,
|
||||
@ -98,7 +98,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",
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
}
|
@ -38,8 +38,8 @@
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"modified": "2020-06-18 20:27:42.615842",
|
||||
"modified_by": "ahmad@havenir.com",
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Item Tax Template",
|
||||
"owner": "Administrator",
|
||||
|
@ -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()
|
||||
|
@ -195,9 +195,7 @@ def create_sales_invoice_record(qty=1):
|
||||
|
||||
def create_records():
|
||||
# create a new loyalty Account
|
||||
if frappe.db.exists("Account", "Loyalty - _TC"):
|
||||
return
|
||||
|
||||
if not frappe.db.exists("Account", "Loyalty - _TC"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Account",
|
||||
"account_name": "Loyalty",
|
||||
@ -208,6 +206,7 @@ def create_records():
|
||||
}).insert()
|
||||
|
||||
# create a new loyalty program Single tier
|
||||
if not frappe.db.exists("Loyalty Program","Test Single Loyalty"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Loyalty Program",
|
||||
"loyalty_program_name": "Test Single Loyalty",
|
||||
@ -227,6 +226,7 @@ def create_records():
|
||||
}).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",
|
||||
@ -236,6 +236,7 @@ def create_records():
|
||||
}).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",
|
||||
@ -262,7 +263,8 @@ def create_records():
|
||||
}).insert()
|
||||
|
||||
# create an item
|
||||
item = frappe.get_doc({
|
||||
if not frappe.db.exists("Item", "Loyal Item"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Item",
|
||||
"item_code": "Loyal Item",
|
||||
"item_name": "Loyal Item",
|
||||
@ -274,9 +276,10 @@ def create_records():
|
||||
}).insert()
|
||||
|
||||
# create item price
|
||||
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": item.item_code,
|
||||
"item_code": "Loyal Item",
|
||||
"price_list_rate": 10000
|
||||
}).insert()
|
||||
|
@ -45,11 +45,11 @@
|
||||
],
|
||||
"icon": "fa fa-credit-card",
|
||||
"idx": 1,
|
||||
"modified": "2019-08-14 14:58:42.079115",
|
||||
"modified_by": "sammish.thundiyil@gmail.com",
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Mode of Payment",
|
||||
"owner": "harshada@webnotestech.com",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
|
@ -12,9 +12,10 @@ frappe.ui.form.on('Payment Entry', {
|
||||
|
||||
setup: function(frm) {
|
||||
frm.set_query("paid_from", function() {
|
||||
frm.events.validate_company(frm);
|
||||
|
||||
var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type) ?
|
||||
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
|
||||
|
||||
return {
|
||||
filters: {
|
||||
"account_type": ["in", account_types],
|
||||
@ -23,13 +24,16 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("party_type", function() {
|
||||
frm.events.validate_company(frm);
|
||||
return{
|
||||
filters: {
|
||||
"name": ["in", Object.keys(frappe.boot.party_account_types)],
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("party_bank_account", function() {
|
||||
return {
|
||||
filters: {
|
||||
@ -39,6 +43,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("bank_account", function() {
|
||||
return {
|
||||
filters: {
|
||||
@ -47,6 +52,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("contact_person", function() {
|
||||
if (frm.doc.party) {
|
||||
return {
|
||||
@ -58,10 +64,12 @@ frappe.ui.form.on('Payment Entry', {
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("paid_to", function() {
|
||||
frm.events.validate_company(frm);
|
||||
|
||||
var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type) ?
|
||||
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
|
||||
|
||||
return {
|
||||
filters: {
|
||||
"account_type": ["in", account_types],
|
||||
@ -150,6 +158,12 @@ frappe.ui.form.on('Payment Entry', {
|
||||
frm.events.show_general_ledger(frm);
|
||||
},
|
||||
|
||||
validate_company: (frm) => {
|
||||
if (!frm.doc.company){
|
||||
frappe.throw({message:__("Please select a Company first."), title: __("Mandatory")});
|
||||
}
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
frm.events.hide_unhide_fields(frm);
|
||||
frm.events.set_dynamic_labels(frm);
|
||||
|
@ -1,6 +1,7 @@
|
||||
frappe.listview_settings['Payment Entry'] = {
|
||||
|
||||
onload: function(listview) {
|
||||
if (listview.page.fields_dict.party_type) {
|
||||
listview.page.fields_dict.party_type.get_query = function() {
|
||||
return {
|
||||
"filters": {
|
||||
@ -9,4 +10,5 @@ frappe.listview_settings['Payment Entry'] = {
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
@ -291,11 +291,11 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-08-21 16:15:49.089450",
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Period Closing Voucher",
|
||||
"owner": "jai@webnotestech.com",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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",
|
||||
|
@ -96,17 +96,28 @@ class POSInvoiceMergeLog(Document):
|
||||
loyalty_amount_sum += doc.loyalty_amount
|
||||
|
||||
for item in doc.get('items'):
|
||||
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
|
||||
|
||||
|
@ -5,13 +5,14 @@
|
||||
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
|
||||
from erpnext.controllers.status_updater import StatusUpdater
|
||||
|
||||
class POSOpeningEntry(StatusUpdater):
|
||||
def validate(self):
|
||||
self.validate_pos_profile_and_cashier()
|
||||
self.validate_payment_method_account()
|
||||
self.set_status()
|
||||
|
||||
def validate_pos_profile_and_cashier(self):
|
||||
@ -21,5 +22,13 @@ class POSOpeningEntry(StatusUpdater):
|
||||
if not cint(frappe.db.get_value("User", self.user, "enabled")):
|
||||
frappe.throw(_("User {} has been disabled. Please select valid user/cashier".format(self.user)))
|
||||
|
||||
def validate_payment_method_account(self):
|
||||
for d in self.balance_details:
|
||||
account = frappe.db.get_value("Mode of Payment Account",
|
||||
{"parent": d.mode_of_payment, "company": self.company}, "default_account")
|
||||
if not account:
|
||||
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
||||
.format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
|
||||
|
||||
def on_submit(self):
|
||||
self.set_status(update=True)
|
@ -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', {
|
||||
|
@ -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",
|
||||
|
@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import msgprint, _
|
||||
from frappe.utils import cint, now
|
||||
from frappe.utils import cint, now, get_link_to_form
|
||||
from six import iteritems
|
||||
from frappe.model.document import Document
|
||||
|
||||
@ -13,7 +13,7 @@ class POSProfile(Document):
|
||||
self.validate_default_profile()
|
||||
self.validate_all_link_fields()
|
||||
self.validate_duplicate_groups()
|
||||
self.check_default_payment()
|
||||
self.validate_payment_methods()
|
||||
|
||||
def validate_default_profile(self):
|
||||
for row in self.applicable_for_users:
|
||||
@ -52,14 +52,23 @@ class POSProfile(Document):
|
||||
if len(customer_groups) != len(set(customer_groups)):
|
||||
frappe.throw(_("Duplicate customer group found in the cutomer group table"), title = "Duplicate Customer Group")
|
||||
|
||||
def check_default_payment(self):
|
||||
if self.payments:
|
||||
def validate_payment_methods(self):
|
||||
if not self.payments:
|
||||
frappe.throw(_("Payment methods are mandatory. Please add at least one payment method."))
|
||||
|
||||
default_mode_of_payment = [d.default for d in self.payments if d.default]
|
||||
if not default_mode_of_payment:
|
||||
frappe.throw(_("Set default mode of payment"))
|
||||
frappe.throw(_("Please select a default mode of payment"))
|
||||
|
||||
if len(default_mode_of_payment) > 1:
|
||||
frappe.throw(_("Multiple default mode of payment is not allowed"))
|
||||
frappe.throw(_("You can only select one mode of payment as default"))
|
||||
|
||||
for d in self.payments:
|
||||
account = frappe.db.get_value("Mode of Payment Account",
|
||||
{"parent": d.mode_of_payment, "company": self.company}, "default_account")
|
||||
if not account:
|
||||
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
||||
.format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
|
||||
|
||||
def on_update(self):
|
||||
self.set_defaults()
|
||||
@ -100,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):
|
||||
|
@ -7,10 +7,10 @@ frappe.ui.form.on('POS Settings', {
|
||||
},
|
||||
|
||||
get_invoice_fields: function(frm) {
|
||||
frappe.model.with_doctype("Sales Invoice", () => {
|
||||
var fields = $.map(frappe.get_doc("DocType", "Sales Invoice").fields, function(d) {
|
||||
frappe.model.with_doctype("POS Invoice", () => {
|
||||
var fields = $.map(frappe.get_doc("DocType", "POS Invoice").fields, function(d) {
|
||||
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 ||
|
||||
d.fieldtype === 'Table') {
|
||||
['Table', 'Button'].includes(d.fieldtype)) {
|
||||
return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname };
|
||||
} else {
|
||||
return null;
|
||||
@ -25,7 +25,7 @@ frappe.ui.form.on('POS Settings', {
|
||||
frappe.ui.form.on("POS Field", {
|
||||
fieldname: function(frm, doctype, name) {
|
||||
var doc = frappe.get_doc(doctype, name);
|
||||
var df = $.map(frappe.get_doc("DocType", "Sales Invoice").fields, function(d) {
|
||||
var df = $.map(frappe.get_doc("DocType", "POS Invoice").fields, function(d) {
|
||||
return doc.fieldname == d.fieldname ? d : null;
|
||||
})[0];
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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({
|
||||
|
@ -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:
|
||||
|
@ -210,7 +210,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-12 14:53:47.679439",
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Taxes and Charges",
|
||||
|
@ -78,7 +78,7 @@
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Taxes and Charges Template",
|
||||
"owner": "wasim@webnotestech.com",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"email": 1,
|
||||
|
@ -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",
|
||||
|
@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe, erpnext
|
||||
import frappe.defaults
|
||||
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
|
||||
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate, get_link_to_form
|
||||
from frappe import _, msgprint, throw
|
||||
from erpnext.accounts.party import get_party_account, get_due_date
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
@ -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)):
|
||||
@ -572,7 +572,8 @@ class SalesInvoice(SellingController):
|
||||
|
||||
def validate_pos(self):
|
||||
if self.is_return:
|
||||
if flt(self.paid_amount) + flt(self.write_off_amount) - flt(self.grand_total) > \
|
||||
invoice_total = self.rounded_total or self.grand_total
|
||||
if flt(self.paid_amount) + flt(self.write_off_amount) - flt(invoice_total) > \
|
||||
1.0/(10.0**(self.precision("grand_total") + 1.0)):
|
||||
frappe.throw(_("Paid amount + Write Off Amount can not be greater than Grand Total"))
|
||||
|
||||
@ -1372,7 +1373,7 @@ def get_bank_cash_account(mode_of_payment, company):
|
||||
{"parent": mode_of_payment, "company": company}, "default_account")
|
||||
if not account:
|
||||
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
||||
.format(mode_of_payment))
|
||||
.format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
|
||||
return {
|
||||
"account": account
|
||||
}
|
||||
@ -1612,16 +1613,14 @@ def update_multi_mode_option(doc, pos_profile):
|
||||
payment.type = payment_mode.type
|
||||
|
||||
doc.set('payments', [])
|
||||
if not pos_profile or not pos_profile.get('payments'):
|
||||
for payment_mode in get_all_mode_of_payments(doc):
|
||||
append_payment(payment_mode)
|
||||
return
|
||||
|
||||
for pos_payment_method in pos_profile.get('payments'):
|
||||
pos_payment_method = pos_payment_method.as_dict()
|
||||
|
||||
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
|
||||
if payment_mode:
|
||||
if not payment_mode:
|
||||
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
||||
.format(get_link_to_form("Mode of Payment", pos_payment_method.mode_of_payment)), title=_("Missing Account"))
|
||||
|
||||
payment_mode[0].default = pos_payment_method.default
|
||||
append_payment(payment_mode[0])
|
||||
|
||||
|
@ -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': [
|
||||
{
|
||||
|
@ -345,6 +345,7 @@ class Subscription(Document):
|
||||
invoice.set_taxes()
|
||||
|
||||
# Due date
|
||||
if self.days_until_due:
|
||||
invoice.append(
|
||||
'payment_schedule',
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -320,7 +320,6 @@ def make_reverse_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
|
||||
|
||||
entry['remarks'] = "On cancellation of " + entry['voucher_no']
|
||||
entry['is_cancelled'] = 1
|
||||
entry['posting_date'] = today()
|
||||
|
||||
if entry['debit'] or entry['credit']:
|
||||
make_entry(entry, adv_adj, "Yes")
|
||||
|
@ -72,7 +72,7 @@ erpnext.accounts.bankReconciliation = class BankReconciliation {
|
||||
check_plaid_status() {
|
||||
const me = this;
|
||||
frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => {
|
||||
if (r && r.enabled == "1") {
|
||||
if (r && r.enabled === "1") {
|
||||
me.plaid_status = "active"
|
||||
} else {
|
||||
me.plaid_status = "inactive"
|
||||
@ -214,31 +214,35 @@ erpnext.accounts.bankTransactionSync = class bankTransactionSync {
|
||||
|
||||
init_config() {
|
||||
const me = this;
|
||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration')
|
||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_plaid_configuration')
|
||||
.then(result => {
|
||||
me.plaid_env = result.plaid_env;
|
||||
me.plaid_public_key = result.plaid_public_key;
|
||||
me.client_name = result.client_name;
|
||||
me.sync_transactions()
|
||||
me.link_token = result.link_token;
|
||||
me.sync_transactions();
|
||||
})
|
||||
}
|
||||
|
||||
sync_transactions() {
|
||||
const me = this;
|
||||
frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (v) => {
|
||||
frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (r) => {
|
||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', {
|
||||
bank: v['bank'],
|
||||
bank: r.bank,
|
||||
bank_account: me.parent.bank_account,
|
||||
freeze: true
|
||||
})
|
||||
.then((result) => {
|
||||
let result_title = (result.length > 0) ? __("{0} bank transaction(s) created", [result.length]) : __("This bank account is already synchronized")
|
||||
let result_title = (result && result.length > 0)
|
||||
? __("{0} bank transaction(s) created", [result.length])
|
||||
: __("This bank account is already synchronized");
|
||||
|
||||
let result_msg = `
|
||||
<div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
|
||||
<h5 class="text-muted">${result_title}</h5>
|
||||
</div>`
|
||||
|
||||
this.parent.$main_section.append(result_msg)
|
||||
frappe.show_alert({message:__("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator:'green'});
|
||||
frappe.show_alert({ message: __("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator: 'green' });
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -384,7 +388,7 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
|
||||
})
|
||||
|
||||
frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments',
|
||||
{bank_transaction: data, freeze:true, freeze_message:__("Finding linked payments")}
|
||||
{ bank_transaction: data, freeze: true, freeze_message: __("Finding linked payments") }
|
||||
).then((result) => {
|
||||
me.make_dialog(result)
|
||||
})
|
||||
|
@ -34,7 +34,7 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
filters: {
|
||||
'company': company
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -617,9 +617,19 @@ class ReceivablePayableReport(object):
|
||||
elif party_type_field=="supplier":
|
||||
self.add_supplier_filters(conditions, values)
|
||||
|
||||
if self.filters.cost_center:
|
||||
self.get_cost_center_conditions(conditions)
|
||||
|
||||
self.add_accounting_dimensions_filters(conditions, values)
|
||||
return " and ".join(conditions), values
|
||||
|
||||
def get_cost_center_conditions(self, conditions):
|
||||
lft, rgt = frappe.db.get_value("Cost Center", self.filters.cost_center, ["lft", "rgt"])
|
||||
cost_center_list = [center.name for center in frappe.get_list("Cost Center", filters = {'lft': (">=", lft), 'rgt': ("<=", rgt)})]
|
||||
|
||||
cost_center_string = '", "'.join(cost_center_list)
|
||||
conditions.append('cost_center in ("{0}")'.format(cost_center_string))
|
||||
|
||||
def get_order_by_condition(self):
|
||||
if self.filters.get('group_by_party'):
|
||||
return "order by party, posting_date"
|
||||
|
@ -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],
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -268,9 +268,9 @@ class GrossProfitGenerator(object):
|
||||
def get_last_purchase_rate(self, item_code, row):
|
||||
condition = ''
|
||||
if row.project:
|
||||
condition += " AND a.project='%s'" % (row.project)
|
||||
condition += " AND a.project=%s" % (frappe.db.escape(row.project))
|
||||
elif row.cost_center:
|
||||
condition += " AND a.cost_center='%s'" % (row.cost_center)
|
||||
condition += " AND a.cost_center=%s" % (frappe.db.escape(row.cost_center))
|
||||
if self.filters.to_date:
|
||||
condition += " AND modified='%s'" % (self.filters.to_date)
|
||||
|
||||
|
76
erpnext/accounts/report/pos_register/pos_register.js
Normal file
76
erpnext/accounts/report/pos_register/pos_register.js
Normal 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;
|
||||
}
|
||||
};
|
30
erpnext/accounts/report/pos_register/pos_register.json
Normal file
30
erpnext/accounts/report/pos_register/pos_register.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
222
erpnext/accounts/report/pos_register/pos_register.py
Normal file
222
erpnext/accounts/report/pos_register/pos_register.py
Normal 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
|
@ -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:
|
||||
@ -64,19 +64,19 @@ 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
|
||||
},
|
||||
{
|
||||
"value": net_income,
|
||||
"label": income_label,
|
||||
"datatype": "Currency",
|
||||
"currency": income[-1].get('currency') if income else default_currency
|
||||
"currency": currency
|
||||
},
|
||||
{
|
||||
"value": net_expense,
|
||||
"label": expense_label,
|
||||
"datatype": "Currency",
|
||||
"currency": expense[-1].get('currency') if expense else default_currency
|
||||
"currency": currency
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -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"),
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -252,13 +252,6 @@ frappe.ui.form.on('Asset', {
|
||||
})
|
||||
},
|
||||
|
||||
available_for_use_date: function(frm) {
|
||||
$.each(frm.doc.finance_books || [], function(i, d) {
|
||||
if(!d.depreciation_start_date) d.depreciation_start_date = frm.doc.available_for_use_date;
|
||||
});
|
||||
refresh_field("finance_books");
|
||||
},
|
||||
|
||||
is_existing_asset: function(frm) {
|
||||
frm.trigger("toggle_reference_doc");
|
||||
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
|
||||
@ -438,6 +431,15 @@ frappe.ui.form.on('Asset Finance Book', {
|
||||
}
|
||||
|
||||
frappe.flags.dont_change_rate = false;
|
||||
},
|
||||
|
||||
depreciation_start_date: function(frm, cdt, cdn) {
|
||||
const book = locals[cdt][cdn];
|
||||
if (frm.doc.available_for_use_date && book.depreciation_start_date == frm.doc.available_for_use_date) {
|
||||
frappe.msgprint(__(`Depreciation Posting Date should not be equal to Available for Use Date.`));
|
||||
book.depreciation_start_date = "";
|
||||
frm.refresh_field("finance_books");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -6,7 +6,7 @@ from __future__ import unicode_literals
|
||||
import frappe, erpnext, math, json
|
||||
from frappe import _
|
||||
from six import string_types
|
||||
from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, month_diff, add_days
|
||||
from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, month_diff, add_days, get_last_day, get_datetime
|
||||
from frappe.model.document import Document
|
||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||
from erpnext.assets.doctype.asset.depreciation \
|
||||
@ -83,6 +83,11 @@ class Asset(AccountsController):
|
||||
if not self.available_for_use_date:
|
||||
frappe.throw(_("Available for use date is required"))
|
||||
|
||||
for d in self.finance_books:
|
||||
if d.depreciation_start_date == self.available_for_use_date:
|
||||
frappe.throw(_("Row #{}: Depreciation Posting Date should not be equal to Available for Use Date.").format(d.idx),
|
||||
title=_("Incorrect Date"))
|
||||
|
||||
def set_missing_values(self):
|
||||
if not self.asset_category:
|
||||
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
||||
@ -135,6 +140,10 @@ class Asset(AccountsController):
|
||||
def make_asset_movement(self):
|
||||
reference_doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice'
|
||||
reference_docname = self.purchase_receipt or self.purchase_invoice
|
||||
transaction_date = getdate(self.purchase_date)
|
||||
if reference_docname:
|
||||
posting_date, posting_time = frappe.db.get_value(reference_doctype, reference_docname, ["posting_date", "posting_time"])
|
||||
transaction_date = get_datetime("{} {}".format(posting_date, posting_time))
|
||||
assets = [{
|
||||
'asset': self.name,
|
||||
'asset_name': self.asset_name,
|
||||
@ -146,7 +155,7 @@ class Asset(AccountsController):
|
||||
'assets': assets,
|
||||
'purpose': 'Receipt',
|
||||
'company': self.company,
|
||||
'transaction_date': getdate(self.purchase_date),
|
||||
'transaction_date': transaction_date,
|
||||
'reference_doctype': reference_doctype,
|
||||
'reference_name': reference_docname
|
||||
}).insert()
|
||||
@ -294,7 +303,7 @@ class Asset(AccountsController):
|
||||
if not row.depreciation_start_date:
|
||||
if not self.available_for_use_date:
|
||||
frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx))
|
||||
row.depreciation_start_date = self.available_for_use_date
|
||||
row.depreciation_start_date = get_last_day(self.available_for_use_date)
|
||||
|
||||
if not self.is_existing_asset:
|
||||
self.opening_accumulated_depreciation = 0
|
||||
@ -457,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
|
||||
|
||||
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')
|
||||
@ -487,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()):
|
||||
|
||||
@ -552,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():
|
||||
|
||||
|
@ -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):
|
||||
@ -371,19 +372,18 @@ class TestAsset(unittest.TestCase):
|
||||
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
|
||||
asset = frappe.get_doc('Asset', asset_name)
|
||||
asset.calculate_depreciation = 1
|
||||
asset.available_for_use_date = nowdate()
|
||||
asset.purchase_date = nowdate()
|
||||
asset.available_for_use_date = '2020-01-01'
|
||||
asset.purchase_date = '2020-01-01'
|
||||
asset.append("finance_books", {
|
||||
"expected_value_after_useful_life": 10000,
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": nowdate()
|
||||
"total_number_of_depreciations": 10,
|
||||
"frequency_of_depreciation": 1
|
||||
})
|
||||
asset.insert()
|
||||
asset.submit()
|
||||
|
||||
post_depreciation_entries(date=add_months(nowdate(), 10))
|
||||
post_depreciation_entries(date=add_months('2020-01-01', 4))
|
||||
|
||||
scrap_asset(asset.name)
|
||||
|
||||
@ -392,9 +392,9 @@ class TestAsset(unittest.TestCase):
|
||||
self.assertTrue(asset.journal_entry_for_scrap)
|
||||
|
||||
expected_gle = (
|
||||
("_Test Accumulated Depreciations - _TC", 30000.0, 0.0),
|
||||
("_Test Accumulated Depreciations - _TC", 36000.0, 0.0),
|
||||
("_Test Fixed Asset - _TC", 0.0, 100000.0),
|
||||
("_Test Gain/Loss on Asset Disposal - _TC", 70000.0, 0.0)
|
||||
("_Test Gain/Loss on Asset Disposal - _TC", 64000.0, 0.0)
|
||||
)
|
||||
|
||||
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
||||
@ -466,8 +466,7 @@ class TestAsset(unittest.TestCase):
|
||||
"expected_value_after_useful_life": 10000,
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-06-06"
|
||||
"frequency_of_depreciation": 10
|
||||
})
|
||||
asset.insert()
|
||||
accumulated_depreciation_after_full_schedule = \
|
||||
@ -560,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")
|
||||
@ -643,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()
|
||||
|
@ -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):
|
||||
|
@ -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)
|
@ -1,347 +1,99 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"actions": [],
|
||||
"creation": "2018-05-08 14:44:37.095570",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"finance_book",
|
||||
"depreciation_method",
|
||||
"total_number_of_depreciations",
|
||||
"column_break_5",
|
||||
"frequency_of_depreciation",
|
||||
"depreciation_start_date",
|
||||
"expected_value_after_useful_life",
|
||||
"value_after_depreciation",
|
||||
"rate_of_depreciation"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"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": "finance_book",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Finance Book",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Finance Book",
|
||||
"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": "Finance Book"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "depreciation_method",
|
||||
"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": 0,
|
||||
"label": "Depreciation Method",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
|
||||
"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": "total_number_of_depreciations",
|
||||
"fieldtype": "Int",
|
||||
"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 Number of Depreciations",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "column_break_5",
|
||||
"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,
|
||||
"label": "",
|
||||
"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,
|
||||
"fieldname": "frequency_of_depreciation",
|
||||
"fieldtype": "Int",
|
||||
"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": "Frequency of Depreciation (Months)",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:parent.doctype == 'Asset'",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "depreciation_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": "Depreciation Start Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Depreciation Posting Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"depends_on": "eval:parent.doctype == 'Asset'",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "expected_value_after_useful_life",
|
||||
"fieldtype": "Currency",
|
||||
"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": "Expected Value After Useful Life",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company:company:default_currency",
|
||||
"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": "Company:company:default_currency"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "value_after_depreciation",
|
||||
"fieldtype": "Currency",
|
||||
"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": "Value After Depreciation",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"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,
|
||||
"depends_on": "eval:doc.depreciation_method == 'Written Down Value'",
|
||||
"description": "In Percentage",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "rate_of_depreciation",
|
||||
"fieldtype": "Percent",
|
||||
"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": "Rate of Depreciation",
|
||||
"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": "Rate of Depreciation"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-04-09 19:45:14.523488",
|
||||
"links": [],
|
||||
"modified": "2020-09-16 12:11:30.631788",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Finance Book",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 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
|
||||
}
|
@ -32,8 +32,7 @@ class TestAssetMovement(unittest.TestCase):
|
||||
"next_depreciation_date": "2020-12-31",
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-06-06"
|
||||
"frequency_of_depreciation": 10
|
||||
})
|
||||
|
||||
if asset.docstatus == 0:
|
||||
@ -82,8 +81,7 @@ class TestAssetMovement(unittest.TestCase):
|
||||
"next_depreciation_date": "2020-12-31",
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-06-06"
|
||||
"frequency_of_depreciation": 10
|
||||
})
|
||||
if asset.docstatus == 0:
|
||||
asset.submit()
|
||||
|
@ -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,
|
||||
@ -60,7 +60,7 @@
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Buying",
|
||||
"modified": "2020-06-29 19:30:24.983050",
|
||||
"modified": "2020-09-30 14:40:55.638458",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying",
|
||||
|
@ -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
|
||||
}
|
@ -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)
|
||||
|
||||
|
@ -148,11 +148,11 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-12 15:43:53.862897",
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order Item Supplied",
|
||||
"owner": "dhanalekshmi@webnotestech.com",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
|
@ -188,11 +188,11 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-10 18:09:33.997618",
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Receipt Item Supplied",
|
||||
"owner": "wasim@webnotestech.com",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
|
@ -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
|
||||
|
@ -214,10 +214,10 @@ 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):
|
||||
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)
|
||||
@ -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
|
@ -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")
|
||||
|
@ -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"));
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -178,6 +178,7 @@ def make_all_scorecards(docname):
|
||||
period_card = make_supplier_scorecard(docname, None)
|
||||
period_card.start_date = start_date
|
||||
period_card.end_date = end_date
|
||||
period_card.insert(ignore_permissions=True)
|
||||
period_card.submit()
|
||||
scp_count = scp_count + 1
|
||||
if start_date < first_start_date:
|
||||
|
@ -106,7 +106,7 @@ def make_supplier_scorecard(source_name, target_doc=None):
|
||||
"doctype": "Supplier Scorecard Scoring Criteria",
|
||||
"postprocess": update_criteria_fields,
|
||||
}
|
||||
}, target_doc, post_process)
|
||||
}, target_doc, post_process, ignore_permissions=True)
|
||||
|
||||
return doc
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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;
|
||||
},
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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():
|
@ -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"))
|
||||
@ -1264,7 +1264,10 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
transitions.append(transition.as_dict())
|
||||
|
||||
if not transitions:
|
||||
frappe.throw(_("You do not have workflow access to update this document."), title=_("Insufficient Workflow Permissions"))
|
||||
frappe.throw(
|
||||
_("You are not allowed to update as per the conditions set in {} Workflow.").format(get_link_to_form("Workflow", workflow)),
|
||||
title=_("Insufficient Permissions")
|
||||
)
|
||||
|
||||
def get_new_child_item(item_row):
|
||||
new_child_function = set_sales_order_defaults if parent_doctype == "Sales Order" else set_purchase_order_defaults
|
||||
@ -1282,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:
|
||||
@ -1316,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')
|
||||
|
@ -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)
|
||||
|
||||
|
@ -165,9 +165,14 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
AND company = %(company)s
|
||||
AND account_currency = %(currency)s
|
||||
AND `{searchfield}` LIKE %(txt)s
|
||||
{mcond}
|
||||
ORDER BY idx DESC, name
|
||||
LIMIT %(offset)s, %(limit)s
|
||||
""".format(account_type_condition=account_type_condition, searchfield=searchfield),
|
||||
""".format(
|
||||
account_type_condition=account_type_condition,
|
||||
searchfield=searchfield,
|
||||
mcond=get_match_cond(doctype)
|
||||
),
|
||||
dict(
|
||||
account_types=filters.get("account_type"),
|
||||
company=filters.get("company"),
|
||||
@ -359,9 +364,25 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
if filters.get("is_return"):
|
||||
having_clause = ""
|
||||
|
||||
meta = frappe.get_meta("Batch", cached=True)
|
||||
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)
|
||||
{search_columns}
|
||||
from `tabStock Ledger Entry` sle
|
||||
INNER JOIN `tabBatch` batch on sle.batch_no = batch.name
|
||||
where
|
||||
@ -370,31 +391,39 @@ 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}
|
||||
group by batch_no {having_clause}
|
||||
order by batch.expiry_date, sle.batch_no desc
|
||||
limit %(start)s, %(page_len)s""".format(
|
||||
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
|
||||
else:
|
||||
return frappe.db.sql("""select name, concat('MFG-', manufacturing_date), concat('EXP-',expiry_date) from `tabBatch` batch
|
||||
return frappe.db.sql("""select name, concat('MFG-', manufacturing_date), concat('EXP-',expiry_date)
|
||||
{search_columns}
|
||||
from `tabBatch` batch
|
||||
where batch.disabled = 0
|
||||
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, 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()
|
||||
|
@ -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
|
||||
@ -81,6 +82,7 @@ class SellingController(StockController):
|
||||
party_details = _get_party_details(customer,
|
||||
ignore_permissions=self.flags.ignore_permissions,
|
||||
doctype=self.doctype, company=self.company,
|
||||
posting_date=self.get('posting_date'),
|
||||
fetch_payment_terms_template=fetch_payment_terms_template,
|
||||
party_address=self.customer_address, shipping_address=self.shipping_address_name)
|
||||
if not self.meta.get_field("sales_team"):
|
||||
@ -93,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)
|
||||
@ -166,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
|
||||
|
||||
@ -180,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("""
|
||||
@ -190,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")
|
||||
|
||||
|
@ -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))
|
||||
|
@ -8,7 +8,7 @@
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Reports",
|
||||
"links": "[\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Details\",\n \"name\": \"Lead Details\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"icon\": \"fa fa-bar-chart\",\n \"label\": \"Sales Funnel\",\n \"name\": \"sales-funnel\",\n \"onboard\": 1,\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Prospects Engaged But Not Converted\",\n \"name\": \"Prospects Engaged But Not Converted\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Opportunity\"\n ],\n \"doctype\": \"Opportunity\",\n \"is_query_report\": true,\n \"label\": \"Minutes to First Response for Opportunity\",\n \"name\": \"Minutes to First Response for Opportunity\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Inactive Customers\",\n \"name\": \"Inactive Customers\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Campaign Efficiency\",\n \"name\": \"Campaign Efficiency\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Owner Efficiency\",\n \"name\": \"Lead Owner Efficiency\",\n \"type\": \"report\"\n }\n]"
|
||||
"links": "[\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Details\",\n \"name\": \"Lead Details\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"icon\": \"fa fa-bar-chart\",\n \"label\": \"Sales Funnel\",\n \"name\": \"sales-funnel\",\n \"onboard\": 1,\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Prospects Engaged But Not Converted\",\n \"name\": \"Prospects Engaged But Not Converted\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Opportunity\"\n ],\n \"doctype\": \"Opportunity\",\n \"is_query_report\": true,\n \"label\": \"First Response Time for Opportunity\",\n \"name\": \"First Response Time for Opportunity\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Inactive Customers\",\n \"name\": \"Inactive Customers\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Campaign Efficiency\",\n \"name\": \"Campaign Efficiency\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Owner Efficiency\",\n \"name\": \"Lead Owner Efficiency\",\n \"type\": \"report\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
@ -42,7 +42,7 @@
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "CRM",
|
||||
"modified": "2020-05-28 13:33:52.906750",
|
||||
"modified": "2020-08-11 18:55:18.238900",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "CRM",
|
||||
|
@ -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",
|
||||
|
@ -22,14 +22,12 @@ class Lead(SellingController):
|
||||
load_address_and_contact(self)
|
||||
|
||||
def before_insert(self):
|
||||
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")
|
@ -24,7 +24,7 @@
|
||||
"converted_by",
|
||||
"sales_stage",
|
||||
"order_lost_reason",
|
||||
"mins_to_first_response",
|
||||
"first_response_time",
|
||||
"expected_closing",
|
||||
"next_contact",
|
||||
"contact_by",
|
||||
@ -152,13 +152,6 @@
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"fieldname": "mins_to_first_response",
|
||||
"fieldtype": "Float",
|
||||
"label": "Mins to first response",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "expected_closing",
|
||||
"fieldtype": "Date",
|
||||
@ -419,12 +412,19 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Converted By",
|
||||
"options": "User"
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"fieldname": "first_response_time",
|
||||
"fieldtype": "Duration",
|
||||
"label": "First Response Time",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-info-sign",
|
||||
"idx": 195,
|
||||
"links": [],
|
||||
"modified": "2020-08-11 17:34:35.066961",
|
||||
"modified": "2020-08-12 17:34:35.066961",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Opportunity",
|
||||
|
@ -1,33 +1,44 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Minutes to First Response for Opportunity"] = {
|
||||
frappe.query_reports["First Response Time for Opportunity"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
'reqd': 1,
|
||||
"reqd": 1,
|
||||
"default": frappe.datetime.add_days(frappe.datetime.nowdate(), -30)
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
'reqd': 1,
|
||||
"reqd": 1,
|
||||
"default": frappe.datetime.nowdate()
|
||||
},
|
||||
],
|
||||
get_chart_data: function (columns, result) {
|
||||
get_chart_data: function (_columns, result) {
|
||||
return {
|
||||
data: {
|
||||
labels: result.map(d => d[0]),
|
||||
datasets: [{
|
||||
name: 'Mins to first response',
|
||||
name: "First Response Time",
|
||||
values: result.map(d => d[1])
|
||||
}]
|
||||
},
|
||||
type: 'line',
|
||||
type: "line",
|
||||
tooltipOptions: {
|
||||
formatTooltipY: d => {
|
||||
let duration_options = {
|
||||
hide_days: 0,
|
||||
hide_seconds: 0
|
||||
};
|
||||
value = frappe.utils.get_formatted_duration(d, duration_options);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,28 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"creation": "2020-08-10 18:34:19.083872",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"letter_head": "Test 2",
|
||||
"modified": "2020-08-10 18:34:19.083872",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "First Response Time for Opportunity",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Opportunity",
|
||||
"report_name": "First Response Time for Opportunity",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Sales User"
|
||||
},
|
||||
{
|
||||
"role": "Sales Manager"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute(filters=None):
|
||||
columns = [
|
||||
{
|
||||
'fieldname': 'creation_date',
|
||||
'label': 'Date',
|
||||
'fieldtype': 'Date',
|
||||
'width': 300
|
||||
},
|
||||
{
|
||||
'fieldname': 'first_response_time',
|
||||
'fieldtype': 'Duration',
|
||||
'label': 'First Response Time',
|
||||
'width': 300
|
||||
},
|
||||
]
|
||||
|
||||
data = frappe.db.sql('''
|
||||
SELECT
|
||||
date(creation) as creation_date,
|
||||
avg(first_response_time) as avg_response_time
|
||||
FROM tabOpportunity
|
||||
WHERE
|
||||
date(creation) between %s and %s
|
||||
and first_response_time > 0
|
||||
GROUP BY creation_date
|
||||
ORDER BY creation_date desc
|
||||
''', (filters.from_date, filters.to_date))
|
||||
|
||||
return columns, data
|
@ -1,26 +0,0 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"creation": "2016-06-17 11:28:25.867258",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 2,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2017-02-24 20:06:08.801109",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Minutes to First Response for Opportunity",
|
||||
"owner": "Administrator",
|
||||
"ref_doctype": "Opportunity",
|
||||
"report_name": "Minutes to First Response for Opportunity",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Sales User"
|
||||
},
|
||||
{
|
||||
"role": "Sales Manager"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute(filters=None):
|
||||
columns = [
|
||||
{
|
||||
'fieldname': 'creation_date',
|
||||
'label': 'Date',
|
||||
'fieldtype': 'Date'
|
||||
},
|
||||
{
|
||||
'fieldname': 'mins',
|
||||
'fieldtype': 'Float',
|
||||
'label': 'Mins to First Response'
|
||||
},
|
||||
]
|
||||
|
||||
data = frappe.db.sql('''select date(creation) as creation_date,
|
||||
avg(mins_to_first_response) as mins
|
||||
from tabOpportunity
|
||||
where date(creation) between %s and %s
|
||||
and mins_to_first_response > 0
|
||||
group by creation_date order by creation_date desc''', (filters.from_date, filters.to_date))
|
||||
|
||||
return columns, data
|
@ -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()
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -2,81 +2,90 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
from frappe.utils.password import get_decrypted_password
|
||||
from plaid import Client
|
||||
from plaid.errors import APIError, ItemError
|
||||
import plaid
|
||||
import requests
|
||||
from plaid.errors import APIError, ItemError, InvalidRequestError
|
||||
|
||||
import frappe
|
||||
import requests
|
||||
from frappe import _
|
||||
|
||||
|
||||
class PlaidConnector():
|
||||
def __init__(self, access_token=None):
|
||||
|
||||
plaid_settings = frappe.get_single("Plaid Settings")
|
||||
|
||||
self.config = {
|
||||
"plaid_client_id": plaid_settings.plaid_client_id,
|
||||
"plaid_secret": get_decrypted_password("Plaid Settings", "Plaid Settings", 'plaid_secret'),
|
||||
"plaid_public_key": plaid_settings.plaid_public_key,
|
||||
"plaid_env": plaid_settings.plaid_env
|
||||
}
|
||||
|
||||
self.client = Client(client_id=self.config.get("plaid_client_id"),
|
||||
secret=self.config.get("plaid_secret"),
|
||||
public_key=self.config.get("plaid_public_key"),
|
||||
environment=self.config.get("plaid_env")
|
||||
)
|
||||
|
||||
self.access_token = access_token
|
||||
self.settings = frappe.get_single("Plaid Settings")
|
||||
self.products = ["auth", "transactions"]
|
||||
self.client_name = frappe.local.site
|
||||
self.client = plaid.Client(
|
||||
client_id=self.settings.plaid_client_id,
|
||||
secret=self.settings.get_password("plaid_secret"),
|
||||
environment=self.settings.plaid_env,
|
||||
api_version="2019-05-29"
|
||||
)
|
||||
|
||||
def get_access_token(self, public_token):
|
||||
if public_token is None:
|
||||
frappe.log_error(_("Public token is missing for this bank"), _("Plaid public token error"))
|
||||
|
||||
response = self.client.Item.public_token.exchange(public_token)
|
||||
access_token = response['access_token']
|
||||
|
||||
access_token = response["access_token"]
|
||||
return access_token
|
||||
|
||||
def get_link_token(self):
|
||||
token_request = {
|
||||
"client_name": self.client_name,
|
||||
"client_id": self.settings.plaid_client_id,
|
||||
"secret": self.settings.plaid_secret,
|
||||
"products": self.products,
|
||||
# only allow Plaid-supported languages and countries (LAST: Sep-19-2020)
|
||||
"language": frappe.local.lang if frappe.local.lang in ["en", "fr", "es", "nl"] else "en",
|
||||
"country_codes": ["US", "CA", "FR", "IE", "NL", "ES", "GB"],
|
||||
"user": {
|
||||
"client_user_id": frappe.generate_hash(frappe.session.user, length=32)
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
response = self.client.LinkToken.create(token_request)
|
||||
except InvalidRequestError:
|
||||
frappe.log_error(frappe.get_traceback(), _("Plaid invalid request error"))
|
||||
frappe.msgprint(_("Please check your Plaid client ID and secret values"))
|
||||
except APIError as e:
|
||||
frappe.log_error(frappe.get_traceback(), _("Plaid authentication error"))
|
||||
frappe.throw(_(str(e)), title=_("Authentication Failed"))
|
||||
else:
|
||||
return response["link_token"]
|
||||
|
||||
def auth(self):
|
||||
try:
|
||||
self.client.Auth.get(self.access_token)
|
||||
print("Authentication successful.....")
|
||||
except ItemError as e:
|
||||
if e.code == 'ITEM_LOGIN_REQUIRED':
|
||||
pass
|
||||
else:
|
||||
if e.code == "ITEM_LOGIN_REQUIRED":
|
||||
pass
|
||||
except APIError as e:
|
||||
if e.code == 'PLANNED_MAINTENANCE':
|
||||
pass
|
||||
else:
|
||||
if e.code == "PLANNED_MAINTENANCE":
|
||||
pass
|
||||
except requests.Timeout:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(e)
|
||||
frappe.log_error(frappe.get_traceback(), _("Plaid authentication error"))
|
||||
frappe.msgprint({"title": _("Authentication Failed"), "message":e, "raise_exception":1, "indicator":'red'})
|
||||
frappe.throw(_(str(e)), title=_("Authentication Failed"))
|
||||
|
||||
def get_transactions(self, start_date, end_date, account_id=None):
|
||||
try:
|
||||
self.auth()
|
||||
kwargs = dict(
|
||||
access_token=self.access_token,
|
||||
start_date=start_date,
|
||||
end_date=end_date
|
||||
)
|
||||
if account_id:
|
||||
account_ids = [account_id]
|
||||
kwargs.update(dict(account_ids=[account_id]))
|
||||
|
||||
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, account_ids=account_ids)
|
||||
|
||||
else:
|
||||
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date)
|
||||
|
||||
transactions = response['transactions']
|
||||
|
||||
while len(transactions) < response['total_transactions']:
|
||||
try:
|
||||
response = self.client.Transactions.get(**kwargs)
|
||||
transactions = response["transactions"]
|
||||
while len(transactions) < response["total_transactions"]:
|
||||
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, offset=len(transactions))
|
||||
transactions.extend(response['transactions'])
|
||||
transactions.extend(response["transactions"])
|
||||
return transactions
|
||||
except Exception:
|
||||
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
|
||||
|
@ -4,14 +4,14 @@
|
||||
frappe.provide("erpnext.integrations");
|
||||
|
||||
frappe.ui.form.on('Plaid Settings', {
|
||||
enabled: function(frm) {
|
||||
enabled: function (frm) {
|
||||
frm.toggle_reqd('plaid_client_id', frm.doc.enabled);
|
||||
frm.toggle_reqd('plaid_secret', frm.doc.enabled);
|
||||
frm.toggle_reqd('plaid_public_key', frm.doc.enabled);
|
||||
frm.toggle_reqd('plaid_env', frm.doc.enabled);
|
||||
},
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.enabled) {
|
||||
|
||||
refresh: function (frm) {
|
||||
if (frm.doc.enabled) {
|
||||
frm.add_custom_button('Link a new bank account', () => {
|
||||
new erpnext.integrations.plaidLink(frm);
|
||||
});
|
||||
@ -22,17 +22,16 @@ frappe.ui.form.on('Plaid Settings', {
|
||||
erpnext.integrations.plaidLink = class plaidLink {
|
||||
constructor(parent) {
|
||||
this.frm = parent;
|
||||
this.product = ["transactions", "auth"];
|
||||
this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js';
|
||||
this.init_config();
|
||||
}
|
||||
|
||||
init_config() {
|
||||
const me = this;
|
||||
me.plaid_env = me.frm.doc.plaid_env;
|
||||
me.plaid_public_key = me.frm.doc.plaid_public_key;
|
||||
me.client_name = frappe.boot.sitename;
|
||||
me.init_plaid();
|
||||
async init_config() {
|
||||
this.product = ["auth", "transactions"];
|
||||
this.plaid_env = this.frm.doc.plaid_env;
|
||||
this.client_name = frappe.boot.sitename;
|
||||
this.token = await this.frm.call("get_link_token").then(resp => resp.message);
|
||||
this.init_plaid();
|
||||
}
|
||||
|
||||
init_plaid() {
|
||||
@ -69,17 +68,17 @@ erpnext.integrations.plaidLink = class plaidLink {
|
||||
}
|
||||
|
||||
onScriptLoaded(me) {
|
||||
me.linkHandler = window.Plaid.create({
|
||||
me.linkHandler = Plaid.create({
|
||||
clientName: me.client_name,
|
||||
product: me.product,
|
||||
env: me.plaid_env,
|
||||
key: me.plaid_public_key,
|
||||
onSuccess: me.plaid_success,
|
||||
product: me.product
|
||||
token: me.token,
|
||||
onSuccess: me.plaid_success
|
||||
});
|
||||
}
|
||||
|
||||
onScriptError(error) {
|
||||
frappe.msgprint('There was an issue loading the link-initialize.js script');
|
||||
frappe.msgprint("There was an issue connecting to Plaid's authentication server");
|
||||
frappe.msgprint(error);
|
||||
}
|
||||
|
||||
@ -87,20 +86,24 @@ erpnext.integrations.plaidLink = class plaidLink {
|
||||
const me = this;
|
||||
|
||||
frappe.prompt({
|
||||
fieldtype:"Link",
|
||||
fieldtype: "Link",
|
||||
options: "Company",
|
||||
label:__("Company"),
|
||||
fieldname:"company",
|
||||
reqd:1
|
||||
label: __("Company"),
|
||||
fieldname: "company",
|
||||
reqd: 1
|
||||
}, (data) => {
|
||||
me.company = data.company;
|
||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {token: token, response: response})
|
||||
.then((result) => {
|
||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {response: response,
|
||||
bank: result, company: me.company});
|
||||
})
|
||||
.then(() => {
|
||||
frappe.show_alert({message:__("Bank accounts added"), indicator:'green'});
|
||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {
|
||||
token: token,
|
||||
response: response
|
||||
}).then((result) => {
|
||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {
|
||||
response: response,
|
||||
bank: result,
|
||||
company: me.company
|
||||
});
|
||||
}).then(() => {
|
||||
frappe.show_alert({ message: __("Bank accounts added"), indicator: 'green' });
|
||||
});
|
||||
}, __("Select a company"), __("Continue"));
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2018-10-25 10:02:48.656165",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
@ -12,7 +11,6 @@
|
||||
"plaid_client_id",
|
||||
"plaid_secret",
|
||||
"column_break_7",
|
||||
"plaid_public_key",
|
||||
"plaid_env"
|
||||
],
|
||||
"fields": [
|
||||
@ -41,12 +39,6 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Plaid Secret"
|
||||
},
|
||||
{
|
||||
"fieldname": "plaid_public_key",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Plaid Public Key"
|
||||
},
|
||||
{
|
||||
"fieldname": "plaid_env",
|
||||
"fieldtype": "Select",
|
||||
@ -69,8 +61,7 @@
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-02-07 15:21:11.616231",
|
||||
"modified": "2020-09-12 02:31:44.542385",
|
||||
"modified_by": "Administrator",
|
||||
"module": "ERPNext Integrations",
|
||||
"name": "Plaid Settings",
|
||||
|
@ -2,37 +2,44 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
import frappe
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
|
||||
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_connector import PlaidConnector
|
||||
from frappe.utils import getdate, formatdate, today, add_months
|
||||
from frappe import _
|
||||
from frappe.desk.doctype.tag.tag import add_tag
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import add_months, formatdate, getdate, today
|
||||
|
||||
|
||||
class PlaidSettings(Document):
|
||||
pass
|
||||
@staticmethod
|
||||
def get_link_token():
|
||||
plaid = PlaidConnector()
|
||||
return plaid.get_link_token()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def plaid_configuration():
|
||||
def get_plaid_configuration():
|
||||
if frappe.db.get_single_value("Plaid Settings", "enabled"):
|
||||
plaid_settings = frappe.get_single("Plaid Settings")
|
||||
return {
|
||||
"plaid_public_key": plaid_settings.plaid_public_key,
|
||||
"plaid_env": plaid_settings.plaid_env,
|
||||
"link_token": plaid_settings.get_link_token(),
|
||||
"client_name": frappe.local.site
|
||||
}
|
||||
else:
|
||||
|
||||
return "disabled"
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_institution(token, response):
|
||||
response = json.loads(response)
|
||||
|
||||
plaid = PlaidConnector()
|
||||
access_token = plaid.get_access_token(token)
|
||||
bank = None
|
||||
|
||||
if not frappe.db.exists("Bank", response["institution"]["name"]):
|
||||
try:
|
||||
@ -44,7 +51,6 @@ def add_institution(token, response):
|
||||
bank.insert()
|
||||
except Exception:
|
||||
frappe.throw(frappe.get_traceback())
|
||||
|
||||
else:
|
||||
bank = frappe.get_doc("Bank", response["institution"]["name"])
|
||||
bank.plaid_access_token = access_token
|
||||
@ -52,6 +58,7 @@ def add_institution(token, response):
|
||||
|
||||
return bank
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_bank_accounts(response, bank, company):
|
||||
try:
|
||||
@ -92,9 +99,8 @@ def add_bank_accounts(response, bank, company):
|
||||
new_account.insert()
|
||||
|
||||
result.append(new_account.name)
|
||||
|
||||
except frappe.UniqueValidationError:
|
||||
frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(new_account.account_name))
|
||||
frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(account["name"]))
|
||||
except Exception:
|
||||
frappe.throw(frappe.get_traceback())
|
||||
|
||||
@ -103,6 +109,7 @@ def add_bank_accounts(response, bank, company):
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def add_account_type(account_type):
|
||||
try:
|
||||
frappe.get_doc({
|
||||
@ -122,10 +129,11 @@ def add_account_subtype(account_subtype):
|
||||
except Exception:
|
||||
frappe.throw(frappe.get_traceback())
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def sync_transactions(bank, bank_account):
|
||||
'''Sync transactions based on the last integration date as the start date, after the sync is completed
|
||||
add the transaction date of the oldest transaction as the last integration date'''
|
||||
"""Sync transactions based on the last integration date as the start date, after sync is completed
|
||||
add the transaction date of the oldest transaction as the last integration date."""
|
||||
last_transaction_date = frappe.db.get_value("Bank Account", bank_account, "last_integration_date")
|
||||
if last_transaction_date:
|
||||
start_date = formatdate(last_transaction_date, "YYYY-MM-dd")
|
||||
@ -147,10 +155,10 @@ def sync_transactions(bank, bank_account):
|
||||
len(result), bank_account, start_date, end_date))
|
||||
|
||||
frappe.db.set_value("Bank Account", bank_account, "last_integration_date", last_transaction_date)
|
||||
|
||||
except Exception:
|
||||
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
|
||||
|
||||
|
||||
def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
|
||||
access_token = None
|
||||
|
||||
@ -168,6 +176,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
|
||||
|
||||
return transactions
|
||||
|
||||
|
||||
def new_bank_transaction(transaction):
|
||||
result = []
|
||||
|
||||
@ -182,8 +191,8 @@ def new_bank_transaction(transaction):
|
||||
|
||||
status = "Pending" if transaction["pending"] == "True" else "Settled"
|
||||
|
||||
try:
|
||||
tags = []
|
||||
try:
|
||||
tags += transaction["category"]
|
||||
tags += ["Plaid Cat. {}".format(transaction["category_id"])]
|
||||
except KeyError:
|
||||
@ -216,6 +225,7 @@ def new_bank_transaction(transaction):
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def automatic_synchronization():
|
||||
settings = frappe.get_doc("Plaid Settings", "Plaid Settings")
|
||||
|
||||
@ -223,4 +233,8 @@ def automatic_synchronization():
|
||||
plaid_accounts = frappe.get_all("Bank Account", filters={"integration_id": ["!=", ""]}, fields=["name", "bank"])
|
||||
|
||||
for plaid_account in plaid_accounts:
|
||||
frappe.enqueue("erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions", bank=plaid_account.bank, bank_account=plaid_account.name)
|
||||
frappe.enqueue(
|
||||
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions",
|
||||
bank=plaid_account.bank,
|
||||
bank_account=plaid_account.name
|
||||
)
|
||||
|
@ -1,14 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
import frappe
|
||||
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings import plaid_configuration, add_account_type, add_account_subtype, new_bank_transaction, add_bank_accounts
|
||||
import json
|
||||
from frappe.utils.response import json_handler
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
|
||||
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings import (
|
||||
add_account_subtype, add_account_type, add_bank_accounts,
|
||||
new_bank_transaction, get_plaid_configuration)
|
||||
from frappe.utils.response import json_handler
|
||||
|
||||
|
||||
class TestPlaidSettings(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@ -31,7 +34,7 @@ class TestPlaidSettings(unittest.TestCase):
|
||||
|
||||
def test_plaid_disabled(self):
|
||||
frappe.db.set_value("Plaid Settings", None, "enabled", 0)
|
||||
self.assertTrue(plaid_configuration() == "disabled")
|
||||
self.assertTrue(get_plaid_configuration() == "disabled")
|
||||
|
||||
def test_add_account_type(self):
|
||||
add_account_type("brokerage")
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user