diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 73c7f4b3008..529723b390c 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '11.1.47' +__version__ = '11.1.48' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py index 20ce7ca9a4c..3e08c2812ef 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.py +++ b/erpnext/accounts/doctype/bank_account/bank_account.py @@ -48,7 +48,10 @@ class BankAccount(Document): # Encode characters as numbers encoded = [encode_char(c) if ord(c) >= 65 and ord(c) <= 90 else c for c in flipped] - to_check = int(''.join(encoded)) + try: + to_check = int(''.join(encoded)) + except ValueError: + frappe.throw(_('IBAN is not valid')) if to_check % 97 != 1: frappe.throw(_('IBAN is not valid')) diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index 3e013f5d6b3..e2f99d6ea3f 100755 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -451,6 +451,10 @@ def make_customer_and_address(customers): def add_customer(data): + customer = data.get('full_name') or data.get('customer') + if frappe.db.exists("Customer", customer.strip()): + return customer.strip() + customer_doc = frappe.new_doc('Customer') customer_doc.customer_name = data.get('full_name') or data.get('customer') customer_doc.customer_pos_id = data.get('customer_pos_id') diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 124b6d34a36..99196b29847 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -397,14 +397,18 @@ class SalesInvoice(SellingController): if pos.get('account_for_change_amount'): self.account_for_change_amount = pos.get('account_for_change_amount') - for fieldname in ('territory', 'naming_series', 'currency', 'taxes_and_charges', 'letter_head', 'tc_name', - 'company', 'select_print_heading', 'cash_bank_account', 'company_address', - 'write_off_account', 'write_off_cost_center', 'apply_discount_on', 'cost_center'): + for fieldname in ('territory', 'naming_series', 'currency', 'letter_head', 'tc_name', + 'company', 'select_print_heading', 'cash_bank_account', 'write_off_account', + 'write_off_cost_center', 'apply_discount_on', 'cost_center'): if (not for_validate) or (for_validate and not self.get(fieldname)): self.set(fieldname, pos.get(fieldname)) customer_price_list = frappe.get_value("Customer", self.customer, 'default_price_list') + for field in ['taxes_and_charges', 'company_address']: + if pos.get(field): + self.set(field, pos.get(fieldname)) + if not customer_price_list: self.set('selling_price_list', pos.get('selling_price_list')) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 0b4fe6dd4e8..aad9621e75f 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -717,7 +717,6 @@ def get_children(doctype, parent, company, is_root=False): parent_fieldname = 'parent_' + doctype.lower().replace(' ', '_') fields = [ 'name as value', - 'root_type', 'is_group as expandable' ] filters = [['docstatus', '<', 2]] @@ -725,11 +724,11 @@ def get_children(doctype, parent, company, is_root=False): filters.append(['ifnull(`{0}`,"")'.format(parent_fieldname), '=', '' if is_root else parent]) if is_root: - fields += ['report_type', 'account_currency'] if doctype == 'Account' else [] + fields += ['root_type', 'report_type', 'account_currency'] if doctype == 'Account' else [] filters.append(['company', '=', company]) else: - fields += ['account_currency'] if doctype == 'Account' else [] + fields += ['root_type', 'account_currency'] if doctype == 'Account' else [] fields += [parent_fieldname + ' as parent'] acc = frappe.get_list(doctype, fields=fields, filters=filters) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b98fbf42cdd..ebf51104bf2 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -825,7 +825,7 @@ class AccountsController(TransactionBase): if self.doctype in ("Sales Invoice", "Purchase Invoice"): grand_total = grand_total - flt(self.write_off_amount) - if total != grand_total: + if total != flt(grand_total, self.precision("grand_total")): frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")) def is_rounded_total_disabled(self): diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index e018dd23961..a1972aa16de 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -329,7 +329,7 @@ class calculate_taxes_and_totals(object): self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"]) - self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate) \ + self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate, self.doc.precision("base_grand_total")) \ if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted) \ else self.doc.base_net_total diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index f5ee366d2f4..114363ba36e 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -8,14 +8,8 @@ erpnext.LeadController = frappe.ui.form.Controller.extend({ setup: function () { this.frm.make_methods = { - 'Quotation': () => erpnext.utils.create_new_doc('Quotation', { - 'quotation_to': this.frm.doc.doctype, - 'party_name': this.frm.doc.name - }), - 'Opportunity': () => erpnext.utils.create_new_doc('Opportunity', { - 'opportunity_from': this.frm.doc.doctype, - 'party_name': this.frm.doc.name - }) + 'Quotation': this.make_quotation, + 'Opportunity': this.create_opportunity } this.frm.fields_dict.customer.get_query = function (doc, cdt, cdn) { diff --git a/erpnext/public/js/sms_manager.js b/erpnext/public/js/sms_manager.js index 6ce8bb11500..ea469e73d83 100644 --- a/erpnext/public/js/sms_manager.js +++ b/erpnext/public/js/sms_manager.js @@ -20,7 +20,7 @@ erpnext.SMSManager = function SMSManager(doc) { 'Purchase Receipt' : 'Items has been received against purchase receipt: ' + doc.name } - if (in_list(['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice'], doc.doctype)) + if (in_list(['Sales Order', 'Delivery Note', 'Sales Invoice'], doc.doctype)) this.show(doc.contact_person, 'Customer', doc.customer, '', default_msg[doc.doctype]); else if (in_list(['Purchase Order', 'Purchase Receipt'], doc.doctype)) this.show(doc.contact_person, 'Supplier', doc.supplier, '', default_msg[doc.doctype]); @@ -28,6 +28,8 @@ erpnext.SMSManager = function SMSManager(doc) { this.show('', '', '', doc.mobile_no, default_msg[doc.doctype]); else if (doc.doctype == 'Opportunity') this.show('', '', '', doc.contact_no, default_msg[doc.doctype]); + else if (doc.doctype == 'Quotation') + this.show(doc.contact_person, doc.quotation_to, doc.party_name, '', default_msg[doc.doctype]); else if (doc.doctype == 'Material Request') this.show('', '', '', '', default_msg[doc.doctype]); diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index e1ddd2c7dac..d902a15494d 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -503,10 +503,12 @@ def make_material_request(source_name, target_doc=None): doc.material_request_type = "Purchase" def update_item(source, target, source_parent): + # qty is for packed items, because packed items don't have stock_qty field + qty = source.get("stock_qty") or source.get("qty") target.project = source_parent.project - target.qty = source.stock_qty - requested_item_qty.get(source.name, 0) + target.qty = qty - requested_item_qty.get(source.name, 0) target.conversion_factor = 1 - target.stock_qty = source.stock_qty - requested_item_qty.get(source.name, 0) + target.stock_qty = qty - requested_item_qty.get(source.name, 0) doc = get_mapped_doc("Sales Order", source_name, { "Sales Order": { diff --git a/erpnext/selling/report/customer_wise_item_price/__init__.py b/erpnext/selling/report/customer_wise_item_price/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.js b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.js new file mode 100644 index 00000000000..d333c8be65e --- /dev/null +++ b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.js @@ -0,0 +1,27 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Customer-wise Item Price"] = { + "filters": [ + { + "label": __("Customer"), + "fieldname": "customer", + "fieldtype": "Link", + "options": "Customer", + "reqd": 1 + }, + { + "label": __("Item"), + "fieldname": "item", + "fieldtype": "Link", + "options": "Item", + "get_query": () => { + return { + query: "erpnext.controllers.queries.item_query", + filters: { 'is_sales_item': 1 } + } + } + } + ] +} diff --git a/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.json b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.json new file mode 100644 index 00000000000..998ba94a8ba --- /dev/null +++ b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.json @@ -0,0 +1,43 @@ +{ + "add_total_row": 0, + "creation": "2019-06-12 03:25:36.263179", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "letter_head": "Delta9", + "modified": "2019-06-12 03:25:36.263179", + "modified_by": "Administrator", + "module": "Selling", + "name": "Customer-wise Item Price", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Customer", + "report_name": "Customer-wise Item Price", + "report_type": "Script Report", + "roles": [ + { + "role": "Sales User" + }, + { + "role": "Stock Manager" + }, + { + "role": "Accounts User" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Sales Manager" + }, + { + "role": "Sales Master Manager" + }, + { + "role": "Stock User" + } + ] +} \ No newline at end of file diff --git a/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py new file mode 100644 index 00000000000..eb9273a5626 --- /dev/null +++ b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py @@ -0,0 +1,103 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals + +import frappe +from erpnext import get_default_company +from erpnext.accounts.party import get_party_details +from erpnext.stock.get_item_details import get_price_list_rate_for +from frappe import _ + + +def execute(filters=None): + if not filters: + filters = {} + + if not filters.get("customer"): + frappe.throw(_("Please select a Customer")) + + columns = get_columns(filters) + data = get_data(filters) + + return columns, data + + +def get_columns(filters=None): + return [ + { + "label": _("Item Code"), + "fieldname": "item_code", + "fieldtype": "Link", + "options": "Item", + "width": 150 + }, + { + "label": _("Item Name"), + "fieldname": "item_name", + "fieldtype": "Data", + "width": 200 + }, + { + "label": _("Selling Rate"), + "fieldname": "selling_rate", + "fieldtype": "Currency" + }, + { + "label": _("Available Stock"), + "fieldname": "available_stock", + "fieldtype": "Float", + "width": 150 + }, + { + "label": _("Price List"), + "fieldname": "price_list", + "fieldtype": "Link", + "options": "Price List", + "width": 120 + } + ] + + +def get_data(filters=None): + data = [] + customer_details = get_customer_details(filters) + + items = get_selling_items(filters) + item_stock_map = frappe.get_all("Bin", fields=["item_code", "sum(actual_qty) AS available"], group_by="item_code") + item_stock_map = {item.item_code: item.available for item in item_stock_map} + + for item in items: + price_list_rate = get_price_list_rate_for(customer_details, item.item_code) or 0.0 + available_stock = item_stock_map.get(item.item_code) + + data.append({ + "item_code": item.item_code, + "item_name": item.item_name, + "selling_rate": price_list_rate, + "price_list": customer_details.get("price_list"), + "available_stock": available_stock, + }) + + return data + + +def get_customer_details(filters): + customer_details = get_party_details(party=filters.get("customer"), party_type="Customer") + customer_details.update({ + "company": get_default_company(), + "price_list": customer_details.get("selling_price_list") + }) + + return customer_details + + +def get_selling_items(filters): + if filters.get("item"): + item_filters = {"item_code": filters.get("item"), "is_sales_item": 1, "disabled": 0} + else: + item_filters = {"is_sales_item": 1, "disabled": 0} + + items = frappe.get_all("Item", filters=item_filters, fields=["item_code", "item_name"], order_by="item_name") + + return items diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 3150d2ba87f..5bd381ba200 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -177,7 +177,8 @@ class StockEntry(StockController): item.set(f, item_details.get(f)) if not item.transfer_qty and item.qty: - item.transfer_qty = item.qty * item.conversion_factor + item.transfer_qty = ( flt(item.qty, item.precision("qty")) + * flt(item.conversion_factor, item.precision("conversion_factor")) ) if (self.purpose in ("Material Transfer", "Material Transfer for Manufacture") and not item.serial_no