diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 683734b8799..f1d822a89c5 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -49,7 +49,7 @@ class Account(Document): 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 6946bcb1064..fb649e6d1b3 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.js +++ b/erpnext/accounts/doctype/cost_center/cost_center.js @@ -17,7 +17,7 @@ erpnext.accounts.CostCenterController = frappe.ui.form.Controller.extend({ 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 f26c80ba59f..0f51a00cd6b 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 @@ class CostCenter(NestedSet): 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 @@ class CostCenter(NestedSet): 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 @@ class CostCenter(NestedSet): 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 a2ba72dba60..bffa8e6d74a 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 6721bd89e17..b36287b6914 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 @@ from __future__ import unicode_literals 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 00000000000..bac293f20f1 --- /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 3a7ab181431..d7a6b2e2be3 100644 --- a/erpnext/config/crm.py +++ b/erpnext/config/crm.py @@ -42,6 +42,11 @@ def get_data(): "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 543396462df..62dfe2326db 100644 --- a/erpnext/config/selling.py +++ b/erpnext/config/selling.py @@ -48,6 +48,11 @@ def get_data(): "name": "SMS Center", "description":_("Send mass SMS to your contacts"), }, + { + "type": "doctype", + "name": "SMS Log", + "description":_("Logs for maintaining sms delivery status"), + }, { "type": "doctype", "name": "Newsletter", diff --git a/erpnext/contacts/__init__.py b/erpnext/contacts/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/contacts/doctype/__init__.py b/erpnext/contacts/doctype/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/contacts/doctype/party_type/__init__.py b/erpnext/contacts/doctype/party_type/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 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 19ffefba7c3..00000000000 --- 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 d21216f1611..00000000000 --- 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 26af40a4414..93ce5e16f64 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.py +++ b/erpnext/manufacturing/doctype/production_order/production_order.py @@ -335,12 +335,15 @@ def get_item_details(item): 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 86a14d8c213..271abac10e5 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 import msgprint, _ 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 @@ class ProductionPlanningTool(Document): 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 @@ class ProductionPlanningTool(Document): 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 67c856db7fb..dfca2f27c5b 100644 --- a/erpnext/modules.txt +++ b/erpnext/modules.txt @@ -9,6 +9,5 @@ Manufacturing Stock Support Utilities -Contacts Shopping Cart Hub Node diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 269dcbaf9c3..c7355073c41 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -177,3 +177,4 @@ erpnext.patches.v5_1.track_operations 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 e8a772a2163..d45fbba4867 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -272,6 +272,10 @@ def make_material_request(source_name, target_doc=None): 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 @@ def make_material_request(source_name, target_doc=None): "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 1403ee5cbdf..909986347f3 100644 --- a/erpnext/setup/doctype/sms_settings/sms_settings.py +++ b/erpnext/setup/doctype/sms_settings/sms_settings.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals 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 @@ def send_sms(receiver_list, msg, sender_name = ''): } 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 @@ def send_via_gateway(arg): 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 @@ def send_request(gateway_url, args): 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 @@ def scrub_gateway_url(url): # 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 629c06f6db3..6265e4a36ce 100644 --- a/erpnext/setup/page/setup_wizard/install_fixtures.py +++ b/erpnext/setup/page/setup_wizard/install_fixtures.py @@ -183,4 +183,4 @@ def install(country=None): 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 00000000000..f7fb73b4461 --- /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 d521adab258..b38bd1c3730 100644 --- a/erpnext/setup/page/setup_wizard/setup_wizard.js +++ b/erpnext/setup/page/setup_wizard/setup_wizard.js @@ -25,6 +25,7 @@ frappe.pages['setup-wizard'].on_page_load = function(wrapper) { 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 @@ erpnext.wiz.WizardSlide = Class.extend({ }); this.form.make(); } else { - $(this.body).html(this.html) + $(this.body).html(this.html); } if(this.id > 0) { @@ -412,11 +413,30 @@ $.extend(erpnext.wiz, { 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 @@ $.extend(erpnext.wiz, { }, }, + 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 @@ $.extend(erpnext.wiz, { label:__("Contact Name") + " " + i, placeholder:__("Contact Name")} ]) } + slide.fields[1].reqd = 1; }, css_class: "two-column" }, @@ -549,6 +600,7 @@ $.extend(erpnext.wiz, { label:__("Contact Name") + " " + i, placeholder:__("Contact Name")}, ]) } + slide.fields[1].reqd = 1; }, css_class: "two-column" }, @@ -578,9 +630,11 @@ $.extend(erpnext.wiz, { {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 @@ $.extend(erpnext.wiz, { }, }); +// 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 4bb01d48d4d..9ddd2dc03e3 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.geo.country_info import get_country_info 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 @@ def setup_account(args=None): 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 @@ def setup_account(args=None): frappe.clear_cache() + make_sample_data() except: if args: traceback = frappe.get_traceback() @@ -297,21 +302,45 @@ def create_taxes(args): 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 @@ def create_items(args): 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 @@ def create_customers(args): }).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 @@ def create_suppliers(args): }).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 @@ def login_as_first_user(args): 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 43fc2cf7824..de54a1d16b5 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 @@ args = { "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 4190f2debdb..d06537066e7 100644 --- a/erpnext/startup/notifications.py +++ b/erpnext/startup/notifications.py @@ -10,6 +10,7 @@ def get_notification_config(): "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 58b1adb8db7..3bd5657d597 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -86,8 +86,12 @@ frappe.ui.form.on("Item", { }, 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 a2e0ade50c9..d3d8e9c6a18 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -325,7 +325,8 @@ class Item(WebsiteGenerator): 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 0651ae89610..c2c6d1ae7f5 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -9,8 +9,13 @@ def execute(filters=None): 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 @@ def execute(filters=None): (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 @@ def get_stock_ledger_entries(filters): 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 @@ def get_sle_conditions(filters): 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 e3c77416763..ba88c622c3f 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", @@ -34,28 +60,25 @@ "permlevel": 0, "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",