[enhancement] add status in customer, supplier
diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
index ce76354..c3f399d 100644
--- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
+++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
@@ -41,7 +41,7 @@
def on_update(self):
check_duplicate_fiscal_year(self)
-
+
def validate_overlap(self):
existing_fiscal_years = frappe.db.sql("""select name from `tabFiscal Year`
where (
@@ -60,18 +60,18 @@
for existing in existing_fiscal_years:
company_for_existing = frappe.db.sql_list("""select company from `tabFiscal Year Company`
where parent=%s""", existing.name)
-
+
overlap = False
if not self.get("companies") or not company_for_existing:
overlap = True
-
+
for d in self.get("companies"):
if d.company in company_for_existing:
overlap = True
-
+
if overlap:
frappe.throw(_("Year start date or end date is overlapping with {0}. To avoid please set company")
- .format(existing.name))
+ .format(existing.name), frappe.NameError)
@frappe.whitelist()
def check_duplicate_fiscal_year(doc):
diff --git a/erpnext/accounts/party_status.py b/erpnext/accounts/party_status.py
new file mode 100644
index 0000000..d0f60a5
--- /dev/null
+++ b/erpnext/accounts/party_status.py
@@ -0,0 +1,72 @@
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+
+from frappe.utils import evaluate_filters
+from erpnext.startup.notifications import get_notification_config
+
+status_depends_on = {
+ 'Customer': ('Opportunity', 'Quotation', 'Sales Order', 'Sales Invoice', 'Project', 'Issue'),
+ 'Supplier': ('Supplier Quotation', 'Purchase Order', 'Purchase Invoice')
+}
+
+default_status = {
+ 'Customer': 'Active',
+ 'Supplier': None
+}
+
+def notify_status(doc, method):
+ '''Notify status to customer, supplier'''
+
+ party_type = None
+ for key, doctypes in status_depends_on.iteritems():
+ if doc.doctype in doctypes:
+ party_type = key
+ break
+
+ if not party_type:
+ return
+
+ party = frappe.get_doc(party_type, doc.get(party_type.lower()))
+ config = get_notification_config().get('for_doctype').get(doc.doctype)
+
+ status = None
+ if config:
+ if evaluate_filters(doc, config):
+ # filters match, passed document is open
+ status = 'Open'
+
+ if status=='Open':
+ if party.status != 'Open':
+ # party not open, make it open
+ party.status = 'Open'
+ party.save(ignore_permissions=True)
+
+ else:
+ if party.status == 'Open':
+ # may be open elsewhere, check
+ # default status
+ party.status = status
+ update_status(party, )
+
+def update_status(doc):
+ '''Set status as open if there is any open notification'''
+ config = get_notification_config()
+
+ original_status = doc.status
+
+ doc.status = default_status[doc.doctype]
+ for doctype in status_depends_on[doc.doctype]:
+ filters = config.get('for_doctype', {}).get(doctype) or {}
+ filters[doc.doctype.lower()] = doc.name
+ if filters:
+ open_count = frappe.get_all(doctype, fields='count(*) as count', filters=filters)
+ if open_count[0].count > 0:
+ doc.status = 'Open'
+ break
+
+ if doc.status != original_status:
+ doc.db_set('status', doc.status)
diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json
index 46ea98f..bafa142 100644
--- a/erpnext/buying/doctype/supplier/supplier.json
+++ b/erpnext/buying/doctype/supplier/supplier.json
@@ -118,6 +118,32 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Status",
+ "length": 0,
+ "no_copy": 0,
+ "options": "\nOpen",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
"fieldname": "column_break0",
"fieldtype": "Column Break",
"hidden": 0,
@@ -658,7 +684,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-04-06 05:39:47.329568",
+ "modified": "2016-04-08 07:43:07.541419",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier",
diff --git a/erpnext/buying/doctype/supplier/supplier_list.js b/erpnext/buying/doctype/supplier/supplier_list.js
index d26932c..acf8e68 100644
--- a/erpnext/buying/doctype/supplier/supplier_list.js
+++ b/erpnext/buying/doctype/supplier/supplier_list.js
@@ -1,3 +1,8 @@
frappe.listview_settings['Supplier'] = {
- add_fields: ["supplier_name", "supplier_type"]
+ add_fields: ["supplier_name", "supplier_type", 'status'],
+ get_indicator: function(doc) {
+ if(doc.status==="Open") {
+ return [doc.status, "red", "status,=," + doc.status];
+ }
+ }
};
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 72bc40d..1a521c6 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -127,15 +127,18 @@
"on_update": "erpnext.hr.doctype.employee.employee.update_user_permissions",
"on_update": "erpnext.utilities.doctype.contact.contact.update_contact"
},
- "Sales Taxes and Charges Template": {
- "on_update": "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.validate_cart_settings"
- },
- "Price List": {
+ ("Sales Taxes and Charges Template", 'Price List'): {
"on_update": "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.validate_cart_settings"
},
"Address": {
"validate": "erpnext.shopping_cart.cart.set_customer_in_address"
- }
+ },
+
+ # bubble transaction notification on master
+ ('Opportunity', 'Quotation', 'Sales Order', 'Sales Invoice', 'Supplier Quotation',
+ 'Purchase Order', 'Purchase Invoice', 'Project', 'Issue'): {
+ 'on_update': 'erpnext.accounts.party_status.notify_status'
+ }
}
scheduler_events = {
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 5c2376e..975d589 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -260,3 +260,4 @@
erpnext.patches.v6_27.fix_recurring_order_status
erpnext.patches.v6_20x.remove_customer_supplier_roles
erpnext.patches.v6_24.rename_item_field
+erpnext.patches.v7_0.update_party_status
diff --git a/erpnext/patches/v7_0/__init__.py b/erpnext/patches/v7_0/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/patches/v7_0/__init__.py
diff --git a/erpnext/patches/v7_0/update_party_status.py b/erpnext/patches/v7_0/update_party_status.py
new file mode 100644
index 0000000..c9cab95
--- /dev/null
+++ b/erpnext/patches/v7_0/update_party_status.py
@@ -0,0 +1,7 @@
+import frappe
+
+def execute():
+ for doctype in ('Customer', 'Supplier'):
+ for doc in frappe.get_all(doctype):
+ doc = frappe.get_doc(doctype, doc.name)
+ doc.update_status()
\ No newline at end of file
diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json
index 6974eb5..ba7aa24 100644
--- a/erpnext/selling/doctype/customer/customer.json
+++ b/erpnext/selling/doctype/customer/customer.json
@@ -170,6 +170,33 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "default": "Active",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Status",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Active\nDormant\nOpen",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
"fieldname": "column_break0",
"fieldtype": "Column Break",
"hidden": 0,
@@ -927,7 +954,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-04-07 01:25:25.676480",
+ "modified": "2016-04-08 07:43:01.381976",
"modified_by": "Administrator",
"module": "Selling",
"name": "Customer",
diff --git a/erpnext/selling/doctype/customer/customer_list.js b/erpnext/selling/doctype/customer/customer_list.js
index 012d3f8..d650b01 100644
--- a/erpnext/selling/doctype/customer/customer_list.js
+++ b/erpnext/selling/doctype/customer/customer_list.js
@@ -1,3 +1,12 @@
frappe.listview_settings['Customer'] = {
- add_fields: ["customer_name", "territory", "customer_group", "customer_type"]
+ add_fields: ["customer_name", "territory", "customer_group", "customer_type", 'status'],
+ get_indicator: function(doc) {
+ color = {
+ 'Open': 'red',
+ 'Active': 'green',
+ 'Dormant', 'dardgrey'
+ }
+ return [__(doc.status), color[doc.status], "status,=," + doc.status];
+ }
+
};
diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py
index 1ca5ce7..85b216b 100644
--- a/erpnext/selling/doctype/customer/test_customer.py
+++ b/erpnext/selling/doctype/customer/test_customer.py
@@ -11,6 +11,8 @@
test_ignore = ["Price List"]
+test_dependencies = ['Quotation']
+
test_records = frappe.get_test_records('Customer')
class TestCustomer(unittest.TestCase):
@@ -99,24 +101,65 @@
frappe.db.sql("delete from `tabCustomer` where customer_name='_Test Customer 1'")
if not frappe.db.get_value("Customer", "_Test Customer 1"):
- test_customer_1 = frappe.get_doc({
- "customer_group": "_Test Customer Group",
- "customer_name": "_Test Customer 1",
- "customer_type": "Individual",
- "doctype": "Customer",
- "territory": "_Test Territory"
- }).insert(ignore_permissions=True)
+ test_customer_1 = frappe.get_doc(
+ get_customer_dict('_Test Customer 1')).insert(ignore_permissions=True)
else:
test_customer_1 = frappe.get_doc("Customer", "_Test Customer 1")
- duplicate_customer = frappe.get_doc({
- "customer_group": "_Test Customer Group",
- "customer_name": "_Test Customer 1",
- "customer_type": "Individual",
- "doctype": "Customer",
- "territory": "_Test Territory"
- }).insert(ignore_permissions=True)
+ duplicate_customer = frappe.get_doc(
+ get_customer_dict('_Test Customer 1')).insert(ignore_permissions=True)
self.assertEquals("_Test Customer 1", test_customer_1.name)
self.assertEquals("_Test Customer 1 - 1", duplicate_customer.name)
self.assertEquals(test_customer_1.customer_name, duplicate_customer.customer_name)
+
+ def test_party_status_open(self):
+ from erpnext.selling.doctype.quotation.test_quotation import get_quotation_dict
+
+ customer = frappe.get_doc(get_customer_dict('Party Status Test')).insert()
+ self.assertEquals(frappe.db.get_value('Customer', customer.name, 'status'), 'Active')
+
+ quotation = frappe.get_doc(get_quotation_dict(customer=customer.name)).insert()
+ self.assertEquals(frappe.db.get_value('Customer', customer.name, 'status'), 'Open')
+
+ quotation.submit()
+ self.assertEquals(frappe.db.get_value('Customer', customer.name, 'status'), 'Active')
+
+ quotation.cancel()
+ quotation.delete()
+ customer.delete()
+
+ def test_party_status_close(self):
+ from erpnext.selling.doctype.quotation.test_quotation import get_quotation_dict
+
+ customer = frappe.get_doc(get_customer_dict('Party Status Test')).insert()
+ self.assertEquals(frappe.db.get_value('Customer', customer.name, 'status'), 'Active')
+
+ # open quotation
+ quotation = frappe.get_doc(get_quotation_dict(customer=customer.name)).insert()
+ self.assertEquals(frappe.db.get_value('Customer', customer.name, 'status'), 'Open')
+
+ # close quotation (submit)
+ quotation.submit()
+
+ quotation1 = frappe.get_doc(get_quotation_dict(customer=customer.name)).insert()
+
+ # still open
+ self.assertEquals(frappe.db.get_value('Customer', customer.name, 'status'), 'Open')
+
+ quotation.cancel()
+ quotation.delete()
+
+ quotation1.delete()
+
+ customer.delete()
+
+def get_customer_dict(customer_name):
+ return {
+ "customer_group": "_Test Customer Group",
+ "customer_name": customer_name,
+ "customer_type": "Individual",
+ "doctype": "Customer",
+ "territory": "_Test Territory"
+ }
+
diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py
index e3b359d..36cc472 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -69,3 +69,21 @@
si.save()
test_records = frappe.get_test_records('Quotation')
+
+def get_quotation_dict(customer=None, item_code=None):
+ if not customer:
+ customer = '_Test Customer'
+ if not item_code:
+ item_code = '_Test Item'
+
+ return {
+ 'doctype': 'Quotation',
+ 'customer': customer,
+ 'items': [
+ {
+ 'item_code': item_code,
+ 'qty': 1,
+ 'rate': 100
+ }
+ ]
+ }
\ No newline at end of file