From 4c46687fb10110db0535569015f13dec7c3a0560 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 2 Apr 2015 22:00:34 +0530 Subject: [PATCH 1/5] [incoming sales email] to opportunity --- erpnext/crm/doctype/lead/lead.py | 7 ++--- .../crm/doctype/opportunity/opportunity.json | 26 +++++++++++++++---- .../crm/doctype/opportunity/opportunity.py | 22 +++++++++++++++- erpnext/hooks.py | 2 +- erpnext/patches.txt | 1 + erpnext/patches/v5_0/update_opportunity.py | 12 +++++++++ .../page/setup_wizard/install_fixtures.py | 2 +- erpnext/support/doctype/issue/issue.json | 5 ++-- 8 files changed, 64 insertions(+), 13 deletions(-) create mode 100644 erpnext/patches/v5_0/update_opportunity.py diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 009cfc20007..0fb8d48ebb3 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -33,6 +33,7 @@ class Lead(SellingController): }) self.set_status() + self.check_email_id_is_unique() if self.source == 'Campaign' and not self.campaign_name and session['user'] != 'Guest': frappe.throw(_("Campaign Name is required")) @@ -45,7 +46,6 @@ class Lead(SellingController): self.lead_owner = None def on_update(self): - self.check_email_id_is_unique() self.add_calendar_event() def add_calendar_event(self, opts=None, force=False): @@ -62,9 +62,10 @@ class Lead(SellingController): # validate email is unique email_list = frappe.db.sql("""select name from tabLead where email_id=%s""", self.email_id) + email_list = [e[0] for e in email_list if e[0]!=self.name] if len(email_list) > 1: - items = [e[0] for e in email_list if e[0]!=self.name] - frappe.throw(_("Email id must be unique, already exists for {0}").format(comma_and(items)), frappe.DuplicateEntryError) + frappe.throw(_("Email id must be unique, already exists for {0}").format(comma_and(email_list)), + frappe.DuplicateEntryError) def on_trash(self): frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""", diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json index 186e866d9dd..71b5337c140 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.json +++ b/erpnext/crm/doctype/opportunity/opportunity.json @@ -92,12 +92,20 @@ "width": "50%" }, { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "permlevel": 0, + "precision": "" + }, + { + "default": "Sales", "fieldname": "enquiry_type", "fieldtype": "Select", "label": "Opportunity Type", "oldfieldname": "enquiry_type", "oldfieldtype": "Select", - "options": "\nSales\nMaintenance", + "options": "Sales\nMaintenance", "permlevel": 0, "read_only": 0, "reqd": 1 @@ -118,6 +126,14 @@ "reqd": 1 }, { + "fieldname": "with_items", + "fieldtype": "Check", + "label": "With Items", + "permlevel": 0, + "precision": "" + }, + { + "depends_on": "with_items", "fieldname": "items_section", "fieldtype": "Section Break", "label": "", @@ -127,7 +143,7 @@ "read_only": 0 }, { - "description": "Items which do not exist in Item master can also be entered on customer's request", + "description": "", "fieldname": "items", "fieldtype": "Table", "label": "Items", @@ -391,8 +407,8 @@ "icon": "icon-info-sign", "idx": 1, "is_submittable": 1, - "modified": "2015-02-23 02:19:39.853388", - "modified_by": "Administrator", + "modified": "2015-04-02 21:33:55.090127", + "modified_by": "rmehta@gmail.com", "module": "CRM", "name": "Opportunity", "owner": "Administrator", @@ -432,5 +448,5 @@ "search_fields": "status,transaction_date,customer,lead,enquiry_type,territory,company", "sort_field": "modified", "sort_order": "DESC", - "title_field": "customer_name" + "title_field": "title" } \ No newline at end of file diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index b6909aa344c..aacd53e4217 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -10,6 +10,23 @@ from frappe.model.mapper import get_mapped_doc from erpnext.utilities.transaction_base import TransactionBase class Opportunity(TransactionBase): + def set_sender(self, email_id): + """Set lead against new opportunity""" + lead_name = frappe.get_value("Lead", {"email_id": email_id}) + if not lead_name: + lead = frappe.get_doc({ + "doctype": "Lead", + "email_id": email_id + "lead_name": email_id + }) + lead.insert() + lead_name = lead.name + + self.enquiry_from = "Lead" + self.lead = lead_name + + def set_subject(self, subject): + self.title = subject def validate(self): self._prev = frappe._dict({ @@ -28,6 +45,9 @@ class Opportunity(TransactionBase): self.validate_lead_cust() self.validate_cust_name() + if not self.title: + self.title = self.customer_name + from erpnext.accounts.utils import validate_fiscal_year validate_fiscal_year(self.transaction_date, self.fiscal_year, _("Opportunity Date"), self) @@ -118,7 +138,7 @@ class Opportunity(TransactionBase): def validate_item_details(self): if not self.get('items'): - frappe.throw(_("Items required")) + return # set missing values item_fields = ("item_name", "description", "item_group", "brand") diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 5d9dad0bf50..a6a2e25e441 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -25,7 +25,7 @@ on_logout = "erpnext.shopping_cart.utils.clear_cart_count" # website update_website_context = "erpnext.shopping_cart.utils.update_website_context" my_account_context = "erpnext.shopping_cart.utils.update_my_account_context" -email_append_to = ["Lead", "Job Applicant", "Opportunity", "Issue", "Warranty Claim"] +email_append_to = ["Job Applicant", "Opportunity", "Issue"] website_route_rules = [ {"from_route": "/orders", "to_route": "Sales Order"}, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 14ec93f8334..68d26797c91 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -134,4 +134,5 @@ erpnext.patches.v4_2.repost_sle_for_si_with_no_warehouse erpnext.patches.v5_0.newsletter execute:frappe.delete_doc("DocType", "Chart of Accounts") execute:frappe.delete_doc("DocType", "Style Settings") +erpnext.patches.v5_0.update_opportunities diff --git a/erpnext/patches/v5_0/update_opportunity.py b/erpnext/patches/v5_0/update_opportunity.py new file mode 100644 index 00000000000..8b5e1e023d7 --- /dev/null +++ b/erpnext/patches/v5_0/update_opportunity.py @@ -0,0 +1,12 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doctype("Opportunity") + + # all existing opportunities were with items + frappe.db.sql("update tabOpportunity set with_items=1, title=customer_name") + frappe.db.sql("update `tabEmail Account` set append_to='Opportunity' where append_to='Lead'") diff --git a/erpnext/setup/page/setup_wizard/install_fixtures.py b/erpnext/setup/page/setup_wizard/install_fixtures.py index 3d583aa705a..7bbe1395f8b 100644 --- a/erpnext/setup/page/setup_wizard/install_fixtures.py +++ b/erpnext/setup/page/setup_wizard/install_fixtures.py @@ -151,7 +151,7 @@ def install(country=None): {"attribute_value": _("White"), "abbr": "WHI"} ]}, - {'doctype': "Email Account", "email_id": "sales@example.com", "append_to": "Lead"}, + {'doctype': "Email Account", "email_id": "sales@example.com", "append_to": "Opportunity"}, {'doctype': "Email Account", "email_id": "support@example.com", "append_to": "Issue"}, {'doctype': "Email Account", "email_id": "jobs@example.com", "append_to": "Job Applicant"} ] diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json index eea90fdfa2b..81e28689971 100644 --- a/erpnext/support/doctype/issue/issue.json +++ b/erpnext/support/doctype/issue/issue.json @@ -64,6 +64,7 @@ "label": "Raised By (Email)", "oldfieldname": "raised_by", "oldfieldtype": "Data", + "options": "Email", "permlevel": 0, "reqd": 1 }, @@ -217,8 +218,8 @@ ], "icon": "icon-ticket", "idx": 1, - "modified": "2015-02-05 05:11:39.362659", - "modified_by": "Administrator", + "modified": "2015-04-02 21:27:09.303683", + "modified_by": "rmehta@gmail.com", "module": "Support", "name": "Issue", "owner": "Administrator", From 3c626faf8fb61e01ff84032d00ada9f4c6c7a23e Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 2 Apr 2015 22:02:33 +0530 Subject: [PATCH 2/5] [incoming sales email] to opportunity --- erpnext/crm/doctype/opportunity/opportunity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index aacd53e4217..80b35f2243d 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -16,10 +16,10 @@ class Opportunity(TransactionBase): if not lead_name: lead = frappe.get_doc({ "doctype": "Lead", - "email_id": email_id + "email_id": email_id, "lead_name": email_id }) - lead.insert() + lead.insert(ignore_permissions=True) lead_name = lead.name self.enquiry_from = "Lead" From 7c32770a695c00efcfb7facd16273c98c049db4d Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 2 Apr 2015 22:06:36 +0530 Subject: [PATCH 3/5] move opportunity item to CRM --- erpnext/crm/doctype/opportunity/opportunity.json | 4 ++-- erpnext/crm/doctype/opportunity_item/__init__.py | 0 .../doctype/opportunity_item/opportunity_item.json | 4 ++-- .../doctype/opportunity_item/opportunity_item.py | 8 ++++---- erpnext/patches/v5_0/update_opportunity.py | 4 +++- erpnext/selling/doctype/opportunity_item/README.md | 1 - erpnext/selling/doctype/opportunity_item/__init__.py | 1 - erpnext/support/doctype/issue/issue.json | 4 ++-- 8 files changed, 13 insertions(+), 13 deletions(-) create mode 100644 erpnext/crm/doctype/opportunity_item/__init__.py rename erpnext/{selling => crm}/doctype/opportunity_item/opportunity_item.json (97%) rename erpnext/{selling => crm}/doctype/opportunity_item/opportunity_item.py (65%) delete mode 100644 erpnext/selling/doctype/opportunity_item/README.md delete mode 100644 erpnext/selling/doctype/opportunity_item/__init__.py diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json index 71b5337c140..0b25ad626a2 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.json +++ b/erpnext/crm/doctype/opportunity/opportunity.json @@ -407,8 +407,8 @@ "icon": "icon-info-sign", "idx": 1, "is_submittable": 1, - "modified": "2015-04-02 21:33:55.090127", - "modified_by": "rmehta@gmail.com", + "modified": "2015-04-02 22:03:52.841173", + "modified_by": "Administrator", "module": "CRM", "name": "Opportunity", "owner": "Administrator", diff --git a/erpnext/crm/doctype/opportunity_item/__init__.py b/erpnext/crm/doctype/opportunity_item/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/selling/doctype/opportunity_item/opportunity_item.json b/erpnext/crm/doctype/opportunity_item/opportunity_item.json similarity index 97% rename from erpnext/selling/doctype/opportunity_item/opportunity_item.json rename to erpnext/crm/doctype/opportunity_item/opportunity_item.json index 889b05ff3e6..afc6fcb31df 100644 --- a/erpnext/selling/doctype/opportunity_item/opportunity_item.json +++ b/erpnext/crm/doctype/opportunity_item/opportunity_item.json @@ -133,9 +133,9 @@ ], "idx": 1, "istable": 1, - "modified": "2015-02-23 02:09:55.105233", + "modified": "2015-04-02 22:04:26.867653", "modified_by": "Administrator", - "module": "Selling", + "module": "CRM", "name": "Opportunity Item", "owner": "Administrator", "permissions": [] diff --git a/erpnext/selling/doctype/opportunity_item/opportunity_item.py b/erpnext/crm/doctype/opportunity_item/opportunity_item.py similarity index 65% rename from erpnext/selling/doctype/opportunity_item/opportunity_item.py rename to erpnext/crm/doctype/opportunity_item/opportunity_item.py index 7aff5ca80a4..7a5ed63f883 100644 --- a/erpnext/selling/doctype/opportunity_item/opportunity_item.py +++ b/erpnext/crm/doctype/opportunity_item/opportunity_item.py @@ -1,10 +1,10 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors +# For license information, please see license.txt from __future__ import unicode_literals import frappe - from frappe.model.document import Document class OpportunityItem(Document): - pass \ No newline at end of file + pass diff --git a/erpnext/patches/v5_0/update_opportunity.py b/erpnext/patches/v5_0/update_opportunity.py index 8b5e1e023d7..8eb45c48e7e 100644 --- a/erpnext/patches/v5_0/update_opportunity.py +++ b/erpnext/patches/v5_0/update_opportunity.py @@ -5,8 +5,10 @@ from __future__ import unicode_literals import frappe def execute(): - frappe.reload_doctype("Opportunity") + frappe.reload_doc('crm', 'doctype', 'opportunity') + frappe.reload_doc('crm', 'doctype', 'opportunity_item') # all existing opportunities were with items + frappe.db.sql("update `tabDocType` set module = 'CRM' where name='Opportunity Item'") frappe.db.sql("update tabOpportunity set with_items=1, title=customer_name") frappe.db.sql("update `tabEmail Account` set append_to='Opportunity' where append_to='Lead'") diff --git a/erpnext/selling/doctype/opportunity_item/README.md b/erpnext/selling/doctype/opportunity_item/README.md deleted file mode 100644 index 810c10bb69f..00000000000 --- a/erpnext/selling/doctype/opportunity_item/README.md +++ /dev/null @@ -1 +0,0 @@ -Items considered in the parent Opportunity. \ No newline at end of file diff --git a/erpnext/selling/doctype/opportunity_item/__init__.py b/erpnext/selling/doctype/opportunity_item/__init__.py deleted file mode 100644 index baffc488252..00000000000 --- a/erpnext/selling/doctype/opportunity_item/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json index 81e28689971..8ba1b5a33c0 100644 --- a/erpnext/support/doctype/issue/issue.json +++ b/erpnext/support/doctype/issue/issue.json @@ -218,8 +218,8 @@ ], "icon": "icon-ticket", "idx": 1, - "modified": "2015-04-02 21:27:09.303683", - "modified_by": "rmehta@gmail.com", + "modified": "2015-04-02 22:06:02.684820", + "modified_by": "Administrator", "module": "Support", "name": "Issue", "owner": "Administrator", From bb2041d22c889e470eeae241ff40e0b520e2ef80 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 2 Apr 2015 22:11:16 +0530 Subject: [PATCH 4/5] [patch] fix --- erpnext/patches.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 68d26797c91..eb7a3d13119 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -134,5 +134,5 @@ erpnext.patches.v4_2.repost_sle_for_si_with_no_warehouse erpnext.patches.v5_0.newsletter execute:frappe.delete_doc("DocType", "Chart of Accounts") execute:frappe.delete_doc("DocType", "Style Settings") -erpnext.patches.v5_0.update_opportunities +erpnext.patches.v5_0.update_opportunity From f63a3d4a80ba293c3216ea0a0ea7c95e388259b5 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 3 Apr 2015 10:59:48 +0530 Subject: [PATCH 5/5] [fiscal year] automatically set year end date if more than one year --- .../doctype/fiscal_year/fiscal_year.py | 12 +++++++----- .../doctype/fiscal_year/test_fiscal_year.py | 18 ++++++++++++++++-- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py index 5d0c20dc231..1baed417006 100644 --- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py +++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py @@ -5,12 +5,11 @@ from __future__ import unicode_literals import frappe from frappe import msgprint, _ from frappe.utils import getdate, add_days, add_years, cstr -from datetime import timedelta +from dateutil.relativedelta import relativedelta from frappe.model.document import Document class FiscalYear(Document): - def set_as_default(self): frappe.db.set_value("Global Defaults", None, "current_fiscal_year", self.name) frappe.get_doc("Global Defaults").on_update() @@ -24,18 +23,21 @@ class FiscalYear(Document): year_start_end_dates = frappe.db.sql("""select year_start_date, year_end_date from `tabFiscal Year` where name=%s""", (self.name)) + self.validate_dates() + if year_start_end_dates: if getdate(self.year_start_date) != year_start_end_dates[0][0] or getdate(self.year_end_date) != year_start_end_dates[0][1]: frappe.throw(_("Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved.")) - def on_update(self): - # validate year start date and year end date + def validate_dates(self): if getdate(self.year_start_date) > getdate(self.year_end_date): frappe.throw(_("Fiscal Year Start Date should not be greater than Fiscal Year End Date")) if (getdate(self.year_end_date) - getdate(self.year_start_date)).days > 366: - frappe.throw(_("Fiscal Year Start Date and Fiscal Year End Date cannot be more than a year apart.")) + date = getdate(self.year_start_date) + relativedelta(years=1) - relativedelta(days=1) + self.year_end_date = date.strftime("%Y-%m-%d") + def on_update(self): check_duplicate_fiscal_year(self) @frappe.whitelist() diff --git a/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py index 9d524ac6e93..092d34a939e 100644 --- a/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py +++ b/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py @@ -3,6 +3,20 @@ from __future__ import unicode_literals -import frappe +import frappe, unittest + +test_records = frappe.get_test_records('Fiscal Year') + +class TestFiscalYear(unittest.TestCase): + def test_extra_year(self): + if frappe.db.exists("Fiscal Year", "_Test Fiscal Year 2000"): + frappe.delete_doc("Fiscal Year", "_Test Fiscal Year 2000") + fy = frappe.get_doc({ + "doctype": "Fiscal Year", + "year": "_Test Fiscal Year 2000", + "year_end_date": "2002-12-31", + "year_start_date": "2000-04-01" + }) + fy.insert() + self.assertEquals(fy.year_end_date, '2001-03-31') -test_records = frappe.get_test_records('Fiscal Year') \ No newline at end of file