diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 514e5b3df5..ce16a6e7db 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -4,7 +4,7 @@ import inspect import frappe from erpnext.hooks import regional_overrides -__version__ = '9.0.0' +__version__ = '9.0.3' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index 8860b092e0..6fae8f7d7a 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -1023,7 +1023,7 @@ "length": 0, "no_copy": 0, "permlevel": 0, - "precision": "2", + "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, @@ -1284,7 +1284,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-08-31 16:34:41.614743", + "modified": "2017-09-27 08:31:38.432574", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index 41b794c176..613f3847b9 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -699,7 +699,7 @@ "length": 0, "no_copy": 0, "permlevel": 0, - "precision": "2", + "precision": "", "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 0, @@ -2166,7 +2166,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-07-17 17:54:48.246507", + "modified": "2017-09-27 08:31:37.827893", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.py b/erpnext/accounts/doctype/tax_rule/tax_rule.py index 2d91a3c85f..9c091e8c9d 100644 --- a/erpnext/accounts/doctype/tax_rule/tax_rule.py +++ b/erpnext/accounts/doctype/tax_rule/tax_rule.py @@ -8,6 +8,7 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import cstr, cint from frappe.contacts.doctype.address.address import get_default_address +from frappe.utils.nestedset import get_root_of from erpnext.setup.doctype.customer_group.customer_group import get_parent_customer_groups class IncorrectCustomerGroup(frappe.ValidationError): pass @@ -136,7 +137,7 @@ def get_tax_template(posting_date, args): if key=="use_for_shopping_cart": conditions.append("use_for_shopping_cart = {0}".format(1 if value else 0)) if key == 'customer_group': - if not value: value = _("All Customer Groups") + if not value: value = get_root_of("Customer Group") customer_group_condition = get_customer_group_condition(value) conditions.append("ifnull({0}, '') in ('', {1})".format(key, customer_group_condition)) else: diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js index ad6ea7d7f8..0dc0ee6b7c 100644 --- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js +++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js @@ -2,29 +2,29 @@ // For license information, please see license.txt frappe.query_reports["Quoted Item Comparison"] = { - "filters": [ + filters: [ { - "fieldname": "supplier_quotation", - "label": __("Supplier Quotation"), - "fieldtype": "Link", - "options": "Supplier Quotation", - "default": "", - "get_query": function () { + fieldtype: "Link", + label: __("Supplier Quotation"), + options: "Supplier Quotation", + fieldname: "supplier_quotation", + default: "", + get_query: () => { return { filters: { "docstatus": ["<", 2] } } } }, { - "fieldname": "item", - "label": __("Item"), - "fieldtype": "Link", - "options": "Item", - "default": "", - "reqd": 1, - "get_query": function () { - var quote = frappe.query_report_filters_by_name.supplier_quotation.get_value(); + reqd: 1, + default: "", + options: "Item", + label: __("Item"), + fieldname: "item", + fieldtype: "Link", + get_query: () => { + let quote = frappe.query_report_filters_by_name.supplier_quotation.get_value(); if (quote != "") { return { - query: "erpnext.buying.doctype.quality_inspection.quality_inspection.item_query", + query: "erpnext.stock.doctype.quality_inspection.quality_inspection.item_query", filters: { "from": "Supplier Quotation Item", "parent": quote @@ -39,47 +39,50 @@ frappe.query_reports["Quoted Item Comparison"] = { } } ], - onload: function (report) { + onload: (report) => { // Create a button for setting the default supplier - report.page.add_inner_button(__("Select Default Supplier"), function () { - - var reporter = frappe.query_reports["Quoted Item Comparison"]; + report.page.add_inner_button(__("Select Default Supplier"), () => { + let reporter = frappe.query_reports["Quoted Item Comparison"]; //Always make a new one so that the latest values get updated reporter.make_default_supplier_dialog(report); - report.dialog.show(); - setTimeout(function () { report.dialog.input.focus(); }, 1000); - }, 'Tools'); }, - "make_default_supplier_dialog": function (report) { + make_default_supplier_dialog: (report) => { // Get the name of the item to change - var filters = report.get_values(); - var item_code = filters.item; + if(!report.data) return; + + let filters = report.get_values(); + let item_code = filters.item; // Get a list of the suppliers (with a blank as well) for the user to select - var select_options = ""; - for (let supplier of report.data) { - select_options += supplier.supplier_name + '\n' - } + let suppliers = $.map(report.data, (row, idx)=>{ return row.supplier_name }) // Create a dialog window for the user to pick their supplier - var d = new frappe.ui.Dialog({ + let dialog = new frappe.ui.Dialog({ title: __('Select Default Supplier'), fields: [ - { fieldname: 'supplier', fieldtype: 'Select', label: 'Supplier', reqd: 1, options: select_options }, - { fieldname: 'ok_button', fieldtype: 'Button', label: 'Set Default Supplier' }, + { + reqd: 1, + label: 'Supplier', + fieldtype: 'Link', + options: 'Supplier', + fieldname: 'supplier', + get_query: () => { + return { + filters: { + 'name': ['in', suppliers] + } + } + } + } ] }); - // On the user clicking the ok button - d.fields_dict.ok_button.input.onclick = function () { - var btn = d.fields_dict.ok_button.input; - var v = report.dialog.get_values(); - if (v) { - $(btn).set_working(); - + dialog.set_primary_action("Set Default Supplier", () => { + let values = dialog.get_values(); + if(values) { // Set the default_supplier field of the appropriate Item to the selected supplier frappe.call({ method: "frappe.client.set_value", @@ -87,17 +90,17 @@ frappe.query_reports["Quoted Item Comparison"] = { doctype: "Item", name: item_code, fieldname: "default_supplier", - value: v.supplier, + value: values.supplier, }, - callback: function (r) { - $(btn).done_working(); + freeze: true, + callback: (r) => { frappe.msgprint("Successfully Set Supplier"); - report.dialog.hide(); + dialog.hide(); } }); } - } - report.dialog = d; + }); + dialog.show(); } } diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py index 44e247e881..c399f3eee2 100644 --- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py +++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py @@ -8,53 +8,55 @@ import frappe def execute(filters=None): qty_list = get_quantity_list(filters.item) - data = get_quote_list(filters.item, qty_list) - columns = get_columns(qty_list) - return columns, data def get_quote_list(item, qty_list): out = [] - if item: - price_data = [] - suppliers = [] - company_currency = frappe.db.get_default("currency") - float_precision = cint(frappe.db.get_default("float_precision")) or 2 - # Get the list of suppliers - for root in frappe.db.sql("""select parent, qty, rate from `tabSupplier Quotation Item` where item_code=%s and docstatus < 2""", item, as_dict=1): - for splr in frappe.db.sql("""SELECT supplier from `tabSupplier Quotation` where name =%s and docstatus < 2""", root.parent, as_dict=1): - ip = frappe._dict({ + if not item: + return [] + + suppliers = [] + price_data = [] + company_currency = frappe.db.get_default("currency") + float_precision = cint(frappe.db.get_default("float_precision")) or 2 + # Get the list of suppliers + for root in frappe.db.sql("""select parent, qty, rate from `tabSupplier Quotation Item` + where item_code=%s and docstatus < 2""", item, as_dict=1): + for splr in frappe.db.sql("""select supplier from `tabSupplier Quotation` + where name =%s and docstatus < 2""", root.parent, as_dict=1): + ip = frappe._dict({ "supplier": splr.supplier, "qty": root.qty, "parent": root.parent, - "rate": root.rate}) - price_data.append(ip) - suppliers.append(splr.supplier) - - #Add a row for each supplier - for root in set(suppliers): - supplier_currency = frappe.db.get_value("Supplier", root, "default_currency") - if supplier_currency: - exchange_rate = get_exchange_rate(supplier_currency, company_currency) - else: - exchange_rate = 1 - - row = frappe._dict({ - "supplier_name": root + "rate": root.rate }) - for col in qty_list: - # Get the quantity for this row - for item_price in price_data: - if str(item_price.qty) == col.key and item_price.supplier == root: - row[col.key] = flt(item_price.rate * exchange_rate, float_precision) - row[col.key + "QUOTE"] = item_price.parent - break - else: - row[col.key] = "" - row[col.key + "QUOTE"] = "" - out.append(row) + price_data.append(ip) + suppliers.append(splr.supplier) + + #Add a row for each supplier + for root in set(suppliers): + supplier_currency = frappe.db.get_value("Supplier", root, "default_currency") + if supplier_currency: + exchange_rate = get_exchange_rate(supplier_currency, company_currency) + else: + exchange_rate = 1 + + row = frappe._dict({ + "supplier_name": root + }) + for col in qty_list: + # Get the quantity for this row + for item_price in price_data: + if str(item_price.qty) == col.key and item_price.supplier == root: + row[col.key] = flt(item_price.rate * exchange_rate, float_precision) + row[col.key + "QUOTE"] = item_price.parent + break + else: + row[col.key] = "" + row[col.key + "QUOTE"] = "" + out.append(row) return out @@ -62,7 +64,8 @@ def get_quantity_list(item): out = [] if item: - qty_list = frappe.db.sql("""select distinct qty from `tabSupplier Quotation Item` where ifnull(item_code,'')=%s and docstatus < 2""", item, as_dict=1) + qty_list = frappe.db.sql("""select distinct qty from `tabSupplier Quotation Item` + where ifnull(item_code,'')=%s and docstatus < 2""", item, as_dict=1) qty_list.sort(reverse=False) for qt in qty_list: col = frappe._dict({ @@ -98,4 +101,4 @@ def get_columns(qty_list): "width": 90 }) - return columns + return columns \ No newline at end of file diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index ff11eb258d..5b5cd9e690 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -174,7 +174,8 @@ def copy_attributes_to_variant(item, variant): # copy non no-copy fields - exclude_fields = ["item_code", "item_name", "show_in_website"] + exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website", + "show_variant_in_website", "opening_stock", "variant_of", "valuation_rate", "variant_based_on"] if item.variant_based_on=='Manufacturer': # don't copy manufacturer values if based on part no @@ -186,6 +187,7 @@ def copy_attributes_to_variant(item, variant): if (field.reqd or field.fieldname in allow_fields) and field.fieldname not in exclude_fields: if variant.get(field.fieldname) != item.get(field.fieldname): variant.set(field.fieldname, item.get(field.fieldname)) + variant.variant_of = item.name variant.has_variants = 0 if not variant.description: diff --git a/erpnext/demo/data/item.json b/erpnext/demo/data/item.json index fe12ce892a..6974b943f6 100644 --- a/erpnext/demo/data/item.json +++ b/erpnext/demo/data/item.json @@ -278,5 +278,16 @@ "item_code": "Autocad", "item_name": "Autocad", "item_group": "All Item Groups" + }, + { + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "valuation_rate": 200, + "default_warehouse": "Stores", + "description": "Corrugated Box", + "item_code": "Corrugated Box", + "item_name": "Corrugated Box", + "item_group": "All Item Groups" } ] \ No newline at end of file diff --git a/erpnext/demo/setup/setup_data.py b/erpnext/demo/setup/setup_data.py index c4df777c88..34e9a3e572 100644 --- a/erpnext/demo/setup/setup_data.py +++ b/erpnext/demo/setup/setup_data.py @@ -16,6 +16,7 @@ def setup(domain): setup_user() setup_employee() setup_user_roles() + setup_role_permissions() employees = frappe.get_all('Employee', fields=['name', 'date_of_joining']) @@ -91,7 +92,8 @@ def setup_fiscal_year(): pass # set the last fiscal year (current year) as default - fiscal_year.set_as_default() + if fiscal_year: + fiscal_year.set_as_default() def setup_holiday_list(): """Setup Holiday List for the current year""" @@ -374,6 +376,22 @@ def setup_pos_profile(): pos.insert() +def setup_role_permissions(): + role_permissions = {'Batch': ['Accounts User', 'Item Manager']} + for doctype, roles in role_permissions.items(): + for role in roles: + if not frappe.db.get_value('Custom DocPerm', + {'parent': doctype, 'role': role}): + frappe.get_doc({ + 'doctype': 'Custom DocPerm', + 'role': role, + 'read': 1, + 'write': 1, + 'create': 1, + 'delete': 1, + 'parent': doctype + }).insert(ignore_permissions=True) + def import_json(doctype, submit=False, values=None): frappe.flags.in_import = True data = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', diff --git a/erpnext/demo/user/stock.py b/erpnext/demo/user/stock.py index 43668fe369..f5ec4f9b58 100644 --- a/erpnext/demo/user/stock.py +++ b/erpnext/demo/user/stock.py @@ -7,6 +7,7 @@ import frappe, random from frappe.desk import query_report from erpnext.stock.stock_ledger import NegativeStockError from erpnext.stock.doctype.serial_no.serial_no import SerialNoRequiredError, SerialNoQtyError +from erpnext.stock.doctype.batch.batch import UnableToSelectBatchError from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return @@ -59,7 +60,7 @@ def make_delivery_note(): try: dn.submit() frappe.db.commit() - except (NegativeStockError, SerialNoRequiredError, SerialNoQtyError): + except (NegativeStockError, SerialNoRequiredError, SerialNoQtyError, UnableToSelectBatchError): frappe.db.rollback() def make_stock_reconciliation(): diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 8306290f72..48ed7415d7 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -27,7 +27,6 @@ doctype_js = { # setup wizard setup_wizard_requires = "assets/erpnext/js/setup_wizard.js" setup_wizard_complete = "erpnext.setup.setup_wizard.setup_wizard.setup_complete" -setup_wizard_success = "erpnext.setup.setup_wizard.setup_wizard.setup_success" setup_wizard_test = "erpnext.setup.setup_wizard.test_setup_wizard.run_setup_wizard_test" before_install = "erpnext.setup.install.check_setup_wizard_not_completed" @@ -81,7 +80,7 @@ website_route_rules = [ {"from_route": "/supplier-quotations/", "to_route": "order", "defaults": { "doctype": "Supplier Quotation", - "parents": [{"label": _("Supplier Quotation"), "route": "quotations"}] + "parents": [{"label": _("Supplier Quotation"), "route": "supplier-quotations"}] } }, {"from_route": "/quotations", "to_route": "Quotation"}, diff --git a/erpnext/manufacturing/doctype/production_order/production_order.py b/erpnext/manufacturing/doctype/production_order/production_order.py index 11475705db..31aedb38f0 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.py +++ b/erpnext/manufacturing/doctype/production_order/production_order.py @@ -51,9 +51,9 @@ class ProductionOrder(Document): def validate_sales_order(self): if self.sales_order: so = frappe.db.sql(""" - select so.name, so_item.delivery_date, so.project + select so.name, so_item.delivery_date, so.project from `tabSales Order` so, `tabSales Order Item` so_item - where so.name=%s and so.name=so_item.parent + where so.name=%s and so.name=so_item.parent and so.docstatus = 1 and so_item.item_code=%s """, (self.sales_order, self.production_item), as_dict=1) @@ -112,7 +112,7 @@ class ProductionOrder(Document): allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings", "over_production_allowance_percentage")) - + if total_qty > so_qty + (allowance_percentage/100 * so_qty): frappe.throw(_("Cannot produce more Item {0} than Sales Order quantity {1}") .format(self.production_item, so_qty), OverProductionError) @@ -217,27 +217,27 @@ class ProductionOrder(Document): def set_production_order_operations(self): """Fetch operations from BOM and set in 'Production Order'""" self.set('operations', []) - + if not self.bom_no \ or cint(frappe.db.get_single_value("Manufacturing Settings", "disable_capacity_planning")): return - + if self.use_multi_level_bom: bom_list = frappe.get_doc("BOM", self.bom_no).traverse_tree() else: bom_list = [self.bom_no] - + operations = frappe.db.sql(""" - select + select operation, description, workstation, idx, - base_hour_rate as hour_rate, time_in_mins, + base_hour_rate as hour_rate, time_in_mins, "Pending" as status, parent as bom from `tabBOM Operation` where parent in (%s) order by idx """ % ", ".join(["%s"]*len(bom_list)), tuple(bom_list), as_dict=1) - + self.set('operations', operations) self.calculate_time() @@ -277,7 +277,7 @@ class ProductionOrder(Document): timesheet.set('time_logs', []) for i, d in enumerate(self.operations): - + if d.status != 'Completed': self.set_start_end_time_for_workstation(d, i) @@ -370,8 +370,13 @@ class ProductionOrder(Document): self.actual_start_date = None self.actual_end_date = None if self.get("operations"): - self.actual_start_date = min([d.actual_start_time for d in self.get("operations")]) - self.actual_end_date = max([d.actual_end_time for d in self.get("operations")]) + actual_start_dates = [d.actual_start_time for d in self.get("operations") if d.actual_start_time] + if actual_start_dates: + self.actual_start_date = min(actual_start_dates) + + actual_end_dates = [d.actual_end_time for d in self.get("operations") if d.actual_end_time] + if actual_end_dates: + self.actual_end_date = max(actual_end_dates) def delete_timesheet(self): for timesheet in frappe.get_all("Timesheet", ["name"], {"production_order": self.name}): @@ -411,18 +416,18 @@ class ProductionOrder(Document): if d.source_warehouse: stock_bin = get_bin(d.item_code, d.source_warehouse) stock_bin.update_reserved_qty_for_production() - + def get_items_and_operations_from_bom(self): self.set_required_items() self.set_production_order_operations() - + return check_if_scrap_warehouse_mandatory(self.bom_no) - + def set_available_qty(self): for d in self.get("required_items"): if d.source_warehouse: d.available_qty_at_source_warehouse = get_latest_stock_qty(d.item_code, d.source_warehouse) - + if self.wip_warehouse: d.available_qty_at_wip_warehouse = get_latest_stock_qty(d.item_code, self.wip_warehouse) @@ -439,7 +444,7 @@ class ProductionOrder(Document): 'required_qty': item.qty, 'source_warehouse': item.source_warehouse or item.default_warehouse }) - + self.set_available_qty() def update_transaferred_qty_for_required_items(self): @@ -463,12 +468,12 @@ class ProductionOrder(Document): def get_item_details(item, project = None): res = frappe.db.sql(""" select stock_uom, description - from `tabItem` - where disabled=0 + from `tabItem` + where disabled=0 and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %s) and name=%s """, (nowdate(), item), as_dict=1) - + if not res: return {} @@ -611,14 +616,14 @@ def make_new_timesheet(source_name, target_doc=None): @frappe.whitelist() def stop_unstop(production_order, status): """ Called from client side on Stop/Unstop event""" - + if not frappe.has_permission("Production Order", "write"): frappe.throw(_("Not permitted"), frappe.PermissionError) - + pro_order = frappe.get_doc("Production Order", production_order) pro_order.update_status(status) pro_order.update_planned_qty() frappe.msgprint(_("Production Order has been {0}").format(status)) pro_order.notify_update() - + return pro_order.status \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 4e8ec2dc03..34e18dccb0 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -408,6 +408,8 @@ erpnext.patches.v8_0.update_stock_qty_value_in_bom_item erpnext.patches.v8_0.update_sales_cost_in_project erpnext.patches.v8_0.save_system_settings erpnext.patches.v8_1.delete_deprecated_reports +erpnext.patches.v9_0.remove_subscription_module +erpnext.patches.v8_7.make_subscription_from_recurring_data erpnext.patches.v8_1.setup_gst_india #2017-06-27 execute:frappe.reload_doc('regional', 'doctype', 'gst_hsn_code') erpnext.patches.v8_1.removed_roles_from_gst_report_non_indian_account @@ -432,21 +434,20 @@ erpnext.patches.v8_5.update_customer_group_in_POS_profile erpnext.patches.v8_6.update_timesheet_company_from_PO erpnext.patches.v8_6.set_write_permission_for_quotation_for_sales_manager erpnext.patches.v8_5.remove_project_type_property_setter -erpnext.patches.v8_7.add_more_gst_fields +erpnext.patches.v8_7.add_more_gst_fields #21-09-2017 erpnext.patches.v8_7.fix_purchase_receipt_status erpnext.patches.v8_6.rename_bom_update_tool erpnext.patches.v8_7.set_offline_in_pos_settings #11-09-17 erpnext.patches.v8_9.add_setup_progress_actions #08-09-2017 #26-09-2017 erpnext.patches.v8_9.rename_company_sales_target_field erpnext.patches.v8_8.set_bom_rate_as_per_uom -erpnext.patches.v8_7.make_subscription_from_recurring_data erpnext.patches.v8_8.add_new_fields_in_accounts_settings -erpnext.patches.v9_0.remove_subscription_module erpnext.patches.v8_9.set_print_zero_amount_taxes erpnext.patches.v8_9.set_default_customer_group erpnext.patches.v8_9.remove_employee_from_salary_structure_parent erpnext.patches.v8_9.delete_gst_doctypes_for_outside_india_accounts erpnext.patches.v8_9.set_default_fields_in_variant_settings +erpnext.patches.v8_9.update_billing_gstin_for_indian_account erpnext.patches.v8_10.add_due_date_to_gle erpnext.patches.v8_10.update_gl_due_date_for_pi_and_si erpnext.patches.v8_10.add_payment_terms_field_to_supplier diff --git a/erpnext/patches/v8_7/make_subscription_from_recurring_data.py b/erpnext/patches/v8_7/make_subscription_from_recurring_data.py index ab0fc121fc..c5d7d7279a 100644 --- a/erpnext/patches/v8_7/make_subscription_from_recurring_data.py +++ b/erpnext/patches/v8_7/make_subscription_from_recurring_data.py @@ -8,9 +8,15 @@ from frappe.utils import today def execute(): frappe.reload_doc('accounts', 'doctype', 'subscription') frappe.reload_doc('selling', 'doctype', 'sales_order') + frappe.reload_doc('selling', 'doctype', 'quotation') frappe.reload_doc('buying', 'doctype', 'purchase_order') + frappe.reload_doc('buying', 'doctype', 'supplier_quotation') frappe.reload_doc('accounts', 'doctype', 'sales_invoice') frappe.reload_doc('accounts', 'doctype', 'purchase_invoice') + frappe.reload_doc('stock', 'doctype', 'purchase_receipt') + frappe.reload_doc('stock', 'doctype', 'delivery_note') + frappe.reload_doc('accounts', 'doctype', 'journal_entry') + frappe.reload_doc('accounts', 'doctype', 'payment_entry') for doctype in ['Sales Order', 'Sales Invoice', 'Purchase Invoice', 'Purchase Invoice']: diff --git a/erpnext/patches/v8_9/update_billing_gstin_for_indian_account.py b/erpnext/patches/v8_9/update_billing_gstin_for_indian_account.py new file mode 100644 index 0000000000..24e20409c1 --- /dev/null +++ b/erpnext/patches/v8_9/update_billing_gstin_for_indian_account.py @@ -0,0 +1,15 @@ +# Copyright (c) 2017, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + + if company: + for doctype in ['Sales Invoice', 'Delivery Note']: + frappe.db.sql(""" update `tab{0}` + set billing_address_gstin = (select gstin from `tabAddress` + where name = customer_address) + where customer_address is not null and customer_address != ''""".format(doctype)) \ No newline at end of file diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 2cb93f02d1..9f4c2b9c35 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -53,12 +53,17 @@ class Project(Document): return frappe.get_all("Task", "*", {"project": self.name}, order_by="exp_start_date asc") def validate(self): + self.validate_project_name() self.validate_dates() self.validate_weights() self.sync_tasks() self.tasks = [] self.send_welcome_email() + def validate_project_name(self): + if frappe.db.exists("Project", self.project_name): + frappe.throw(_("Project {0} already exists").format(self.project_name)) + def validate_dates(self): if self.expected_start_date and self.expected_end_date: if getdate(self.expected_end_date) < getdate(self.expected_start_date): diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 12ab732676..84624b8997 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -288,8 +288,11 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ }, set_cumulative_total: function(row_idx, tax) { - var tax_amount = (in_list(["Valuation and Total", "Total"], tax.category) ? - tax.tax_amount_after_discount_amount : 0); + var tax_amount = tax.tax_amount_after_discount_amount; + if (tax.category == 'Valuation') { + tax_amount = 0; + } + if (tax.add_deduct_tax == "Deduct") { tax_amount = -1*tax_amount; } if(row_idx==0) { diff --git a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json index 7b3a8d6b72..2a2145c7f7 100644 --- a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json +++ b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json @@ -84,34 +84,13 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-08-31 14:38:52.220743", - "modified_by": "ewdszx@ed.ews", + "modified": "2017-09-29 14:38:52.220743", + "modified_by": "Administrator", "module": "Regional", "name": "GST HSN Code", "name_case": "", "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], + "permissions": [], "quick_entry": 1, "read_only": 0, "read_only_onload": 0, diff --git a/erpnext/regional/doctype/gst_settings/gst_settings.json b/erpnext/regional/doctype/gst_settings/gst_settings.json index 04065e29df..67084b45f8 100644 --- a/erpnext/regional/doctype/gst_settings/gst_settings.json +++ b/erpnext/regional/doctype/gst_settings/gst_settings.json @@ -83,34 +83,13 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2017-08-31 14:39:15.625952", - "modified_by": "ewdszx@ed.ews", + "modified": "2017-09-29 14:39:15.625952", + "modified_by": "Administrator", "module": "Regional", "name": "GST Settings", "name_case": "", "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], + "permissions": [], "quick_entry": 1, "read_only": 0, "read_only_onload": 0, diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 106a3d5740..68a52cc5ee 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -12,7 +12,7 @@ def setup(company=None, patch=True): make_custom_fields() add_permissions() add_custom_roles_for_reports() - add_hsn_sac_codes() + frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test) add_print_formats() if not patch: update_address_template() @@ -47,12 +47,14 @@ def add_hsn_sac_codes(): def create_hsn_codes(data, code_field): for d in data: - if not frappe.db.exists("GST HSN Code", d[code_field]): - hsn_code = frappe.new_doc('GST HSN Code') - hsn_code.description = d["description"] - hsn_code.hsn_code = d[code_field] - hsn_code.name = d[code_field] + hsn_code = frappe.new_doc('GST HSN Code') + hsn_code.description = d["description"] + hsn_code.hsn_code = d[code_field] + hsn_code.name = d[code_field] + try: hsn_code.db_insert() + except frappe.DuplicateEntryError: + pass def add_custom_roles_for_reports(): for report_name in ('GST Sales Register', 'GST Purchase Register', @@ -70,7 +72,6 @@ def add_custom_roles_for_reports(): def add_permissions(): for doctype in ('GST HSN Code', 'GST Settings'): - add_permission(doctype, 'Accounts Manager', 0) add_permission(doctype, 'All', 0) def add_print_formats(): @@ -111,12 +112,15 @@ def make_custom_fields(): ] sales_invoice_gst_fields = [ + dict(fieldname='billing_address_gstin', label='Billing Address GSTIN', + fieldtype='Data', insert_after='customer_address', + options='customer_address.gstin', print_hide=1), dict(fieldname='customer_gstin', label='Customer GSTIN', fieldtype='Data', insert_after='shipping_address', options='shipping_address_name.gstin', print_hide=1), dict(fieldname='place_of_supply', label='Place of Supply', fieldtype='Data', insert_after='customer_gstin', print_hide=1, - options='shipping_address_name.gst_state_number', read_only=1), + options='shipping_address_name.gst_state_number', read_only=0), dict(fieldname='company_gstin', label='Company GSTIN', fieldtype='Data', insert_after='company_address', options='company_address.gstin', print_hide=1) diff --git a/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py b/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py index 40bbae8b55..4e57a52665 100644 --- a/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py +++ b/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py @@ -8,6 +8,7 @@ from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register i def execute(filters=None): return _execute(filters, additional_table_columns=[ dict(fieldtype='Data', label='Customer GSTIN', width=120), + dict(fieldtype='Data', label='Billing Address GSTIN', width=140), dict(fieldtype='Data', label='Company GSTIN', width=120), dict(fieldtype='Data', label='Place of Supply', width=120), dict(fieldtype='Data', label='Reverse Charge', width=120), @@ -17,6 +18,7 @@ def execute(filters=None): dict(fieldtype='Data', label='HSN Code', width=120) ], additional_query_columns=[ 'customer_gstin', + 'billing_address_gstin', 'company_gstin', 'place_of_supply', 'reverse_charge', diff --git a/erpnext/regional/report/gst_sales_register/gst_sales_register.py b/erpnext/regional/report/gst_sales_register/gst_sales_register.py index 7f6f809276..e79d722b9f 100644 --- a/erpnext/regional/report/gst_sales_register/gst_sales_register.py +++ b/erpnext/regional/report/gst_sales_register/gst_sales_register.py @@ -8,6 +8,7 @@ from erpnext.accounts.report.sales_register.sales_register import _execute def execute(filters=None): return _execute(filters, additional_table_columns=[ dict(fieldtype='Data', label='Customer GSTIN', width=120), + dict(fieldtype='Data', label='Billing Address GSTIN', width=140), dict(fieldtype='Data', label='Company GSTIN', width=120), dict(fieldtype='Data', label='Place of Supply', width=120), dict(fieldtype='Data', label='Reverse Charge', width=120), @@ -16,6 +17,7 @@ def execute(filters=None): dict(fieldtype='Data', label='E-Commerce GSTIN', width=130) ], additional_query_columns=[ 'customer_gstin', + 'billing_address_gstin', 'company_gstin', 'place_of_supply', 'reverse_charge', diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json index 794494d8ee..3fc56fa2ce 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.json +++ b/erpnext/selling/doctype/quotation_item/quotation_item.json @@ -684,7 +684,7 @@ "length": 0, "no_copy": 0, "permlevel": 0, - "precision": "2", + "precision": "", "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 0, @@ -1583,7 +1583,7 @@ "istable": 1, "max_attachments": 0, "menu_index": 0, - "modified": "2017-05-10 17:14:45.736424", + "modified": "2017-09-27 08:31:37.485134", "modified_by": "Administrator", "module": "Selling", "name": "Quotation Item", diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index 47bc1b6077..fd7b0f29b3 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -714,7 +714,7 @@ "length": 0, "no_copy": 0, "permlevel": 0, - "precision": "2", + "precision": "", "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 0, @@ -745,7 +745,7 @@ "length": 0, "no_copy": 0, "permlevel": 0, - "precision": "2", + "precision": "", "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 1, @@ -1963,7 +1963,7 @@ "istable": 1, "max_attachments": 0, "menu_index": 0, - "modified": "2017-07-28 14:04:04.289428", + "modified": "2017-09-27 08:31:37.129537", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js index ec1a292621..1b67ff2b72 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.js +++ b/erpnext/selling/page/point_of_sale/point_of_sale.js @@ -21,7 +21,9 @@ frappe.pages['point-of-sale'].on_page_load = function(wrapper) { }; frappe.pages['point-of-sale'].refresh = function(wrapper) { - cur_frm = wrapper.pos.frm; + if (wrapper.pos) { + cur_frm = wrapper.pos.frm; + } } erpnext.pos.PointOfSale = class PointOfSale { @@ -742,7 +744,7 @@ class POSCart { this.wrapper.find('.discount_amount').on('change', (e) => { frappe.model.set_value(this.frm.doctype, this.frm.docname, - 'discount_amount', e.target.value); + 'discount_amount', flt(e.target.value)); this.frm.trigger('discount_amount') .then(() => { let discount_wrapper = this.wrapper.find('.additional_discount_percentage'); diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index 8ed288b6e9..d74f1f06e3 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -3,12 +3,15 @@ from __future__ import unicode_literals import frappe, json +from frappe.utils.nestedset import get_root_of @frappe.whitelist() def get_items(start, page_length, price_list, item_group, search_value=""): serial_no = "" batch_no = "" item_code = search_value + if not frappe.db.exists('Item Group', item_group): + item_group = get_root_of('Item Group') if search_value: # search serial no @@ -31,7 +34,7 @@ def get_items(start, page_length, price_list, item_group, search_value=""): ON (item_det.item_code=i.name or item_det.item_code=i.variant_of) where - i.disabled = 0 and i.has_variants = 0 + i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1 and i.item_group in (select name from `tabItem Group` where lft >= {lft} and rgt <= {rgt}) and (i.item_code like %(item_code)s or i.item_name like %(item_code)s or i.barcode like %(item_code)s) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 0459e84b25..4884c06343 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -75,11 +75,7 @@ class Company(Document): if not frappe.local.flags.ignore_chart_of_accounts: self.create_default_accounts() self.create_default_warehouses() - - if cint(frappe.db.get_single_value('System Settings', 'setup_complete')): - # In the case of setup, fixtures should be installed after setup_success - # This also prevents db commits before setup is successful - install_country_fixtures(self.name) + install_country_fixtures(self.name) if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": self.name}): self.create_default_cost_center() diff --git a/erpnext/setup/setup_wizard/setup_wizard.py b/erpnext/setup/setup_wizard/setup_wizard.py index bf9221784c..a80399d905 100644 --- a/erpnext/setup/setup_wizard/setup_wizard.py +++ b/erpnext/setup/setup_wizard/setup_wizard.py @@ -66,10 +66,6 @@ def setup_complete(args=None): pass -def setup_success(args=None): - company = frappe.db.sql("select name from tabCompany", as_dict=True)[0]["name"] - install_country_fixtures(company) - def create_fiscal_year_and_company(args): if (args.get('fy_start_date')): curr_fiscal_year = get_fy_details(args.get('fy_start_date'), args.get('fy_end_date')) diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index f003ce4b1c..49c439ba5c 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -120,8 +120,9 @@ def enable_all_roles_and_domains(): _role.save() # add all roles to users - user = frappe.get_doc("User", "Administrator") - user.add_roles(*[role.get("name") for role in roles]) + if roles: + user = frappe.get_doc("User", "Administrator") + user.add_roles(*[role.get("name") for role in roles]) domains = frappe.get_list("Domain") if not domains: diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 809c7ead4f..043dc73d37 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -103,14 +103,15 @@ def split_batch(batch_no, item_code, warehouse, qty, new_batch_id = None): def set_batch_nos(doc, warehouse_field, throw = False): '''Automatically select `batch_no` for outgoing items in item table''' for d in doc.items: + qty = d.get('stock_qty') or d.get('qty') or 0 has_batch_no = frappe.db.get_value('Item', d.item_code, 'has_batch_no') warehouse = d.get(warehouse_field, None) - if has_batch_no and warehouse and d.qty > 0: + if has_batch_no and warehouse and qty > 0: if not d.batch_no: - d.batch_no = get_batch_no(d.item_code, warehouse, d.qty, throw) + d.batch_no = get_batch_no(d.item_code, warehouse, qty, throw) else: batch_qty = get_batch_qty(batch_no=d.batch_no, warehouse=warehouse) - if flt(batch_qty) < flt(d.qty): + if flt(batch_qty) < flt(qty): frappe.throw(_("Row #{0}: The batch {1} has only {2} qty. Please select another batch which has {3} qty available or split the row into multiple rows, to deliver/issue from multiple batches").format(d.idx, d.batch_no, batch_qty, d.qty)) def get_batch_no(item_code, warehouse, qty, throw=False): diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index a441a7f51d..4e1ea4059c 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -713,7 +713,7 @@ "length": 0, "no_copy": 0, "permlevel": 0, - "precision": "2", + "precision": "", "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 0, @@ -1956,7 +1956,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-05-10 17:14:50.456930", + "modified": "2017-09-27 08:31:38.768846", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/stock/doctype/item_price/item_price.json b/erpnext/stock/doctype/item_price/item_price.json index 0bd00093ac..fee229f761 100644 --- a/erpnext/stock/doctype/item_price/item_price.json +++ b/erpnext/stock/doctype/item_price/item_price.json @@ -1,5 +1,5 @@ { - "allow_copy": 0, + "allow_copy": 0, "allow_import": 1, "allow_rename": 0, "autoname": "ITEM-PRICE-.#####", @@ -165,7 +165,7 @@ "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, - "in_filter": 0, + "in_filter": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "", @@ -357,19 +357,19 @@ "set_only_once": 0, "unique": 0 } - ], + ], "hide_heading": 0, "hide_toolbar": 0, "icon": "fa fa-flag", "idx": 1, "image_view": 0, "in_create": 0, - "in_dialog": 0, - "is_submittable": 0, + "in_dialog": 0, + "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-02-20 13:27:23.896148", + "modified": "2017-09-28 03:56:20.814993", "modified_by": "Administrator", "module": "Stock", "name": "Item Price", @@ -422,6 +422,6 @@ "show_name_in_global_search": 0, "sort_order": "ASC", "title_field": "item_code", - "track_changes": 0, + "track_changes": 1, "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js index f3404cc78b..df78572dcb 100644 --- a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js +++ b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js @@ -4,8 +4,8 @@ frappe.ui.form.on('Item Variant Settings', { setup: function(frm) { const allow_fields = []; - const exclude_fields = ["item_code", "item_name", "show_in_website", "show_variant_in_website", - "opening_stock", "variant_of", "valuation_rate", "variant_based_on"]; + const exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website", + "show_variant_in_website", "opening_stock", "variant_of", "valuation_rate", "variant_based_on"]; frappe.model.with_doctype('Item', () => { frappe.get_meta('Item').fields.forEach(d => { diff --git a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py index 80462d1ab8..0c6acd4290 100644 --- a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py +++ b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py @@ -10,8 +10,8 @@ class ItemVariantSettings(Document): def set_default_fields(self): self.fields = [] fields = frappe.get_meta('Item').fields - exclude_fields = ["item_code", "item_name", "show_in_website", "show_variant_in_website", - "standard_rate", "opening_stock", "image", "description", + exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website", + "show_variant_in_website", "standard_rate", "opening_stock", "image", "description", "variant_of", "valuation_rate", "description", "variant_based_on", "website_image", "thumbnail", "website_specifiations", "web_long_description"] diff --git a/erpnext/templates/includes/rfq.js b/erpnext/templates/includes/rfq.js index 3b45cbe506..b56c416dbd 100644 --- a/erpnext/templates/includes/rfq.js +++ b/erpnext/templates/includes/rfq.js @@ -85,7 +85,7 @@ rfq = Class.extend({ frappe.unfreeze(); if(r.message){ $('.btn-sm').hide() - window.location.href = "/quotations/" + encodeURIComponent(r.message); + window.location.href = "/supplier-quotations/" + encodeURIComponent(r.message); } } })