diff --git a/erpnext/accounts/doctype/item_tax_template/test_records.json b/erpnext/accounts/doctype/item_tax_template/test_records.json index db540e86aac..4d9537d4b89 100644 --- a/erpnext/accounts/doctype/item_tax_template/test_records.json +++ b/erpnext/accounts/doctype/item_tax_template/test_records.json @@ -2,6 +2,7 @@ { "doctype": "Item Tax Template", "title": "_Test Account Excise Duty @ 10", + "company": "_Test Company", "taxes": [ { "doctype": "Item Tax Template Detail", @@ -14,6 +15,7 @@ { "doctype": "Item Tax Template", "title": "_Test Account Excise Duty @ 12", + "company": "_Test Company", "taxes": [ { "doctype": "Item Tax Template Detail", @@ -26,6 +28,7 @@ { "doctype": "Item Tax Template", "title": "_Test Account Excise Duty @ 15", + "company": "_Test Company", "taxes": [ { "doctype": "Item Tax Template Detail", @@ -38,6 +41,7 @@ { "doctype": "Item Tax Template", "title": "_Test Account Excise Duty @ 20", + "company": "_Test Company", "taxes": [ { "doctype": "Item Tax Template Detail", @@ -50,6 +54,7 @@ { "doctype": "Item Tax Template", "title": "_Test Item Tax Template 1", + "company": "_Test Company", "taxes": [ { "doctype": "Item Tax Template Detail", diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index e1f1b9b4dfc..dc27cfb3342 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -11,6 +11,7 @@ import json from six import string_types import frappe +from erpnext.accounts.doctype.pricing_rule.pricing_rule import set_transaction_type from erpnext.setup.doctype.item_group.item_group import get_child_item_groups from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses from erpnext.stock.get_item_details import get_conversion_factor, get_default_income_account @@ -418,9 +419,28 @@ def apply_pricing_rule_on_transaction(doc): values = {} conditions = get_other_conditions(conditions, values, doc) - pricing_rules = frappe.db.sql(""" Select `tabPricing Rule`.* from `tabPricing Rule` - where {conditions} and `tabPricing Rule`.disable = 0 - """.format(conditions = conditions), values, as_dict=1) + args = frappe._dict({ + 'doctype': doc.doctype, + 'transaction_type': None, + }) + set_transaction_type(args) + tran_type_condition = '{} = 1'.format(args.transaction_type) + + sql = """ + SELECT + `tabPricing Rule`.* + FROM + `tabPricing Rule` + WHERE + {conditions} and + {tran_type_condition} and + `tabPricing Rule`.disable = 0 + """.format( + conditions=conditions, + tran_type_condition=tran_type_condition, + ) + + pricing_rules = frappe.db.sql(sql, values, as_dict=1) if pricing_rules: pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty, diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index a8dfab6a54b..ff1dbedbd31 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -1,5 +1,4 @@ { - "actions": [], "autoname": "hash", "creation": "2013-05-22 12:43:10", "doctype": "DocType", @@ -86,6 +85,7 @@ "item_tax_rate", "bom", "include_exploded_items", + "purchase_invoice_item", "col_break6", "purchase_order", "po_detail", @@ -764,12 +764,21 @@ "label": "Asset Category", "options": "Asset Category", "read_only": 1 + }, + { + "depends_on": "eval:parent.update_stock == 1", + "fieldname": "purchase_invoice_item", + "fieldtype": "Data", + "ignore_user_permissions": 1, + "label": "Purchase Invoice Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, - "links": [], - "modified": "2020-04-07 18:34:35.104178", + "modified": "2020-06-30 16:48:01.398356", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", 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 b2294e4318f..49deebd3c6c 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -1,5 +1,4 @@ { - "actions": [], "autoname": "hash", "creation": "2013-06-04 11:02:19", "doctype": "DocType", @@ -87,6 +86,7 @@ "edit_references", "sales_order", "so_detail", + "sales_invoice_item", "column_break_74", "delivery_note", "dn_detail", @@ -783,12 +783,21 @@ "fieldtype": "Link", "label": "Finance Book", "options": "Finance Book" + }, + { + "depends_on": "eval:parent.update_stock == 1", + "fieldname": "sales_invoice_item", + "fieldtype": "Data", + "ignore_user_permissions": 1, + "label": "Sales Invoice Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, - "links": [], - "modified": "2019-12-04 12:22:38.517710", + "modified": "2020-06-30 16:47:37.650996", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 0973f165232..c7e6bcf4d65 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -182,7 +182,7 @@ class BuyingController(StockController): if item.item_code and item.qty and item.item_code in stock_and_asset_items: item_proportion = flt(item.base_net_amount) / stock_and_asset_items_amount if stock_and_asset_items_amount \ else flt(item.qty) / stock_and_asset_items_qty - + if i == (last_item_idx - 1): item.item_tax_amount = flt(valuation_amount_adjustment, self.precision("item_tax_amount", item)) @@ -540,9 +540,19 @@ class BuyingController(StockController): "serial_no": cstr(d.serial_no).strip() }) if self.is_return: - original_incoming_rate = frappe.db.get_value("Stock Ledger Entry", - {"voucher_type": "Purchase Receipt", "voucher_no": self.return_against, - "item_code": d.item_code}, "incoming_rate") + filters = { + "voucher_type": self.doctype, + "voucher_no": self.return_against, + "item_code": d.item_code + } + + if (self.doctype == "Purchase Invoice" and self.update_stock + and d.get("purchase_invoice_item")): + filters["voucher_detail_no"] = d.purchase_invoice_item + elif self.doctype == "Purchase Receipt" and d.get("purchase_receipt_item"): + filters["voucher_detail_no"] = d.purchase_receipt_item + + original_incoming_rate = frappe.db.get_value("Stock Ledger Entry", filters, "incoming_rate") sle.update({ "outgoing_rate": original_incoming_rate @@ -728,7 +738,7 @@ class BuyingController(StockController): if delete_asset and is_auto_create_enabled: # need to delete movements to delete assets otherwise throws link exists error movements = frappe.db.sql( - """SELECT asm.name + """SELECT asm.name FROM `tabAsset Movement` asm, `tabAsset Movement Item` asm_item WHERE asm_item.parent=asm.name and asm_item.asset=%s""", asset.name, as_dict=1) for movement in movements: diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 90c67f1e521..18b5daf128a 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -278,6 +278,8 @@ def make_return_doc(doctype, source_name, target_doc=None): target_doc.rejected_warehouse = source_doc.rejected_warehouse target_doc.po_detail = source_doc.po_detail target_doc.pr_detail = source_doc.pr_detail + target_doc.purchase_invoice_item = source_doc.name + elif doctype == "Delivery Note": target_doc.against_sales_order = source_doc.against_sales_order target_doc.against_sales_invoice = source_doc.against_sales_invoice @@ -287,12 +289,14 @@ def make_return_doc(doctype, source_name, target_doc=None): target_doc.dn_detail = source_doc.name if default_warehouse_for_sales_return: target_doc.warehouse = default_warehouse_for_sales_return + elif doctype == "Sales Invoice": target_doc.sales_order = source_doc.sales_order target_doc.delivery_note = source_doc.delivery_note target_doc.so_detail = source_doc.so_detail target_doc.dn_detail = source_doc.dn_detail target_doc.expense_account = source_doc.expense_account + target_doc.sales_invoice_item = source_doc.name if default_warehouse_for_sales_return: target_doc.warehouse = default_warehouse_for_sales_return diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index c25ad060674..1399654ffd2 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -216,7 +216,9 @@ class SellingController(StockController): 'target_warehouse': p.target_warehouse, 'company': self.company, 'voucher_type': self.doctype, - 'allow_zero_valuation': d.allow_zero_valuation_rate + 'allow_zero_valuation': d.allow_zero_valuation_rate, + 'sales_invoice_item': d.get("sales_invoice_item"), + 'delivery_note_item': d.get("dn_detail") })) else: il.append(frappe._dict({ @@ -232,7 +234,9 @@ class SellingController(StockController): 'target_warehouse': d.target_warehouse, 'company': self.company, 'voucher_type': self.doctype, - 'allow_zero_valuation': d.allow_zero_valuation_rate + 'allow_zero_valuation': d.allow_zero_valuation_rate, + 'sales_invoice_item': d.get("sales_invoice_item"), + 'delivery_note_item': d.get("dn_detail") })) return il @@ -301,7 +305,11 @@ class SellingController(StockController): d.conversion_factor = get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0 return_rate = 0 if cint(self.is_return) and self.return_against and self.docstatus==1: - return_rate = self.get_incoming_rate_for_sales_return(d.item_code, self.return_against) + against_document_no = (d.get("sales_invoice_item") + if self.doctype == "Sales Invoice" else d.get("delivery_note_item")) + + return_rate = self.get_incoming_rate_for_sales_return(d.item_code, + self.return_against, against_document_no) # On cancellation or if return entry submission, make stock ledger entry for # target warehouse first, to update serial no values properly diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index ff6ac420208..29f389edfec 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -297,14 +297,19 @@ class StockController(AccountsController): return serialized_items - def get_incoming_rate_for_sales_return(self, item_code, against_document): + def get_incoming_rate_for_sales_return(self, item_code, against_document, against_document_no=None): incoming_rate = 0.0 + cond = '' if against_document and item_code: + if against_document_no: + cond = " and voucher_detail_no = %s" %(frappe.db.escape(against_document_no)) + incoming_rate = frappe.db.sql("""select abs(stock_value_difference / actual_qty) from `tabStock Ledger Entry` where voucher_type = %s and voucher_no = %s - and item_code = %s limit 1""", + and item_code = %s {0} limit 1""".format(cond), (self.doctype, against_document, item_code)) + incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0 return incoming_rate diff --git a/erpnext/controllers/tests/test_mapper.py b/erpnext/controllers/tests/test_mapper.py index d02308d8f21..66459fdbf8a 100644 --- a/erpnext/controllers/tests/test_mapper.py +++ b/erpnext/controllers/tests/test_mapper.py @@ -13,14 +13,12 @@ class TestMapper(unittest.TestCase): '''Test mapping of multiple source docs on a single target doc''' make_test_records("Item") - items = frappe.get_all("Item", fields = ["name", "item_code"], filters = {'is_sales_item': 1, 'has_variants': 0}) - customers = frappe.get_all("Customer") - if items and customers: - # Make source docs (quotations) and a target doc (sales order) - customer = random.choice(customers).name - qtn1, item_list_1 = self.make_quotation(items, customer) - qtn2, item_list_2 = self.make_quotation(items, customer) - so, item_list_3 = self.make_sales_order() + items = ['_Test Item', '_Test Item 2', '_Test FG Item'] + + # Make source docs (quotations) and a target doc (sales order) + qtn1, item_list_1 = self.make_quotation(items, '_Test Customer') + qtn2, item_list_2 = self.make_quotation(items, '_Test Customer') + so, item_list_3 = self.make_sales_order() # Map source docs to target with corresponding mapper method method = "erpnext.selling.doctype.quotation.quotation.make_sales_order" @@ -28,18 +26,12 @@ class TestMapper(unittest.TestCase): # Assert that all inserted items are present in updated sales order src_items = item_list_1 + item_list_2 + item_list_3 - self.assertEqual(set([d.item_code for d in src_items]), + self.assertEqual(set([d for d in src_items]), set([d.item_code for d in updated_so.items])) - def get_random_items(self, items, limit): - '''Get a number of random items from a list of given items''' - random_items = [] - for i in range(0, limit): - random_items.append(random.choice(items)) - return random_items - def make_quotation(self, items, customer): - item_list = self.get_random_items(items, 3) + def make_quotation(self, item_list, customer): + qtn = frappe.get_doc({ "doctype": "Quotation", "quotation_to": "Customer", @@ -49,7 +41,7 @@ class TestMapper(unittest.TestCase): "valid_till" : add_months(nowdate(), 1) }) for item in item_list: - qtn.append("items", {"qty": "2", "item_code": item.item_code}) + qtn.append("items", {"qty": "2", "item_code": item}) qtn.submit() return qtn, item_list @@ -60,7 +52,7 @@ class TestMapper(unittest.TestCase): "base_rate": 100.0, "description": "CPU", "doctype": "Sales Order Item", - "item_code": "_Test Item Home Desktop 100", + "item_code": "_Test Item", "item_name": "CPU", "parentfield": "items", "qty": 10.0, @@ -72,4 +64,4 @@ class TestMapper(unittest.TestCase): }) so = frappe.get_doc(frappe.get_test_records('Sales Order')[0]) so.insert(ignore_permissions=True) - return so, [item] + return so, [item.item_code] diff --git a/erpnext/controllers/tests/test_qty_based_taxes.py b/erpnext/controllers/tests/test_qty_based_taxes.py index fd9936bae99..aaeac5d9399 100644 --- a/erpnext/controllers/tests/test_qty_based_taxes.py +++ b/erpnext/controllers/tests/test_qty_based_taxes.py @@ -30,6 +30,7 @@ class TestTaxes(unittest.TestCase): self.item_tax_template = frappe.get_doc({ 'doctype': 'Item Tax Template', 'title': uuid4(), + 'company': self.company.name, 'taxes': [ { 'tax_type': self.account.name, diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js index 13079172fe8..9ca46912fa7 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.js +++ b/erpnext/crm/doctype/opportunity/opportunity.js @@ -60,12 +60,18 @@ frappe.ui.form.on("Opportunity", { opportunity_from: function(frm) { frm.toggle_reqd("party_name", frm.doc.opportunity_from); + frm.trigger("setup_opportunity_from"); + frm.set_value("party_name",""); + }, + + setup_opportunity_from: function(frm) { + frm.trigger('setup_queries'); frm.trigger("set_dynamic_field_label"); }, refresh: function(frm) { var doc = frm.doc; - frm.events.opportunity_from(frm); + frm.trigger("setup_opportunity_from"); frm.trigger('toggle_mandatory'); erpnext.toggle_naming_series(); diff --git a/erpnext/education/api.py b/erpnext/education/api.py index 1a19716b508..d79a143ea20 100644 --- a/erpnext/education/api.py +++ b/erpnext/education/api.py @@ -151,7 +151,7 @@ def get_fee_components(fee_structure): :param fee_structure: Fee Structure. """ if fee_structure: - fs = frappe.get_list("Fee Component", fields=["fees_category", "amount"] , filters={"parent": fee_structure}, order_by= "idx") + fs = frappe.get_list("Fee Component", fields=["fees_category", "description", "amount"] , filters={"parent": fee_structure}, order_by= "idx") return fs @@ -363,9 +363,9 @@ def get_current_enrollment(student, academic_year=None): select name as program_enrollment, student_name, program, student_batch_name as student_batch, student_category, academic_term, academic_year - from + from `tabProgram Enrollment` - where + where student = %s and academic_year = %s order by creation''', (student, current_academic_year), as_dict=1) diff --git a/erpnext/education/doctype/fees/fees.js b/erpnext/education/doctype/fees/fees.js index 17ef44954b1..ba9dafce1e1 100644 --- a/erpnext/education/doctype/fees/fees.js +++ b/erpnext/education/doctype/fees/fees.js @@ -161,6 +161,7 @@ frappe.ui.form.on("Fees", { $.each(r.message, function(i, d) { var row = frappe.model.add_child(frm.doc, "Fee Component", "components"); row.fees_category = d.fees_category; + row.description = d.description; row.amount = d.amount; }); } diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index 452aa74281e..774aa51daa1 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -776,10 +776,10 @@ class SalarySlip(TransactionBase): # other taxes and charges on income tax for d in tax_slab.other_taxes_and_charges: - if flt(d.min_taxable_income) and flt(d.min_taxable_income) > tax_amount: + if flt(d.min_taxable_income) and flt(d.min_taxable_income) > annual_taxable_earning: continue - if flt(d.max_taxable_income) and flt(d.max_taxable_income) < tax_amount: + if flt(d.max_taxable_income) and flt(d.max_taxable_income) < annual_taxable_earning: continue tax_amount += tax_amount * flt(d.percent) / 100 diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json index c797b7ea77c..11925681dfd 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json @@ -701,7 +701,7 @@ "columns": 0, "default": "Draft", "fieldname": "status", - "fieldtype": "Data", + "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -1001,7 +1001,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-08-21 14:44:44.911402", + "modified": "2020-07-15 14:44:44.911402", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Visit", diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index d5f83d60298..59edcf1d7e6 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -3,7 +3,7 @@ frappe.provide("erpnext.financial_statements"); erpnext.financial_statements = { "filters": get_filters(), "formatter": function(value, row, column, data, default_formatter) { - if (column.fieldname=="account") { + if (data && column.fieldname=="account") { value = data.account_name || value; column.link_onclick = @@ -13,7 +13,7 @@ erpnext.financial_statements = { value = default_formatter(value, row, column, data); - if (!data.parent_account) { + if (data && !data.parent_account) { value = $(`${value}`); var $value = $(value).css("font-weight", "bold"); diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index e78ab9fb690..f29e42f8940 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -333,8 +333,8 @@ erpnext.SerialNoBatchSelector = Class.extend({ }; }, change: function () { - let val = this.get_value(); - if (val.length === 0) { + const batch_no = this.get_value(); + if (!batch_no) { this.grid_row.on_grid_fields_dict .available_qty.set_value(0); return; @@ -354,14 +354,11 @@ erpnext.SerialNoBatchSelector = Class.extend({ return; } - let batch_number = me.item.batch_no || - this.grid_row.on_grid_fields_dict.batch_no.get_value(); - if (me.warehouse_details.name) { frappe.call({ method: 'erpnext.stock.doctype.batch.batch.get_batch_qty', args: { - batch_no: batch_number, + batch_no, warehouse: me.warehouse_details.name, item_code: me.item_code }, diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py index 222dfa1eb78..a3ed4cebb12 100644 --- a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py +++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py @@ -7,6 +7,8 @@ from frappe import _ from frappe.utils import flt from frappe.model.meta import get_field_precision from frappe.utils.xlsxutils import handle_html +from six import iteritems +import json def execute(filters=None): return _execute(filters) @@ -21,21 +23,24 @@ def _execute(filters=None): itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency) data = [] + added_item = [] for d in item_list: - row = [d.gst_hsn_code, d.description, d.stock_uom, d.stock_qty] - total_tax = 0 - for tax in tax_columns: - item_tax = itemised_tax.get(d.name, {}).get(tax, {}) - total_tax += flt(item_tax.get("tax_amount")) + if (d.parent, d.item_code) not in added_item: + row = [d.gst_hsn_code, d.description, d.stock_uom, d.stock_qty] + total_tax = 0 + for tax in tax_columns: + item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {}) + total_tax += flt(item_tax.get("tax_amount", 0)) - row += [d.base_net_amount + total_tax] - row += [d.base_net_amount] + row += [d.base_net_amount + total_tax] + row += [d.base_net_amount] - for tax in tax_columns: - item_tax = itemised_tax.get(d.name, {}).get(tax, {}) - row += [item_tax.get("tax_amount", 0)] + for tax in tax_columns: + item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {}) + row += [item_tax.get("tax_amount", 0)] - data.append(row) + data.append(row) + added_item.append((d.parent, d.item_code)) if data: data = get_merged_data(columns, data) # merge same hsn code data return columns, data @@ -103,7 +108,7 @@ def get_items(filters): match_conditions = " and {0} ".format(match_conditions) - return frappe.db.sql(""" + items = frappe.db.sql(""" select `tabSales Invoice Item`.name, `tabSales Invoice Item`.base_price_list_rate, `tabSales Invoice Item`.gst_hsn_code, `tabSales Invoice Item`.stock_qty, @@ -118,10 +123,9 @@ def get_items(filters): """ % (conditions, match_conditions), filters, as_dict=1) + return items -def get_tax_accounts(item_list, columns, company_currency, - doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"): - import json +def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"): item_row_map = {} tax_columns = [] invoice_item_row = {} @@ -171,7 +175,7 @@ def get_tax_accounts(item_list, columns, company_currency, for d in item_row_map.get(parent, {}).get(item_code, []): item_tax_amount = tax_amount if item_tax_amount: - itemised_tax.setdefault(d.name, {})[description] = frappe._dict({ + itemised_tax.setdefault((parent, item_code), {})[description] = frappe._dict({ "tax_amount": flt(item_tax_amount, tax_amount_precision) }) except ValueError: @@ -179,42 +183,32 @@ def get_tax_accounts(item_list, columns, company_currency, tax_columns.sort() for desc in tax_columns: - columns.append(desc + " Amount:Currency/currency:160") + columns.append({ + "label": desc, + "fieldname": frappe.scrub(desc), + "fieldtype": "Float", + "width": 110 + }) - # columns += ["Total Amount:Currency/currency:110"] return itemised_tax, tax_columns def get_merged_data(columns, data): merged_hsn_dict = {} # to group same hsn under one key and perform row addition - add_column_index = [] # store index of columns that needs to be added - tax_col = len(get_columns()) - fields_to_merge = ["stock_qty", "total_amount", "taxable_amount"] # columns for which index needs to be found - - for i,d in enumerate(columns): - # check if fieldname in to_merge list and ignore tax-columns - if i < tax_col and d["fieldname"] in fields_to_merge: - add_column_index.append(i) + result = [] for row in data: - if row[0] in merged_hsn_dict: - to_add_row = merged_hsn_dict.get(row[0]) + merged_hsn_dict.setdefault(row[0], {}) + for i, d in enumerate(columns): + if d['fieldtype'] not in ('Int', 'Float', 'Currency'): + merged_hsn_dict[row[0]][d['fieldname']] = row[i] + else: + if merged_hsn_dict.get(row[0], {}).get(d['fieldname'], ''): + merged_hsn_dict[row[0]][d['fieldname']] += row[i] + else: + merged_hsn_dict[row[0]][d['fieldname']] = row[i] - # add columns from the add_column_index table - for k in add_column_index: - to_add_row[k] += row[k] + for key, value in iteritems(merged_hsn_dict): + result.append(value) - # add tax columns - for k in range(len(columns)): - if tax_col <= k < len(columns): - to_add_row[k] += row[k] - - # update hsn dict with the newly added data - merged_hsn_dict[row[0]] = to_add_row - else: - merged_hsn_dict[row[0]] = row - - # extract data rows to be displayed in report - data = [merged_hsn_dict[d] for d in merged_hsn_dict] - - return data + return result diff --git a/erpnext/selling/doctype/sales_order/test_records.json b/erpnext/selling/doctype/sales_order/test_records.json index 6cbd6c2fc17..8a090e6d3d3 100644 --- a/erpnext/selling/doctype/sales_order/test_records.json +++ b/erpnext/selling/doctype/sales_order/test_records.json @@ -1,39 +1,39 @@ [ { "advance_paid": 0.0, - "company": "_Test Company", - "conversion_rate": 1.0, - "currency": "INR", - "customer": "_Test Customer", - "customer_group": "_Test Customer Group", - "customer_name": "_Test Customer", - "doctype": "Sales Order", - "base_grand_total": 1000.0, - "grand_total": 1000.0, - "naming_series": "_T-Sales Order-", - "order_type": "Sales", - "plc_conversion_rate": 1.0, - "price_list_currency": "INR", + "company": "_Test Company", + "conversion_rate": 1.0, + "currency": "INR", + "customer": "_Test Customer", + "customer_group": "_Test Customer Group", + "customer_name": "_Test Customer", + "doctype": "Sales Order", + "base_grand_total": 1000.0, + "grand_total": 1000.0, + "naming_series": "_T-Sales Order-", + "order_type": "Sales", + "plc_conversion_rate": 1.0, + "price_list_currency": "INR", "items": [ { - "base_amount": 1000.0, - "base_rate": 100.0, - "description": "CPU", - "doctype": "Sales Order Item", - "item_code": "_Test Item Home Desktop 100", - "item_name": "CPU", - "delivery_date": "2013-02-23", - "parentfield": "items", - "qty": 10.0, - "rate": 100.0, + "base_amount": 1000.0, + "base_rate": 100.0, + "description": "CPU", + "doctype": "Sales Order Item", + "item_code": "_Test Item", + "item_name": "_Test Item 1", + "delivery_date": "2013-02-23", + "parentfield": "items", + "qty": 10.0, + "rate": 100.0, "warehouse": "_Test Warehouse - _TC", "stock_uom": "_Test UOM", "conversion_factor": 1.0, "uom": "_Test UOM" } - ], - "selling_price_list": "_Test Price List", - "territory": "_Test Territory", + ], + "selling_price_list": "_Test Price List", + "territory": "_Test Territory", "transaction_date": "2013-02-21" } ] \ No newline at end of file diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index ec283461ef8..deace33f343 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -13,7 +13,7 @@ from erpnext.controllers.item_variant import (ItemVariantExistsError, from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups, invalidate_cache_for) from frappe import _, msgprint from frappe.utils import (cint, cstr, flt, formatdate, get_timestamp, getdate, - now_datetime, random_string, strip) + now_datetime, random_string, strip, get_link_to_form) from frappe.utils.html_utils import clean_html from frappe.website.doctype.website_slideshow.website_slideshow import \ get_slideshow @@ -634,6 +634,9 @@ class Item(WebsiteGenerator): + ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list])) def after_rename(self, old_name, new_name, merge): + if merge: + self.validate_duplicate_item_in_stock_reconciliation(old_name, new_name) + if self.route: invalidate_cache_for_item(self) clear_cache(self.route) @@ -656,6 +659,27 @@ class Item(WebsiteGenerator): frappe.db.set_value(dt, d.name, "item_wise_tax_detail", json.dumps(item_wise_tax_detail), update_modified=False) + def validate_duplicate_item_in_stock_reconciliation(self, old_name, new_name): + records = frappe.db.sql(""" SELECT parent, COUNT(*) as records + FROM `tabStock Reconciliation Item` + WHERE item_code = %s and docstatus = 1 + GROUP By item_code, warehouse, parent + HAVING records > 1 + """, new_name, as_dict=1) + + if not records: return + document = _("Stock Reconciliation") if len(records) == 1 else _("Stock Reconciliations") + + msg = _("The items {0} and {1} are present in the following {2} :
" + .format(frappe.bold(old_name), frappe.bold(new_name), document)) + + msg += ', '.join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "

" + + msg += _("Note: To merge the items, create a separate Stock Reconciliation for the old item {0}" + .format(frappe.bold(old_name))) + + frappe.throw(_(msg), title=_("Merge not allowed")) + def set_last_purchase_rate(self, new_name): last_purchase_rate = get_last_purchase_details(new_name).get("base_net_rate", 0) frappe.db.set_value("Item", new_name, "last_purchase_rate", last_purchase_rate) diff --git a/erpnext/stock/doctype/item/test_records.json b/erpnext/stock/doctype/item/test_records.json index 6c1a55945c8..9ca887c77e3 100644 --- a/erpnext/stock/doctype/item/test_records.json +++ b/erpnext/stock/doctype/item/test_records.json @@ -92,8 +92,7 @@ { "doctype": "Item Tax", "parentfield": "taxes", - "item_tax_template": "_Test Account Excise Duty @ 10", - "tax_category": "" + "item_tax_template": "_Test Account Excise Duty @ 10" } ], "stock_uom": "_Test UOM 1" @@ -371,8 +370,7 @@ { "doctype": "Item Tax", "parentfield": "taxes", - "item_tax_template": "_Test Account Excise Duty @ 10", - "tax_category": "" + "item_tax_template": "_Test Account Excise Duty @ 10" }, { "doctype": "Item Tax", @@ -451,14 +449,13 @@ { "doctype": "Item Tax", "parentfield": "taxes", - "item_tax_template": "_Test Account Excise Duty @ 20", - "tax_category": "" + "item_tax_template": "_Test Account Excise Duty @ 20" }, { "doctype": "Item Tax", "parentfield": "taxes", - "item_tax_template": "_Test Item Tax Template 1", - "tax_category": "_Test Tax Category 1" + "tax_category": "_Test Tax Category 1", + "item_tax_template": "_Test Item Tax Template 1" } ] } diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index b113781def3..1ccd8cf31a0 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -19,6 +19,12 @@ frappe.ui.form.on('Material Request', { frm.set_indicator_formatter('item_code', function(doc) { return (doc.stock_qty<=doc.ordered_qty) ? "green" : "orange"; }); + frm.set_query("from_warehouse", "items", function(doc) { + return { + filters: {'company': doc.company} + }; + }); + }, onload: function(frm) { @@ -27,11 +33,24 @@ frappe.ui.form.on('Material Request', { // set schedule_date set_schedule_date(frm); - frm.fields_dict["items"].grid.get_field("warehouse").get_query = function(doc) { + + frm.set_query("warehouse", "items", function(doc) { return { filters: {'company': doc.company} }; - }; + }); + + frm.set_query("set_warehouse", function(doc){ + return { + filters: {'company': doc.company} + }; + }); + + frm.set_query("set_from_warehouse", function(doc){ + return { + filters: {'company': doc.company} + }; + }); }, onload_post_render: function(frm) { diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json index 2be14c8006a..b9427289449 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.json +++ b/erpnext/stock/doctype/serial_no/serial_no.json @@ -1,6 +1,7 @@ { "actions": [], "allow_import": 1, + "allow_rename": 1, "autoname": "field:serial_no", "creation": "2013-05-16 10:59:15", "description": "Distinct unit of an Item", @@ -426,7 +427,7 @@ "icon": "fa fa-barcode", "idx": 1, "links": [], - "modified": "2020-06-25 15:53:50.900855", + "modified": "2020-07-22 15:53:50.900855", "modified_by": "Administrator", "module": "Stock", "name": "Serial No", diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 2cb43ae2db3..bbdac992b58 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -193,7 +193,7 @@ class SerialNo(StockController): def after_rename(self, old, new, merge=False): """rename serial_no text fields""" for dt in frappe.db.sql("""select parent from tabDocField - where fieldname='serial_no' and fieldtype in ('Text', 'Small Text')"""): + where fieldname='serial_no' and fieldtype in ('Text', 'Small Text', 'Long Text')"""): for item in frappe.db.sql("""select name, serial_no from `tab%s` where serial_no like %s""" % (dt[0], frappe.db.escape('%' + old + '%'))): diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 36f309c1a6e..0dc87767dde 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -257,6 +257,7 @@ class StockReconciliation(StockController): sl_entries.append(args) + qty_after_transaction = 0 for serial_no in serial_nos: args = self.get_sle_for_items(row, [serial_no]) @@ -270,11 +271,19 @@ class StockReconciliation(StockController): if previous_sle and row.warehouse != previous_sle.get("warehouse"): # If serial no exists in different warehouse + warehouse = previous_sle.get("warehouse", '') or row.warehouse + + if not qty_after_transaction: + qty_after_transaction = get_stock_balance(row.item_code, + warehouse, self.posting_date, self.posting_time) + + qty_after_transaction -= 1 + new_args = args.copy() new_args.update({ 'actual_qty': -1, - 'qty_after_transaction': cint(previous_sle.get('qty_after_transaction')) - 1, - 'warehouse': previous_sle.get("warehouse", '') or row.warehouse, + 'qty_after_transaction': qty_after_transaction, + 'warehouse': warehouse, 'valuation_rate': previous_sle.get("valuation_rate") }) diff --git a/erpnext/stock/doctype/warehouse/warehouse.json b/erpnext/stock/doctype/warehouse/warehouse.json index 0606d0bdc64..909d8216308 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.json +++ b/erpnext/stock/doctype/warehouse/warehouse.json @@ -1,5 +1,4 @@ { - "actions": [], "allow_import": 1, "allow_rename": 1, "creation": "2013-03-07 18:50:32", @@ -10,12 +9,14 @@ "field_order": [ "warehouse_detail", "warehouse_name", + "section_break_3", + "warehouse_type", + "parent_warehouse", "is_group", - "company", - "disabled", "column_break_4", "account", - "warehouse_type", + "company", + "disabled", "address_and_contact", "address_html", "column_break_10", @@ -31,7 +32,6 @@ "state", "pin", "tree_details", - "parent_warehouse", "lft", "rgt", "old_parent" @@ -91,6 +91,7 @@ "options": "Account" }, { + "depends_on": "eval:!doc.__islocal", "fieldname": "address_and_contact", "fieldtype": "Section Break", "label": "Address and Contact" @@ -224,13 +225,16 @@ "fieldtype": "Link", "label": "Warehouse Type", "options": "Warehouse Type" + }, + { + "fieldname": "section_break_3", + "fieldtype": "Section Break" } ], "icon": "fa fa-building", "idx": 1, "is_tree": 1, - "links": [], - "modified": "2020-03-18 18:26:00.479541", + "modified": "2020-07-22 14:46:37.650475", "modified_by": "Administrator", "module": "Stock", "name": "Warehouse",