diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index 86c3a8a9336..c0a65ed0d4c 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -41,9 +41,10 @@ class BankTransaction(StatusUpdater): else: allocated_amount = 0.0 - amount = abs(flt(self.withdrawal) - flt(self.deposit)) - self.db_set("allocated_amount", flt(allocated_amount)) - self.db_set("unallocated_amount", amount - flt(allocated_amount)) + unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - allocated_amount + + self.db_set("allocated_amount", flt(allocated_amount, self.precision("allocated_amount"))) + self.db_set("unallocated_amount", flt(unallocated_amount, self.precision("unallocated_amount"))) self.reload() self.set_status(update=True) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 2ac7370f4ef..b2672424af9 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -11,23 +11,8 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e super.setup(doc); } company() { + super.company(); erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype); - - let me = this; - if (this.frm.doc.company) { - frappe.call({ - method: - "erpnext.accounts.party.get_party_account", - args: { - party_type: 'Customer', - party: this.frm.doc.customer, - company: this.frm.doc.company - }, - callback: (response) => { - if (response) me.frm.set_value("debit_to", response.message); - }, - }); - } } onload() { var me = this; diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 9219cc5e383..88e26863cf1 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -85,7 +85,10 @@ class ReceivablePayableReport(object): self.skip_total_row = 1 if self.filters.get("in_party_currency"): - self.skip_total_row = 1 + if self.filters.get("party") and len(self.filters.get("party")) == 1: + self.skip_total_row = 0 + else: + self.skip_total_row = 1 def get_data(self): self.get_ple_entries() diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.js b/erpnext/accounts/report/balance_sheet/balance_sheet.js index f1f8e5f6e7c..57de73e72d0 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.js +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.js @@ -6,6 +6,19 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { erpnext.utils.add_dimensions('Balance Sheet', 10); + frappe.query_reports["Balance Sheet"]["filters"].push( + { + "fieldname": "selected_view", + "label": __("Select View"), + "fieldtype": "Select", + "options": [ + { "value": "Report", "label": __("Report View") }, + { "value": "Growth", "label": __("Growth View") } + ], + "default": "Report", + "reqd": 1 + }, + ); frappe.query_reports["Balance Sheet"]["filters"].push({ "fieldname": "accumulated_values", "label": __("Accumulated Values"), diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index 5d3d4d74978..b5dd66f75fe 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -319,7 +319,8 @@ def get_items(filters, additional_query_columns): `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, `tabPurchase Invoice`.unrealized_profit_loss_account, `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, - `tabPurchase Invoice Item`.`item_name` as pi_item_name, `tabPurchase Invoice Item`.`item_group` as pi_item_group, + `tabPurchase Invoice Item`.`item_name` as pi_item_name, `tabPurchase Invoice Item`.`item_group` + ,`tabPurchase Invoice Item`.`item_group` as pi_item_group, `tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group, `tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`, `tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`, diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index ce22d7566c1..94457d59981 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -350,7 +350,13 @@ def get_conditions(filters, additional_conditions=None): and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)""" if filters.get("warehouse"): - conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s""" + if frappe.db.get_value("Warehouse", filters.get("warehouse"), "is_group"): + lft, rgt = frappe.db.get_all( + "Warehouse", filters={"name": filters.get("warehouse")}, fields=["lft", "rgt"], as_list=True + )[0] + conditions += f"and ifnull(`tabSales Invoice Item`.warehouse, '') in (select name from `tabWarehouse` where lft > {lft} and rgt < {rgt}) " + else: + conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s""" if filters.get("brand"): conditions += """and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s""" diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js index e794f270c2b..4e5cd312734 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js @@ -8,6 +8,21 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { erpnext.utils.add_dimensions('Profit and Loss Statement', 10); + frappe.query_reports["Profit and Loss Statement"]["filters"].push( + { + "fieldname": "selected_view", + "label": __("Select View"), + "fieldtype": "Select", + "options": [ + { "value": "Report", "label": __("Report View") }, + { "value": "Growth", "label": __("Growth View") }, + { "value": "Margin", "label": __("Margin View") }, + ], + "default": "Report", + "reqd": 1 + }, + ); + frappe.query_reports["Profit and Loss Statement"]["filters"].push( { "fieldname": "include_default_book_entries", diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index b3e9996b515..280cf338f70 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -10,7 +10,7 @@ import frappe.defaults from frappe import _, qb, throw from frappe.model.meta import get_field_precision from frappe.query_builder import AliasedQuery, Criterion, Table -from frappe.query_builder.functions import Sum +from frappe.query_builder.functions import Round, Sum from frappe.query_builder.utils import DocType from frappe.utils import ( cint, @@ -549,16 +549,19 @@ def check_if_advance_entry_modified(args): args, ) else: - ret = frappe.db.sql( - """select name from `tabPayment Entry` - where - name = %(voucher_no)s and docstatus = 1 - and party_type = %(party_type)s and party = %(party)s and {0} = %(account)s - and round(unallocated_amount, {1}) = round(%(unreconciled_amount)s, {1}) - """.format( - party_account_field, precision - ), - args, + pe = qb.DocType("Payment Entry") + ret = ( + qb.from_(pe) + .select(pe.name) + .where( + (pe.name == args.voucher_no) + & (pe.docstatus == 1) + & (pe.party_type == args.party_type) + & (pe.party == args.party) + & (pe[party_account_field] == args.account) + & (Round(pe.unallocated_amount, precision) == Round(args.unreconciled_amount, precision)) + ) + .run() ) if not ret: diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 3fcb9720dd1..7f5e4debf29 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -513,14 +513,15 @@ class Asset(AccountsController): ) # Adjust depreciation amount in the last period based on the expected value after useful life - if finance_book.expected_value_after_useful_life and ( - ( - n == cint(final_number_of_depreciations) - 1 - and value_after_depreciation != finance_book.expected_value_after_useful_life - ) - or value_after_depreciation < finance_book.expected_value_after_useful_life + if ( + n == cint(final_number_of_depreciations) - 1 + and flt(value_after_depreciation) != flt(finance_book.expected_value_after_useful_life) + ) or flt(value_after_depreciation) < flt( + finance_book.expected_value_after_useful_life ): - depreciation_amount += value_after_depreciation - finance_book.expected_value_after_useful_life + depreciation_amount += flt(value_after_depreciation) - flt( + finance_book.expected_value_after_useful_life + ) skip_row = True if flt(depreciation_amount, self.precision("gross_purchase_amount")) > 0: diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 6523713ece0..22bd6b1d6ff 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -520,7 +520,7 @@ def reverse_depreciation_entry_made_after_disposal(asset, date): else: row += 1 - if schedule.schedule_date == date: + if schedule.schedule_date == date and schedule.journal_entry: if not disposal_was_made_on_original_schedule_date( asset, schedule, row, date ) or disposal_happens_in_the_future(date): diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index a347ece5f5b..2b5d8d0ffdd 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -77,13 +77,13 @@ class AssetCapitalization(StockController): "Stock Ledger Entry", "Repost Item Valuation", "Asset", - "Asset Movement" + "Asset Movement", ) self.cancel_target_asset() self.update_stock_ledger() self.make_gl_entries() self.restore_consumed_asset_items() - + def cancel_target_asset(self): if self.entry_type == "Capitalization" and self.target_asset: asset_doc = frappe.get_doc("Asset", self.target_asset) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 2420d987c23..f642cde73c3 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -201,6 +201,18 @@ class AccountsController(TransactionBase): ) ) + if self.get("is_return") and self.get("return_against"): + document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note" + frappe.msgprint( + _( + "{0} will be treated as a standalone {0}. Post creation use {1} tool to reconcile against {2}." + ).format( + document_type, + get_link_to_form("Payment Reconciliation", "Payment Reconciliation"), + get_link_to_form(self.doctype, self.get("return_against")), + ) + ) + pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid" if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)): self.set_advances() diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py index 029b4b720a7..83629092fa4 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py @@ -16,11 +16,49 @@ class BlanketOrder(Document): def validate(self): self.validate_dates() self.validate_duplicate_items() + self.set_party_item_code() def validate_dates(self): if getdate(self.from_date) > getdate(self.to_date): frappe.throw(_("From date cannot be greater than To date")) + def set_party_item_code(self): + item_ref = {} + if self.blanket_order_type == "Selling": + item_ref = self.get_customer_items_ref() + else: + item_ref = self.get_supplier_items_ref() + + if not item_ref: + return + + for row in self.items: + row.party_item_code = item_ref.get(row.item_code) + + def get_customer_items_ref(self): + items = [d.item_code for d in self.items] + + return frappe._dict( + frappe.get_all( + "Item Customer Detail", + filters={"parent": ("in", items), "customer_name": self.customer}, + fields=["parent", "ref_code"], + as_list=True, + ) + ) + + def get_supplier_items_ref(self): + items = [d.item_code for d in self.items] + + return frappe._dict( + frappe.get_all( + "Item Supplier", + filters={"parent": ("in", items), "supplier": self.supplier}, + fields=["parent", "supplier_part_no"], + as_list=True, + ) + ) + def validate_duplicate_items(self): item_list = [] for item in self.items: diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py index e9fc25b5bcb..3f3b6f092c0 100644 --- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py @@ -5,6 +5,7 @@ from frappe.tests.utils import FrappeTestCase from frappe.utils import add_months, today from erpnext import get_company_currency +from erpnext.stock.doctype.item.test_item import make_item from .blanket_order import make_order @@ -90,6 +91,30 @@ class TestBlanketOrder(FrappeTestCase): frappe.db.set_single_value("Buying Settings", "blanket_order_allowance", 10) po.submit() + def test_party_item_code(self): + item_doc = make_item("_Test Item 1 for Blanket Order") + item_code = item_doc.name + + customer = "_Test Customer" + supplier = "_Test Supplier" + + if not frappe.db.exists( + "Item Customer Detail", {"customer_name": customer, "parent": item_code} + ): + item_doc.append("customer_items", {"customer_name": customer, "ref_code": "CUST-REF-1"}) + item_doc.save() + + if not frappe.db.exists("Item Supplier", {"supplier": supplier, "parent": item_code}): + item_doc.append("supplier_items", {"supplier": supplier, "supplier_part_no": "SUPP-PART-1"}) + item_doc.save() + + # Blanket Order for Selling + bo = make_blanket_order(blanket_order_type="Selling", customer=customer, item_code=item_code) + self.assertEqual(bo.items[0].party_item_code, "CUST-REF-1") + + bo = make_blanket_order(blanket_order_type="Purchasing", supplier=supplier, item_code=item_code) + self.assertEqual(bo.items[0].party_item_code, "SUPP-PART-1") + def make_blanket_order(**args): args = frappe._dict(args) diff --git a/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json b/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json index 977ad547f55..aa7831fd6b8 100644 --- a/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json +++ b/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2018-05-24 07:20:04.255236", "doctype": "DocType", "editable_grid": 1, @@ -6,6 +7,7 @@ "field_order": [ "item_code", "item_name", + "party_item_code", "column_break_3", "qty", "rate", @@ -62,10 +64,17 @@ "fieldname": "terms_and_conditions", "fieldtype": "Text", "label": "Terms and Conditions" + }, + { + "fieldname": "party_item_code", + "fieldtype": "Data", + "label": "Party Item Code", + "read_only": 1 } ], "istable": 1, - "modified": "2019-11-18 19:37:46.245878", + "links": [], + "modified": "2024-02-14 18:25:26.479672", "modified_by": "Administrator", "module": "Manufacturing", "name": "Blanket Order Item", @@ -74,5 +83,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 5e1974299ee..666505e2e38 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -2,7 +2,57 @@ frappe.provide("erpnext.financial_statements"); erpnext.financial_statements = { "filters": get_filters(), + "baseData": null, "formatter": function(value, row, column, data, default_formatter, filter) { + if(frappe.query_report.get_filter_value("selected_view") == "Growth" && data && column.colIndex >= 3){ + //Assuming that the first three columns are s.no, account name and the very first year of the accounting values, to calculate the relative percentage values of the successive columns. + const lastAnnualValue = row[column.colIndex - 1].content; + const currentAnnualvalue = data[column.fieldname]; + if(currentAnnualvalue == undefined) return 'NA'; //making this not applicable for undefined/null values + let annualGrowth = 0; + if(lastAnnualValue == 0 && currentAnnualvalue > 0){ + //If the previous year value is 0 and the current value is greater than 0 + annualGrowth = 1; + } + else if(lastAnnualValue > 0){ + annualGrowth = (currentAnnualvalue - lastAnnualValue) / lastAnnualValue; + } + + const growthPercent = (Math.round(annualGrowth*10000)/100); //calculating the rounded off percentage + + value = $(`${((growthPercent >=0)? '+':'' )+growthPercent+'%'}`); + if(growthPercent < 0){ + value = $(value).addClass("text-danger"); + } + else{ + value = $(value).addClass("text-success"); + } + value = $(value).wrap("
").parent().html(); + + return value; + } + else if(frappe.query_report.get_filter_value("selected_view") == "Margin" && data){ + if(column.fieldname =="account" && data.account_name == __("Income")){ + //Taking the total income from each column (for all the financial years) as the base (100%) + this.baseData = row; + } + if(column.colIndex >= 2){ + //Assuming that the first two columns are s.no and account name, to calculate the relative percentage values of the successive columns. + const currentAnnualvalue = data[column.fieldname]; + const baseValue = this.baseData[column.colIndex].content; + if(currentAnnualvalue == undefined || baseValue <= 0) return 'NA'; + const marginPercent = Math.round((currentAnnualvalue/baseValue)*10000)/100; + + value = $(`${marginPercent+'%'}`); + if(marginPercent < 0) + value = $(value).addClass("text-danger"); + else + value = $(value).addClass("text-success"); + value = $(value).wrap("").parent().html(); + return value; + } + + } if (data && column.fieldname=="account") { value = data.account_name || value; diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 4dc8e6b9cbd..c5bc601e391 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -250,7 +250,7 @@ frappe.ui.form.on('Material Request', { fields: [ {"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"), options:"BOM", reqd: 1, get_query: function() { - return {filters: { docstatus:1 }}; + return {filters: { docstatus:1, "is_active": 1 }}; }}, {"fieldname":"warehouse", "fieldtype":"Link", "label":__("For Warehouse"), options:"Warehouse", reqd: 1}, diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 36c877ebc88..86462476a5d 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -32,6 +32,10 @@ class PickList(Document): self.update_status() self.set_item_locations() + if self.get("locations"): + self.validate_sales_order_percentage() + + def validate_sales_order_percentage(self): # set percentage picked in SO for location in self.get("locations"): if ( diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 0786ce6be2a..b6459ba1e28 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -548,7 +548,9 @@ frappe.ui.form.on('Stock Entry', { let fields = [ {"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"), - options:"BOM", reqd: 1, get_query: filters()}, + options:"BOM", reqd: 1, get_query: () => { + return {filters: { docstatus:1, "is_active": 1 }}; + }}, {"fieldname":"source_warehouse", "fieldtype":"Link", "label":__("Source Warehouse"), options:"Warehouse"}, {"fieldname":"target_warehouse", "fieldtype":"Link", "label":__("Target Warehouse"), diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 7f3e0cf4c21..25041b1973b 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -72,8 +72,8 @@ class Issue(Document): "reference_name": self.name, } ) - communication.ignore_permissions = True - communication.ignore_mandatory = True + communication.flags.ignore_permissions = True + communication.flags.ignore_mandatory = True communication.save() @frappe.whitelist() diff --git a/erpnext/utilities/product.py b/erpnext/utilities/product.py index 963cd2bf8f0..69559c1ed9b 100644 --- a/erpnext/utilities/product.py +++ b/erpnext/utilities/product.py @@ -51,8 +51,8 @@ def get_web_item_qty_in_stock(item_code, item_warehouse_field, warehouse=None): .where((BIN.item_code == item_code) & (BIN.warehouse == warehouse)) ).run() - stock_qty = stock_qty[0][0] if stock_qty: + stock_qty = flt(stock_qty[0][0]) total_stock += adjust_qty_for_expired_items(item_code, stock_qty, warehouse) in_stock = int(total_stock > 0)