Merge remote-tracking branch 'frappe/develop' into develop
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index 683734b..f1d822a 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -49,7 +49,7 @@
self.root_type = par.root_type
def validate_root_details(self):
- #does not exists parent
+ # does not exists parent
if frappe.db.exists("Account", self.name):
if not frappe.db.get_value("Account", self.name, "parent_account"):
throw(_("Root cannot be edited."))
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.js b/erpnext/accounts/doctype/cost_center/cost_center.js
index 6946bcb..fb649e6 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.js
+++ b/erpnext/accounts/doctype/cost_center/cost_center.js
@@ -17,7 +17,7 @@
return {
filters:[
['Account', 'company', '=', me.frm.doc.company],
- ['Account', 'report_type', '=', 'Profit and Loss'],
+ ['Account', 'root_type', '=', 'Expense'],
['Account', 'is_group', '=', '0'],
]
}
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.py b/erpnext/accounts/doctype/cost_center/cost_center.py
index f26c80b..0f51a00 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.py
+++ b/erpnext/accounts/doctype/cost_center/cost_center.py
@@ -3,9 +3,7 @@
from __future__ import unicode_literals
import frappe
-
-from frappe import msgprint, _
-
+from frappe import _
from frappe.utils.nestedset import NestedSet
class CostCenter(NestedSet):
@@ -14,18 +12,46 @@
def autoname(self):
self.name = self.cost_center_name.strip() + ' - ' + \
frappe.db.get_value("Company", self.company, "abbr")
+
+
+ def validate(self):
+ self.validate_mandatory()
+ self.validate_accounts()
def validate_mandatory(self):
if self.cost_center_name != self.company and not self.parent_cost_center:
- msgprint(_("Please enter parent cost center"), raise_exception=1)
+ frappe.throw(_("Please enter parent cost center"))
elif self.cost_center_name == self.company and self.parent_cost_center:
- msgprint(_("Root cannot have a parent cost center"), raise_exception=1)
+ frappe.throw(_("Root cannot have a parent cost center"))
+
+ def validate_accounts(self):
+ if self.is_group==1 and self.get("budgets"):
+ frappe.throw(_("Budget cannot be set for Group Cost Center"))
+
+ check_acc_list = []
+ for d in self.get('budgets'):
+ if d.account:
+ account_details = frappe.db.get_value("Account", d.account,
+ ["is_group", "company", "root_type"], as_dict=1)
+ if account_details.is_group:
+ frappe.throw(_("Budget cannot be assigned against Group Account {0}").format(d.account))
+ elif account_details.company != self.company:
+ frappe.throw(_("Account {0} does not belongs to company {1}").format(d.account, self.company))
+ elif account_details.root_type != "Expense":
+ frappe.throw(_("Budget cannot be assigned against {0}, as it's not an Expense account")
+ .format(d.account))
+
+ if [d.account, d.fiscal_year] in check_acc_list:
+ frappe.throw(_("Account {0} has been entered more than once for fiscal year {1}")
+ .format(d.account, d.fiscal_year))
+ else:
+ check_acc_list.append([d.account, d.fiscal_year])
def convert_group_to_ledger(self):
if self.check_if_child_exists():
- msgprint(_("Cannot convert Cost Center to ledger as it has child nodes"), raise_exception=1)
+ frappe.throw(_("Cannot convert Cost Center to ledger as it has child nodes"))
elif self.check_gle_exists():
- msgprint(_("Cost Center with existing transactions can not be converted to ledger"), raise_exception=1)
+ frappe.throw(_("Cost Center with existing transactions can not be converted to ledger"))
else:
self.is_group = 0
self.save()
@@ -33,7 +59,7 @@
def convert_ledger_to_group(self):
if self.check_gle_exists():
- msgprint(_("Cost Center with existing transactions can not be converted to group"), raise_exception=1)
+ frappe.throw(_("Cost Center with existing transactions can not be converted to group"))
else:
self.is_group = 1
self.save()
@@ -46,21 +72,6 @@
return frappe.db.sql("select name from `tabCost Center` where \
parent_cost_center = %s and docstatus != 2", self.name)
- def validate_budget_details(self):
- check_acc_list = []
- for d in self.get('budgets'):
- if self.is_group==1:
- msgprint(_("Budget cannot be set for Group Cost Centers"), raise_exception=1)
-
- if [d.account, d.fiscal_year] in check_acc_list:
- msgprint(_("Account {0} has been entered more than once for fiscal year {1}").format(d.account, d.fiscal_year), raise_exception=1)
- else:
- check_acc_list.append([d.account, d.fiscal_year])
-
- def validate(self):
- self.validate_mandatory()
- self.validate_budget_details()
-
def before_rename(self, olddn, newdn, merge=False):
# Add company abbr if not provided
from erpnext.setup.doctype.company.company import get_name_with_abbr
diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.py b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.py
index a2ba72d..bffa8e6 100644
--- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.py
+++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.py
@@ -4,10 +4,9 @@
from __future__ import unicode_literals
from frappe.model.document import Document
-from erpnext.controllers.accounts_controller import validate_taxes_and_charges, validate_inclusive_tax
+from erpnext.accounts.doctype.sales_taxes_and_charges_template.sales_taxes_and_charges_template \
+ import valdiate_taxes_and_charges_template
class PurchaseTaxesandChargesTemplate(Document):
def validate(self):
- for tax in self.get("taxes"):
- validate_taxes_and_charges(tax)
- validate_inclusive_tax(tax, self)
+ valdiate_taxes_and_charges_template(self)
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
index 6721bd8..b36287b 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
@@ -5,21 +5,25 @@
import frappe
from frappe.model.document import Document
from erpnext.controllers.accounts_controller import validate_taxes_and_charges, validate_inclusive_tax
+from frappe.utils.nestedset import get_root_of
class SalesTaxesandChargesTemplate(Document):
def validate(self):
- if self.is_default == 1:
- frappe.db.sql("""update `tabSales Taxes and Charges Template`
- set is_default = 0
- where ifnull(is_default,0) = 1
- and name != %s and company = %s""",
- (self.name, self.company))
+ valdiate_taxes_and_charges_template(self)
- # at least one territory
- self.validate_table_has_rows("territories")
+def valdiate_taxes_and_charges_template(doc):
+ if not doc.is_default and not frappe.get_all(doc.doctype, filters={"is_default": 1}):
+ doc.is_default = 1
- for tax in self.get("taxes"):
- validate_taxes_and_charges(tax)
- validate_inclusive_tax(tax, self)
+ if doc.is_default == 1:
+ frappe.db.sql("""update `tab{0}` set is_default = 0
+ where ifnull(is_default,0) = 1 and name != %s and company = %s""".format(doc.doctype),
+ (doc.name, doc.company))
+ if doc.meta.get_field("territories"):
+ if not doc.territories:
+ doc.append("territories", {"territory": get_root_of("Territory") })
+ for tax in doc.get("taxes"):
+ validate_taxes_and_charges(tax)
+ validate_inclusive_tax(tax, doc)
diff --git a/erpnext/change_log/current/sms.md b/erpnext/change_log/current/sms.md
new file mode 100644
index 0000000..bac293f
--- /dev/null
+++ b/erpnext/change_log/current/sms.md
@@ -0,0 +1 @@
+- Now system will give SMS delivery message and maintain a log
\ No newline at end of file
diff --git a/erpnext/config/crm.py b/erpnext/config/crm.py
index 3a7ab18..d7a6b2e 100644
--- a/erpnext/config/crm.py
+++ b/erpnext/config/crm.py
@@ -42,6 +42,11 @@
"name": "SMS Center",
"description":_("Send mass SMS to your contacts"),
},
+ {
+ "type": "doctype",
+ "name": "SMS Log",
+ "description":_("Logs for maintaining sms delivery status"),
+ }
]
},
{
diff --git a/erpnext/config/selling.py b/erpnext/config/selling.py
index 5433964..62dfe23 100644
--- a/erpnext/config/selling.py
+++ b/erpnext/config/selling.py
@@ -50,6 +50,11 @@
},
{
"type": "doctype",
+ "name": "SMS Log",
+ "description":_("Logs for maintaining sms delivery status"),
+ },
+ {
+ "type": "doctype",
"name": "Newsletter",
"description": _("Newsletters to contacts, leads."),
},
diff --git a/erpnext/contacts/__init__.py b/erpnext/contacts/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/contacts/__init__.py
+++ /dev/null
diff --git a/erpnext/contacts/doctype/__init__.py b/erpnext/contacts/doctype/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/contacts/doctype/__init__.py
+++ /dev/null
diff --git a/erpnext/contacts/doctype/party_type/__init__.py b/erpnext/contacts/doctype/party_type/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/contacts/doctype/party_type/__init__.py
+++ /dev/null
diff --git a/erpnext/contacts/doctype/party_type/party_type.json b/erpnext/contacts/doctype/party_type/party_type.json
deleted file mode 100644
index 19ffefb..0000000
--- a/erpnext/contacts/doctype/party_type/party_type.json
+++ /dev/null
@@ -1,92 +0,0 @@
-{
- "allow_rename": 1,
- "autoname": "field:party_type_name",
- "creation": "2014-04-07 12:32:18.010384",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Master",
- "fields": [
- {
- "fieldname": "party_type_name",
- "fieldtype": "Data",
- "in_list_view": 1,
- "label": "Party Type Name",
- "permlevel": 0,
- "reqd": 1
- },
- {
- "fieldname": "parent_party_type",
- "fieldtype": "Link",
- "label": "Parent Party Type",
- "options": "Party Type",
- "permlevel": 0
- },
- {
- "default": "Yes",
- "fieldname": "allow_children",
- "fieldtype": "Select",
- "label": "Allow Children",
- "options": "Yes\nNo",
- "permlevel": 0
- },
- {
- "fieldname": "default_price_list",
- "fieldtype": "Link",
- "ignore_user_permissions": 1,
- "label": "Default Price List",
- "options": "Price List",
- "permlevel": 0
- },
- {
- "fieldname": "lft",
- "fieldtype": "Int",
- "hidden": 1,
- "label": "LFT",
- "permlevel": 0,
- "read_only": 1,
- "search_index": 1
- },
- {
- "fieldname": "rgt",
- "fieldtype": "Int",
- "hidden": 1,
- "label": "RGT",
- "permlevel": 0,
- "read_only": 1,
- "search_index": 1
- },
- {
- "fieldname": "old_parent",
- "fieldtype": "Data",
- "hidden": 1,
- "label": "Old Parent",
- "permlevel": 0,
- "read_only": 1
- }
- ],
- "modified": "2015-02-05 05:11:42.046004",
- "modified_by": "Administrator",
- "module": "Contacts",
- "name": "Party Type",
- "owner": "Administrator",
- "permissions": [
- {
- "apply_user_permissions": 1,
- "create": 1,
- "permlevel": 0,
- "read": 1,
- "role": "Sales User",
- "share": 1,
- "write": 1
- },
- {
- "apply_user_permissions": 1,
- "create": 1,
- "permlevel": 0,
- "read": 1,
- "role": "Purchase User",
- "share": 1,
- "write": 1
- }
- ]
-}
\ No newline at end of file
diff --git a/erpnext/contacts/doctype/party_type/party_type.py b/erpnext/contacts/doctype/party_type/party_type.py
deleted file mode 100644
index d21216f..0000000
--- a/erpnext/contacts/doctype/party_type/party_type.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-from frappe.utils.nestedset import NestedSet
-
-class PartyType(NestedSet):
- nsm_parent_field = 'parent_party_type';
diff --git a/erpnext/manufacturing/doctype/production_order/production_order.py b/erpnext/manufacturing/doctype/production_order/production_order.py
index 26af40a..93ce5e1 100644
--- a/erpnext/manufacturing/doctype/production_order/production_order.py
+++ b/erpnext/manufacturing/doctype/production_order/production_order.py
@@ -335,12 +335,15 @@
res = frappe.db.sql("""select stock_uom, description
from `tabItem` where (ifnull(end_of_life, "0000-00-00")="0000-00-00" or end_of_life > now())
and name=%s""", item, as_dict=1)
-
if not res:
return {}
res = res[0]
res["bom_no"] = frappe.db.get_value("BOM", filters={"item": item, "is_default": 1})
+ if not res["bom_no"]:
+ variant_of= frappe.db.get_value("Item", item, "variant_of")
+ if variant_of:
+ res["bom_no"] = frappe.db.get_value("BOM", filters={"item": variant_of, "is_default": 1})
return res
@frappe.whitelist()
diff --git a/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py b/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py
index 86a14d8..271abac 100644
--- a/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py
+++ b/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py
@@ -9,6 +9,7 @@
from frappe.model.document import Document
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
+from erpnext.manufacturing.doctype.production_order.production_order import get_item_details
class ProductionPlanningTool(Document):
def __init__(self, arg1, arg2=None):
@@ -27,16 +28,7 @@
return ret
def get_item_details(self, item_code):
- """ Pull other item details from item master"""
-
- item = frappe.db.sql("""select description, stock_uom, default_bom
- from `tabItem` where name = %s""", item_code, as_dict =1)
- ret = {
- 'description' : item and item[0]['description'],
- 'stock_uom' : item and item[0]['stock_uom'],
- 'bom_no' : item and item[0]['default_bom']
- }
- return ret
+ return get_item_details(item_code)
def clear_so_table(self):
self.set('sales_orders', [])
@@ -142,15 +134,14 @@
self.clear_item_table()
for p in items:
- item_details = frappe.db.sql("""select description, stock_uom, default_bom
- from tabItem where name=%s""", p['item_code'])
+ item_details = get_item_details(p['item_code'])
pi = self.append('items', {})
pi.sales_order = p['parent']
pi.warehouse = p['warehouse']
pi.item_code = p['item_code']
- pi.description = item_details and item_details[0][0] or ''
- pi.stock_uom = item_details and item_details[0][1] or ''
- pi.bom_no = item_details and item_details[0][2] or ''
+ pi.description = item_details and item_details.description or ''
+ pi.stock_uom = item_details and item_details.stock_uom or ''
+ pi.bom_no = item_details and item_details.bom_no or ''
pi.so_pending_qty = flt(p['pending_qty'])
pi.planned_qty = flt(p['pending_qty'])
diff --git a/erpnext/modules.txt b/erpnext/modules.txt
index 67c856d..dfca2f2 100644
--- a/erpnext/modules.txt
+++ b/erpnext/modules.txt
@@ -9,6 +9,5 @@
Stock
Support
Utilities
-Contacts
Shopping Cart
Hub Node
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 269dcba..c735507 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -177,3 +177,4 @@
execute:frappe.rename_doc("DocType", "Salary Manager", "Process Payroll", force=True)
erpnext.patches.v5_1.rename_roles
erpnext.patches.v5_1.default_bom
+execute:frappe.delete_doc("DocType", "Party Type")
\ No newline at end of file
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index e8a772a..d45fbba 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -272,6 +272,10 @@
def postprocess(source, doc):
doc.material_request_type = "Purchase"
+ so = frappe.get_doc("Sales Order", source_name)
+
+ item_table = "Packed Item" if so.packed_items else "Sales Order Item"
+
doc = get_mapped_doc("Sales Order", source_name, {
"Sales Order": {
"doctype": "Material Request",
@@ -279,7 +283,7 @@
"docstatus": ["=", 1]
}
},
- "Sales Order Item": {
+ item_table: {
"doctype": "Material Request Item",
"field_map": {
"parent": "sales_order_no",
diff --git a/erpnext/setup/doctype/sms_settings/sms_settings.py b/erpnext/setup/doctype/sms_settings/sms_settings.py
index 1403ee5..9099863 100644
--- a/erpnext/setup/doctype/sms_settings/sms_settings.py
+++ b/erpnext/setup/doctype/sms_settings/sms_settings.py
@@ -5,7 +5,7 @@
import frappe
from frappe import _, throw, msgprint
-from frappe.utils import cstr, nowdate
+from frappe.utils import nowdate
from frappe.model.document import Document
@@ -63,8 +63,7 @@
}
if frappe.db.get_value('SMS Settings', None, 'sms_gateway_url'):
- ret = send_via_gateway(arg)
- msgprint(ret)
+ send_via_gateway(arg)
else:
msgprint(_("Please Update SMS Settings"))
@@ -74,12 +73,17 @@
for d in ss.get("parameters"):
args[d.parameter] = d.value
- resp = []
+ success_list = []
for d in arg.get('receiver_list'):
args[ss.receiver_parameter] = d
- resp.append(send_request(ss.sms_gateway_url, args))
+ status = send_request(ss.sms_gateway_url, args)
+ if status == 200:
+ success_list.append(d)
- return resp
+ if len(success_list) > 0:
+ args.update(arg)
+ create_sms_log(args, success_list)
+ frappe.msgprint(_("SMS sent to following numbers: {0}").format("\n" + "\n".join(success_list)))
# Send Request
# =========================================================
@@ -90,11 +94,8 @@
headers = {}
headers['Accept'] = "text/plain, text/html, */*"
conn.request('GET', api_url + urllib.urlencode(args), headers = headers) # send request
- resp = conn.getresponse() # get response
- resp = resp.read()
- if resp.status==200:
- create_sms_log()
- return resp
+ resp = conn.getresponse() # get response
+ return resp.status
# Split gateway url to server and api url
# =========================================================
@@ -109,12 +110,13 @@
# Create SMS Log
# =========================================================
-def create_sms_log(arg, sent_sms):
- sl = frappe.get_doc('SMS Log')
- sl.sender_name = arg['sender_name']
+def create_sms_log(args, sent_to):
+ sl = frappe.new_doc('SMS Log')
+ sl.sender_name = args['sender_name']
sl.sent_on = nowdate()
- sl.receiver_list = cstr(arg['receiver_list'])
- sl.message = arg['message']
- sl.no_of_requested_sms = len(arg['receiver_list'])
- sl.no_of_sent_sms = sent_sms
+ sl.message = args['message']
+ sl.no_of_requested_sms = len(args['receiver_list'])
+ sl.requested_numbers = "\n".join(args['receiver_list'])
+ sl.no_of_sent_sms = len(sent_to)
+ sl.sent_to = "\n".join(sent_to)
sl.save()
diff --git a/erpnext/setup/page/setup_wizard/install_fixtures.py b/erpnext/setup/page/setup_wizard/install_fixtures.py
index 629c06f..6265e4a 100644
--- a/erpnext/setup/page/setup_wizard/install_fixtures.py
+++ b/erpnext/setup/page/setup_wizard/install_fixtures.py
@@ -183,4 +183,4 @@
parent_link_field = ("parent_" + scrub(doc.doctype))
if doc.meta.get_field(parent_link_field) and not doc.get(parent_link_field):
doc.flags.ignore_mandatory = True
- doc.insert()
+ doc.insert(ignore_permissions=True)
diff --git a/erpnext/setup/page/setup_wizard/sample_data.py b/erpnext/setup/page/setup_wizard/sample_data.py
new file mode 100644
index 0000000..f7fb73b
--- /dev/null
+++ b/erpnext/setup/page/setup_wizard/sample_data.py
@@ -0,0 +1,117 @@
+# 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.make_random import add_random_children, get_random
+import frappe.utils
+
+def make_sample_data():
+ """Create a few opportunities, quotes, material requests, issues, todos, projects
+ to help the user get started"""
+
+ selling_items = frappe.get_all("Item", filters = {"is_sales_item": "Yes"})
+ buying_items = frappe.get_all("Item", filters = {"is_sales_item": "No"})
+
+ if selling_items:
+ for i in range(3):
+ make_opportunity(selling_items)
+ make_quote(selling_items)
+
+ make_projects()
+
+ if buying_items:
+ make_material_request(buying_items)
+
+ frappe.db.commit()
+
+def make_opportunity(selling_items):
+ b = frappe.get_doc({
+ "doctype": "Opportunity",
+ "enquiry_from": "Customer",
+ "customer": get_random("Customer"),
+ "enquiry_type": "Sales",
+ "with_items": 1
+ })
+
+ add_random_children(b, "items", rows=len(selling_items), randomize = {
+ "qty": (1, 5),
+ "item_code": ("Item", {"is_sales_item": "Yes"})
+ }, unique="item_code")
+
+ b.insert(ignore_permissions=True)
+
+ b.add_comment("This is a dummy record")
+
+def make_quote(selling_items):
+ qtn = frappe.get_doc({
+ "doctype": "Quotation",
+ "quotation_to": "Customer",
+ "customer": get_random("Customer"),
+ "order_type": "Sales"
+ })
+
+ add_random_children(qtn, "items", rows=len(selling_items), randomize = {
+ "qty": (1, 5),
+ "item_code": ("Item", {"is_sales_item": "Yes"})
+ }, unique="item_code")
+
+ qtn.insert(ignore_permissions=True)
+
+ qtn.add_comment("This is a dummy record")
+
+def make_material_request(buying_items):
+ for i in buying_items:
+ mr = frappe.get_doc({
+ "doctype": "Material Request",
+ "material_request_type": "Purchase",
+ "items": [{
+ "schedule_date": frappe.utils.add_days(frappe.utils.nowdate(), 7),
+ "item_code": i.name,
+ "qty": 10
+ }]
+ })
+ mr.insert()
+ mr.submit()
+
+ mr.add_comment("This is a dummy record")
+
+
+def make_issue():
+ pass
+
+def make_projects():
+ project = frappe.get_doc({
+ "doctype": "Project",
+ "project_name": "ERPNext Implementation",
+ })
+ current_date = frappe.utils.nowdate()
+ project.set("tasks", [
+ {
+ "title": "Explore ERPNext",
+ "start_date": frappe.utils.add_days(current_date, 1),
+ "end_date": frappe.utils.add_days(current_date, 2)
+ },
+ {
+ "title": "Run Sales Cycle",
+ "start_date": frappe.utils.add_days(current_date, 2),
+ "end_date": frappe.utils.add_days(current_date, 3)
+ },
+ {
+ "title": "Run Billing Cycle",
+ "start_date": frappe.utils.add_days(current_date, 3),
+ "end_date": frappe.utils.add_days(current_date, 4)
+ },
+ {
+ "title": "Run Purchase Cycle",
+ "start_date": frappe.utils.add_days(current_date, 4),
+ "end_date": frappe.utils.add_days(current_date, 5)
+ },
+ {
+ "title": "Go Live!",
+ "start_date": frappe.utils.add_days(current_date, 5),
+ "end_date": frappe.utils.add_days(current_date, 6)
+ }])
+
+ project.insert(ignore_permissions=True)
diff --git a/erpnext/setup/page/setup_wizard/setup_wizard.js b/erpnext/setup/page/setup_wizard/setup_wizard.js
index d521ada..b38bd1c 100644
--- a/erpnext/setup/page/setup_wizard/setup_wizard.js
+++ b/erpnext/setup/page/setup_wizard/setup_wizard.js
@@ -25,6 +25,7 @@
erpnext.wiz.user.slide,
erpnext.wiz.org.slide,
erpnext.wiz.branding.slide,
+ erpnext.wiz.users.slide,
erpnext.wiz.taxes.slide,
erpnext.wiz.customers.slide,
erpnext.wiz.suppliers.slide,
@@ -137,7 +138,7 @@
});
this.form.make();
} else {
- $(this.body).html(this.html)
+ $(this.body).html(this.html);
}
if(this.id > 0) {
@@ -412,11 +413,30 @@
onload: function(slide) {
erpnext.wiz.org.load_chart_of_accounts(slide);
erpnext.wiz.org.bind_events(slide);
+ erpnext.wiz.org.set_fy_dates(slide);
},
css_class: "single-column"
},
+ set_fy_dates: function(slide) {
+ var country = slide.wiz.get_values().country;
+
+ if(country) {
+ var fy = erpnext.wiz.fiscal_years[country];
+ var current_year = moment(new Date()).year();
+ var next_year = current_year + 1;
+ if(!fy) {
+ fy = ["01-01", "12-31"];
+ next_year = current_year;
+ }
+
+ slide.get_field("fy_start_date").set_input(current_year + "-" + fy[0]);
+ slide.get_field("fy_end_date").set_input(next_year + "-" + fy[1]);
+ }
+
+ },
+
load_chart_of_accounts: function(slide) {
var country = slide.wiz.get_values().country;
@@ -486,11 +506,41 @@
},
},
+ users: {
+ slide: {
+ icon: "icon-money",
+ "title": __("Add Users"),
+ "help": __("Add users to your organization"),
+ "fields": [],
+ before_load: function(slide) {
+ slide.fields = [];
+ for(var i=1; i<5; i++) {
+ slide.fields = slide.fields.concat([
+ {fieldtype:"Section Break"},
+ {fieldtype:"Data", fieldname:"user_fullname_"+ i,
+ label:__("Full Name")},
+ {fieldtype:"Data", fieldname:"user_email_" + i,
+ label:__("Email ID"), placeholder:__("user@example.com"),
+ options: "Email"},
+ {fieldtype:"Column Break"},
+ {fieldtype: "Check", fieldname: "user_sales_" + i,
+ label:__("Sales"), default: 1},
+ {fieldtype: "Check", fieldname: "user_purchaser_" + i,
+ label:__("Purchaser"), default: 1},
+ {fieldtype: "Check", fieldname: "user_accountant_" + i,
+ label:__("Accountant"), default: 1},
+ ]);
+ }
+ },
+ css_class: "two-column"
+ },
+ },
+
taxes: {
slide: {
icon: "icon-money",
"title": __("Add Taxes"),
- "help": __("List your tax heads (e.g. VAT, Excise; they should have unique names) and their standard rates. This will create a standard template, which you can edit and add more later."),
+ "help": __("List your tax heads (e.g. VAT, Customs etc; they should have unique names) and their standard rates. This will create a standard template, which you can edit and add more later."),
"fields": [],
before_load: function(slide) {
slide.fields = [];
@@ -526,6 +576,7 @@
label:__("Contact Name") + " " + i, placeholder:__("Contact Name")}
])
}
+ slide.fields[1].reqd = 1;
},
css_class: "two-column"
},
@@ -549,6 +600,7 @@
label:__("Contact Name") + " " + i, placeholder:__("Contact Name")},
])
}
+ slide.fields[1].reqd = 1;
},
css_class: "two-column"
},
@@ -578,9 +630,11 @@
{fieldtype: "Check", fieldname: "is_sales_item_" + i, label:__("We sell this Item"), default: 1},
{fieldtype: "Check", fieldname: "is_purchase_item_" + i, label:__("We buy this Item")},
{fieldtype:"Column Break"},
+ {fieldtype:"Currency", fieldname:"item_price_" + i, label:__("Rate")},
{fieldtype:"Attach Image", fieldname:"item_img_" + i, label:__("Attach Image")},
])
}
+ slide.fields[1].reqd = 1;
},
css_class: "two-column"
},
@@ -627,3 +681,25 @@
},
});
+// Source: https://en.wikipedia.org/wiki/Fiscal_year
+// default 1st Jan - 31st Dec
+
+erpnext.wiz.fiscal_years = {
+ "Afghanistan": ["12-20", "12-21"],
+ "Australia": ["07-01", "06-30"],
+ "Bangladesh": ["07-01", "06-30"],
+ "Canada": ["04-01", "03-31"],
+ "Costa Rica": ["10-01", "09-30"],
+ "Egypt": ["07-01", "06-30"],
+ "Hong Kong": ["04-01", "03-31"],
+ "India": ["04-01", "03-31"],
+ "Iran": ["06-23", "06-22"],
+ "Italy": ["07-01", "06-30"],
+ "Myanmar": ["04-01", "03-31"],
+ "New Zealand": ["04-01", "03-31"],
+ "Pakistan": ["07-01", "06-30"],
+ "Singapore": ["04-01", "03-31"],
+ "South Africa": ["03-01", "02-28"],
+ "Thailand": ["10-01", "09-30"],
+ "United Kingdom": ["04-01", "03-31"],
+}
diff --git a/erpnext/setup/page/setup_wizard/setup_wizard.py b/erpnext/setup/page/setup_wizard/setup_wizard.py
index 4bb01d4..9ddd2dc 100644
--- a/erpnext/setup/page/setup_wizard/setup_wizard.py
+++ b/erpnext/setup/page/setup_wizard/setup_wizard.py
@@ -2,7 +2,7 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
-import frappe, json
+import frappe, json, copy
from frappe.utils import cstr, flt, getdate
from frappe import _
@@ -13,6 +13,7 @@
from frappe.utils.nestedset import get_root_of
from .default_website import website_maker
import install_fixtures
+from .sample_data import make_sample_data
@frappe.whitelist()
def setup_account(args=None):
@@ -38,6 +39,9 @@
create_fiscal_year_and_company(args)
frappe.local.message_log = []
+ create_users(args)
+ frappe.local.message_log = []
+
set_defaults(args)
frappe.local.message_log = []
@@ -81,6 +85,7 @@
frappe.clear_cache()
+ make_sample_data()
except:
if args:
traceback = frappe.get_traceback()
@@ -297,21 +302,45 @@
tax_group = frappe.db.get_value("Account", {"company": args.get("company_name"),
"is_group": 1, "account_type": "Tax", "root_type": "Liability"})
if tax_group:
- frappe.get_doc({
- "doctype":"Account",
- "company": args.get("company_name").strip(),
- "parent_account": tax_group,
- "account_name": args.get("tax_" + str(i)),
- "is_group": 0,
- "report_type": "Balance Sheet",
- "account_type": "Tax",
- "tax_rate": flt(tax_rate) if tax_rate else None
- }).insert()
+ account = make_tax_head(args, i, tax_group, tax_rate)
+ make_sales_and_purchase_tax_templates(account)
+
except frappe.NameError, e:
if e.args[2][0]==1062:
pass
else:
raise
+def make_tax_head(args, i, tax_group, tax_rate):
+ return frappe.get_doc({
+ "doctype":"Account",
+ "company": args.get("company_name").strip(),
+ "parent_account": tax_group,
+ "account_name": args.get("tax_" + str(i)),
+ "is_group": 0,
+ "report_type": "Balance Sheet",
+ "account_type": "Tax",
+ "tax_rate": flt(tax_rate) if tax_rate else None
+ }).insert(ignore_permissions=True)
+
+def make_sales_and_purchase_tax_templates(account):
+ doc = {
+ "doctype": "Sales Taxes and Charges Template",
+ "title": account.name,
+ "taxes": [{
+ "category": "Valuation and Total",
+ "charge_type": "On Net Total",
+ "account_head": account.name,
+ "description": "{0} @ {1}".format(account.account_name, account.tax_rate),
+ "rate": account.tax_rate
+ }]
+ }
+
+ # Sales
+ frappe.get_doc(copy.deepcopy(doc)).insert()
+
+ # Purchase
+ doc["doctype"] = "Purchase Taxes and Charges Template"
+ frappe.get_doc(copy.deepcopy(doc)).insert()
def create_items(args):
for i in xrange(1,6):
@@ -349,9 +378,30 @@
filename, filetype, content = item_image
fileurl = save_file(filename, content, "Item", item, decode=True).file_url
frappe.db.set_value("Item", item, "image", fileurl)
+
+ if args.get("item_price_" + str(i)):
+ item_price = flt(args.get("item_price_" + str(i)))
+
+ if is_sales_item:
+ price_list_name = frappe.db.get_value("Price List", {"selling": 1})
+ make_item_price(item, price_list_name, item_price)
+
+ if is_purchase_item:
+ price_list_name = frappe.db.get_value("Price List", {"buying": 1})
+ make_item_price(item, price_list_name, item_price)
+
except frappe.NameError:
pass
+def make_item_price(item, price_list_name, item_price):
+ frappe.get_doc({
+ "doctype": "Item Price",
+ "price_list": price_list_name,
+ "item_code": item,
+ "price_list_rate": item_price
+ }).insert()
+
+
def create_customers(args):
for i in xrange(1,6):
customer = args.get("customer_" + str(i))
@@ -367,13 +417,8 @@
}).insert()
if args.get("customer_contact_" + str(i)):
- contact = args.get("customer_contact_" + str(i)).split(" ")
- frappe.get_doc({
- "doctype":"Contact",
- "customer": customer,
- "first_name":contact[0],
- "last_name": len(contact) > 1 and contact[1] or ""
- }).insert()
+ create_contact(args.get("customer_contact_" + str(i)),
+ "customer", customer)
except frappe.NameError:
pass
@@ -390,16 +435,21 @@
}).insert()
if args.get("supplier_contact_" + str(i)):
- contact = args.get("supplier_contact_" + str(i)).split(" ")
- frappe.get_doc({
- "doctype":"Contact",
- "supplier": supplier,
- "first_name":contact[0],
- "last_name": len(contact) > 1 and contact[1] or ""
- }).insert()
+ create_contact(args.get("supplier_contact_" + str(i)),
+ "supplier", supplier)
except frappe.NameError:
pass
+def create_contact(contact, party_type, party):
+ """Create contact based on given contact name"""
+ contact = contact.strip().split(" ")
+
+ frappe.get_doc({
+ "doctype":"Contact",
+ party_type: party,
+ "first_name":contact[0],
+ "last_name": len(contact) > 1 and contact[1] or ""
+ }).insert()
def create_letter_head(args):
if args.get("attach_letterhead"):
@@ -451,6 +501,60 @@
if args.get("email") and hasattr(frappe.local, "login_manager"):
frappe.local.login_manager.login_as(args.get("email"))
+def create_users(args):
+ # create employee for self
+ emp = frappe.get_doc({
+ "doctype": "Employee",
+ "full_name": " ".join(filter(None, [args.get("first_name"), args.get("last_name")])),
+ "user_id": frappe.session.user,
+ "status": "Active",
+ "company": args.get("company_name")
+ })
+ emp.flags.ignore_mandatory = True
+ emp.insert(ignore_permissions = True)
+
+ for i in xrange(1,5):
+ email = args.get("user_email_" + str(i))
+ fullname = args.get("user_fullname_" + str(i))
+ if email:
+ if not fullname:
+ fullname = email.split("@")[0]
+
+ parts = fullname.split(" ", 1)
+
+ user = frappe.get_doc({
+ "doctype": "User",
+ "email": email,
+ "first_name": parts[0],
+ "last_name": parts[1] if len(parts) > 1 else "",
+ "enabled": 1,
+ "user_type": "System User"
+ })
+
+ # default roles
+ user.append_roles("Projects User", "Stock User", "Support Team")
+
+ if args.get("user_sales_" + str(i)):
+ user.append_roles("Sales User", "Sales Manager", "Accounts User")
+ if args.get("user_purchaser_" + str(i)):
+ user.append_roles("Purchase User", "Purchase Manager", "Accounts User")
+ if args.get("user_accountant_" + str(i)):
+ user.append_roles("Accounts Manager", "Accounts User")
+
+ user.flags.delay_emails = True
+ user.insert(ignore_permissions=True)
+
+ # create employee
+ emp = frappe.get_doc({
+ "doctype": "Employee",
+ "full_name": fullname,
+ "user_id": user.name,
+ "status": "Active",
+ "company": args.get("company_name")
+ })
+ emp.flags.ignore_mandatory = True
+ emp.insert(ignore_permissions = True)
+
@frappe.whitelist()
def load_messages(language):
frappe.clear_cache()
diff --git a/erpnext/setup/page/setup_wizard/test_setup_data.py b/erpnext/setup/page/setup_wizard/test_setup_data.py
index 43fc2cf..de54a1d 100644
--- a/erpnext/setup/page/setup_wizard/test_setup_data.py
+++ b/erpnext/setup/page/setup_wizard/test_setup_data.py
@@ -51,4 +51,15 @@
"timezone": "America/New_York",
"password": "password",
"email": "test@erpnext.com",
+"user_email_1": "testsetup1@example.com",
+"user_fullname_1": "test setup user",
+"user_sales_1": 1,
+"user_purchaser_1": 1,
+"user_accountant_1": 1,
+"user_email_1": "testsetup2@example.com",
+"user_fullname_1": "test setup user",
+"user_sales_2": 1,
+"user_purchaser_2": 0,
+"user_accountant_2": 0
+
}
diff --git a/erpnext/startup/notifications.py b/erpnext/startup/notifications.py
index 4190f2d..d065370 100644
--- a/erpnext/startup/notifications.py
+++ b/erpnext/startup/notifications.py
@@ -10,6 +10,7 @@
"Issue": {"status": "Open"},
"Warranty Claim": {"status": "Open"},
"Task": {"status": "Open"},
+ "Project": {"status": "Open"},
"Lead": {"status": "Open"},
"Contact": {"status": "Open"},
"Opportunity": {"status": "Open"},
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 58b1adb..3bd5657 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -86,8 +86,12 @@
},
manage_variants: function(frm) {
- frappe.route_options = {"item_code": frm.doc.name };
- frappe.set_route("List", "Manage Variants");
+ if (cur_frm.doc.__unsaved==1) {
+ frappe.throw(__("You have unsaved changes. Please save."))
+ } else {
+ frappe.route_options = {"item_code": frm.doc.name };
+ frappe.set_route("List", "Manage Variants");
+ }
}
});
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index a2e0ade..d3d8e9c 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -325,7 +325,8 @@
for d in variants:
update_variant(self.name, d)
updated.append(d.item_code)
- frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated)))
+ if updated:
+ frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated)))
def validate_has_variants(self):
if not self.has_variants and frappe.db.get_value("Item", self.name, "has_variants"):
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index 0651ae8..c2c6d1a 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -9,8 +9,13 @@
columns = get_columns()
sl_entries = get_stock_ledger_entries(filters)
item_details = get_item_details(filters)
-
+ opening_row = get_opening_balance(filters, columns)
+
data = []
+
+ if opening_row:
+ data.append(opening_row)
+
for sle in sl_entries:
item_detail = item_details[sle.item_code]
@@ -20,7 +25,7 @@
(sle.incoming_rate if sle.actual_qty > 0 else 0.0),
sle.valuation_rate, sle.stock_value, sle.voucher_type, sle.voucher_no,
sle.batch_no, sle.serial_no, sle.company])
-
+
return columns, data
def get_columns():
@@ -40,7 +45,7 @@
where company = %(company)s and
posting_date between %(from_date)s and %(to_date)s
{sle_conditions}
- order by posting_date desc, posting_time desc, name desc"""\
+ order by posting_date asc, posting_time asc, name asc"""\
.format(sle_conditions=get_sle_conditions(filters)), filters, as_dict=1)
def get_item_details(filters):
@@ -73,3 +78,22 @@
conditions.append("voucher_no=%(voucher_no)s")
return "and {}".format(" and ".join(conditions)) if conditions else ""
+
+def get_opening_balance(filters, columns):
+ if not (filters.item_code and filters.warehouse and filters.from_date):
+ return
+
+ from erpnext.stock.stock_ledger import get_previous_sle
+ last_entry = get_previous_sle({
+ "item_code": filters.item_code,
+ "warehouse": filters.warehouse,
+ "posting_date": filters.from_date,
+ "posting_time": "00:00:00"
+ })
+
+ row = [""]*len(columns)
+ row[1] = _("'Opening'")
+ for i, v in ((9, 'qty_after_transaction'), (11, 'valuation_rate'), (12, 'stock_value')):
+ row[i] = last_entry.get(v, 0)
+
+ return row
\ No newline at end of file
diff --git a/erpnext/utilities/doctype/sms_log/sms_log.json b/erpnext/utilities/doctype/sms_log/sms_log.json
index e3c7741..ba88c62 100644
--- a/erpnext/utilities/doctype/sms_log/sms_log.json
+++ b/erpnext/utilities/doctype/sms_log/sms_log.json
@@ -1,32 +1,58 @@
{
"autoname": "SMSLOG/.########",
- "creation": "2012-03-27 14:36:47.000000",
+ "creation": "2012-03-27 14:36:47",
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
- "fieldname": "column_break0",
- "fieldtype": "Column Break",
- "permlevel": 0,
- "width": "50%"
- },
- {
"fieldname": "sender_name",
"fieldtype": "Data",
"label": "Sender Name",
- "permlevel": 0
+ "permlevel": 0,
+ "read_only": 1
},
{
"fieldname": "sent_on",
"fieldtype": "Date",
"label": "Sent On",
- "permlevel": 0
+ "permlevel": 0,
+ "read_only": 1
},
{
- "fieldname": "receiver_list",
+ "fieldname": "column_break0",
+ "fieldtype": "Column Break",
+ "permlevel": 0,
+ "read_only": 0,
+ "width": "50%"
+ },
+ {
+ "fieldname": "message",
"fieldtype": "Small Text",
- "label": "Receiver List",
- "permlevel": 0
+ "label": "Message",
+ "permlevel": 0,
+ "read_only": 1
+ },
+ {
+ "fieldname": "sec_break1",
+ "fieldtype": "Section Break",
+ "options": "Simple",
+ "permlevel": 0,
+ "precision": "",
+ "read_only": 0
+ },
+ {
+ "fieldname": "no_of_requested_sms",
+ "fieldtype": "Int",
+ "label": "No of Requested SMS",
+ "permlevel": 0,
+ "read_only": 1
+ },
+ {
+ "fieldname": "requested_numbers",
+ "fieldtype": "Small Text",
+ "label": "Requested Numbers",
+ "permlevel": 0,
+ "read_only": 1
},
{
"fieldname": "column_break1",
@@ -35,27 +61,24 @@
"width": "50%"
},
{
- "fieldname": "no_of_requested_sms",
- "fieldtype": "Int",
- "label": "No of Requested SMS",
- "permlevel": 0
- },
- {
"fieldname": "no_of_sent_sms",
"fieldtype": "Int",
"label": "No of Sent SMS",
- "permlevel": 0
+ "permlevel": 0,
+ "read_only": 1
},
{
- "fieldname": "message",
+ "fieldname": "sent_to",
"fieldtype": "Small Text",
- "label": "Message",
- "permlevel": 0
+ "label": "Sent To",
+ "permlevel": 0,
+ "precision": "",
+ "read_only": 1
}
],
"icon": "icon-mobile-phone",
"idx": 1,
- "modified": "2013-12-20 19:24:35.000000",
+ "modified": "2015-07-22 11:53:25.998578",
"modified_by": "Administrator",
"module": "Utilities",
"name": "SMS Log",