diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index af1c06643a1..d984d86af25 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -33,7 +33,7 @@ class PeriodClosingVoucher(AccountsController): def on_cancel(self): self.validate_future_closing_vouchers() self.db_set("gle_processing_status", "In Progress") - self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") + self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry") gle_count = frappe.db.count( "GL Entry", {"voucher_type": "Period Closing Voucher", "voucher_no": self.name, "is_cancelled": 0}, diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 7c013b6abef..d3058e44509 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -518,7 +518,7 @@ class POSInvoice(SalesInvoice): selling_price_list = ( customer_price_list or customer_group_price_list or profile.get("selling_price_list") ) - if customer_currency != profile.get("currency"): + if customer_currency and customer_currency != profile.get("currency"): self.set("currency", customer_currency) else: diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index e1f32952205..5055c345917 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -64,6 +64,7 @@ def get_report_pdf(doc, consolidated=True): filters = get_common_filters(doc) if doc.report == "General Ledger": + filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency)) col, res = get_soa(filters) for x in [0, -2, -1]: res[x]["account"] = res[x]["account"].replace("'", "") @@ -142,7 +143,8 @@ def get_gl_filters(doc, entry, tax_id, presentation_currency): def get_ar_filters(doc, entry): return { "report_date": doc.posting_date if doc.posting_date else None, - "customer": entry.customer, + "party_type": "Customer", + "party": entry.customer, "customer_name": entry.customer_name if entry.customer_name else None, "payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None, "sales_partner": doc.sales_partner if doc.sales_partner else None, diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 378be113e7c..e0a7ff002bb 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1801,6 +1801,10 @@ class TestSalesInvoice(unittest.TestCase): ) def test_outstanding_amount_after_advance_payment_entry_cancellation(self): + """Test impact of advance PE submission/cancellation on SI and SO.""" + from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order + + sales_order = make_sales_order(item_code="138-CMS Shoe", qty=1, price_list_rate=500) pe = frappe.get_doc( { "doctype": "Payment Entry", @@ -1820,10 +1824,25 @@ class TestSalesInvoice(unittest.TestCase): "paid_to": "_Test Cash - _TC", } ) + pe.append( + "references", + { + "reference_doctype": "Sales Order", + "reference_name": sales_order.name, + "total_amount": sales_order.grand_total, + "outstanding_amount": sales_order.grand_total, + "allocated_amount": 300, + }, + ) pe.insert() pe.submit() + sales_order.reload() + self.assertEqual(sales_order.advance_paid, 300) + si = frappe.copy_doc(test_records[0]) + si.items[0].sales_order = sales_order.name + si.items[0].so_detail = sales_order.get("items")[0].name si.is_pos = 0 si.append( "advances", @@ -1831,6 +1850,7 @@ class TestSalesInvoice(unittest.TestCase): "doctype": "Sales Invoice Advance", "reference_type": "Payment Entry", "reference_name": pe.name, + "reference_row": pe.references[0].name, "advance_amount": 300, "allocated_amount": 300, "remarks": pe.remarks, @@ -1839,7 +1859,13 @@ class TestSalesInvoice(unittest.TestCase): si.insert() si.submit() - si.load_from_db() + si.reload() + pe.reload() + sales_order.reload() + + # Check if SO is unlinked/replaced by SI in PE & if SO advance paid is 0 + self.assertEqual(pe.references[0].reference_name, si.name) + self.assertEqual(sales_order.advance_paid, 0.0) # check outstanding after advance allocation self.assertEqual( @@ -1847,11 +1873,9 @@ class TestSalesInvoice(unittest.TestCase): flt(si.rounded_total - si.total_advance, si.precision("outstanding_amount")), ) - # added to avoid Document has been modified exception - pe = frappe.get_doc("Payment Entry", pe.name) pe.cancel() + si.reload() - si.load_from_db() # check outstanding after advance cancellation self.assertEqual( flt(si.outstanding_amount), diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 27a85701edd..484ff7fa2b3 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -108,11 +108,8 @@ frappe.query_reports["Accounts Payable"] = { }, on_change: () => { frappe.query_report.set_filter_value('party', ""); - let party_type = frappe.query_report.get_filter_value('party_type'); frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier"); - } - }, { "fieldname":"party", diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js index ea200720dff..8a1725c0485 100644 --- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js +++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js @@ -72,10 +72,27 @@ frappe.query_reports["Accounts Payable Summary"] = { } }, { - "fieldname":"supplier", - "label": __("Supplier"), + "fieldname": "party_type", + "label": __("Party Type"), "fieldtype": "Link", - "options": "Supplier" + "options": "Party Type", + get_query: () => { + return { + filters: { + 'account_type': 'Payable' + } + }; + }, + on_change: () => { + frappe.query_report.set_filter_value('party', ""); + frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier"); + } + }, + { + "fieldname":"party", + "label": __("Party"), + "fieldtype": "Dynamic Link", + "options": "party_type", }, { "fieldname":"payment_terms_template", diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index bb00d616dbc..67a14e7880a 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -52,9 +52,7 @@ frappe.query_reports["Accounts Receivable"] = { }, on_change: () => { frappe.query_report.set_filter_value('party', ""); - let party_type = frappe.query_report.get_filter_value('party_type'); frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer"); - } }, { diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js index 715cd6476e8..a78fbeb0308 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js @@ -72,10 +72,28 @@ frappe.query_reports["Accounts Receivable Summary"] = { } }, { - "fieldname":"customer", - "label": __("Customer"), + "fieldname": "party_type", + "label": __("Party Type"), "fieldtype": "Link", - "options": "Customer" + "options": "Party Type", + "Default": "Customer", + get_query: () => { + return { + filters: { + 'account_type': 'Receivable' + } + }; + }, + on_change: () => { + frappe.query_report.set_filter_value('party', ""); + frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer"); + } + }, + { + "fieldname":"party", + "label": __("Party"), + "fieldtype": "Dynamic Link", + "options": "party_type", }, { "fieldname":"customer_group", diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js index e45c3adcb6d..6e233c802f0 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.js +++ b/erpnext/accounts/report/trial_balance/trial_balance.js @@ -99,6 +99,12 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "label": __("Include Default Book Entries"), "fieldtype": "Check", "default": 1 + }, + { + "fieldname": "show_net_values", + "label": __("Show net values in opening and closing columns"), + "fieldtype": "Check", + "default": 1 } ], "formatter": erpnext.financial_statements.formatter, diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 376571f0346..2a8aa0c202f 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -120,7 +120,9 @@ def get_data(filters): ignore_opening_entries=True, ) - calculate_values(accounts, gl_entries_by_account, opening_balances) + calculate_values( + accounts, gl_entries_by_account, opening_balances, filters.get("show_net_values") + ) accumulate_values_into_parents(accounts, accounts_by_name) data = prepare_data(accounts, filters, parent_children_map, company_currency) @@ -310,7 +312,7 @@ def get_opening_balance( return gle -def calculate_values(accounts, gl_entries_by_account, opening_balances): +def calculate_values(accounts, gl_entries_by_account, opening_balances, show_net_values): init = { "opening_debit": 0.0, "opening_credit": 0.0, @@ -335,7 +337,8 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances): d["closing_debit"] = d["opening_debit"] + d["debit"] d["closing_credit"] = d["opening_credit"] + d["credit"] - prepare_opening_closing(d) + if show_net_values: + prepare_opening_closing(d) def calculate_total_row(accounts, company_currency): @@ -375,7 +378,7 @@ def prepare_data(accounts, filters, parent_children_map, company_currency): for d in accounts: # Prepare opening closing for group account - if parent_children_map.get(d.account): + if parent_children_map.get(d.account) and filters.get("show_net_values"): prepare_opening_closing(d) has_value = False diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 8f0ef869ad3..971932e415a 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -560,6 +560,10 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): """ jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0] + # Update Advance Paid in SO/PO since they might be getting unlinked + if jv_detail.get("reference_type") in ("Sales Order", "Purchase Order"): + frappe.get_doc(jv_detail.reference_type, jv_detail.reference_name).set_total_advance_paid() + if flt(d["unadjusted_amount"]) - flt(d["allocated_amount"]) != 0: # adjust the unreconciled balance amount_in_account_currency = flt(d["unadjusted_amount"]) - flt(d["allocated_amount"]) @@ -625,6 +629,13 @@ def update_reference_in_payment_entry( if d.voucher_detail_no: existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0] + + # Update Advance Paid in SO/PO since they are getting unlinked + if existing_row.get("reference_doctype") in ("Sales Order", "Purchase Order"): + frappe.get_doc( + existing_row.reference_doctype, existing_row.reference_name + ).set_total_advance_paid() + original_row = existing_row.as_dict().copy() existing_row.update(reference_details) diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index b01b76d1ec9..6faddd2a8be 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -655,7 +655,7 @@ class SubcontractingController(StockController): { "item_code": item.rm_item_code, "warehouse": self.supplier_warehouse, - "actual_qty": -1 * flt(item.consumed_qty), + "actual_qty": -1 * flt(item.consumed_qty, item.precision("consumed_qty")), "dependant_sle_voucher_detail_no": item.reference_name, }, ) diff --git a/erpnext/e_commerce/doctype/website_item/test_website_item.py b/erpnext/e_commerce/doctype/website_item/test_website_item.py index 019a5f9ee4f..8eebfdb83af 100644 --- a/erpnext/e_commerce/doctype/website_item/test_website_item.py +++ b/erpnext/e_commerce/doctype/website_item/test_website_item.py @@ -312,7 +312,7 @@ class TestWebsiteItem(unittest.TestCase): # check if stock details are fetched and item not in stock with warehouse set data = get_product_info_for_website(item_code, skip_quotation_creation=True) self.assertFalse(bool(data.product_info["in_stock"])) - self.assertEqual(data.product_info["stock_qty"][0][0], 0) + self.assertEqual(data.product_info["stock_qty"], 0) # disable show stock availability setup_e_commerce_settings({"show_stock_availability": 0}) @@ -355,7 +355,7 @@ class TestWebsiteItem(unittest.TestCase): # check if stock details are fetched and item is in stock with warehouse set data = get_product_info_for_website(item_code, skip_quotation_creation=True) self.assertTrue(bool(data.product_info["in_stock"])) - self.assertEqual(data.product_info["stock_qty"][0][0], 2) + self.assertEqual(data.product_info["stock_qty"], 2) # unset warehouse frappe.db.set_value("Website Item", {"item_code": item_code}, "website_warehouse", "") @@ -364,7 +364,7 @@ class TestWebsiteItem(unittest.TestCase): # (even though it has stock in some warehouse) data = get_product_info_for_website(item_code, skip_quotation_creation=True) self.assertFalse(bool(data.product_info["in_stock"])) - self.assertFalse(bool(data.product_info["stock_qty"])) + self.assertFalse(data.product_info["stock_qty"]) # disable show stock availability setup_e_commerce_settings({"show_stock_availability": 0}) diff --git a/erpnext/e_commerce/doctype/website_item/website_item.js b/erpnext/e_commerce/doctype/website_item/website_item.js index 7b7193e833a..b6595cce8a9 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.js +++ b/erpnext/e_commerce/doctype/website_item/website_item.js @@ -5,12 +5,6 @@ frappe.ui.form.on('Website Item', { onload: (frm) => { // should never check Private frm.fields_dict["website_image"].df.is_private = 0; - - frm.set_query("website_warehouse", () => { - return { - filters: {"is_group": 0} - }; - }); }, refresh: (frm) => { diff --git a/erpnext/e_commerce/doctype/website_item/website_item.json b/erpnext/e_commerce/doctype/website_item/website_item.json index 6556eabf4ab..6f551a0b42d 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.json +++ b/erpnext/e_commerce/doctype/website_item/website_item.json @@ -135,7 +135,7 @@ "fieldtype": "Column Break" }, { - "description": "Show Stock availability based on this warehouse.", + "description": "Show Stock availability based on this warehouse. If the parent warehouse is selected, then the system will display the consolidated available quantity of all child warehouses.", "fieldname": "website_warehouse", "fieldtype": "Link", "ignore_user_permissions": 1, @@ -348,7 +348,7 @@ "index_web_pages_for_search": 1, "links": [], "make_attachments_public": 1, - "modified": "2022-09-30 04:01:52.090732", + "modified": "2023-09-12 14:19:22.822689", "modified_by": "Administrator", "module": "E-commerce", "name": "Website Item", diff --git a/erpnext/e_commerce/product_data_engine/query.py b/erpnext/e_commerce/product_data_engine/query.py index e6a595a0344..975f87608a6 100644 --- a/erpnext/e_commerce/product_data_engine/query.py +++ b/erpnext/e_commerce/product_data_engine/query.py @@ -259,6 +259,10 @@ class ProductQuery: ) def get_stock_availability(self, item): + from erpnext.templates.pages.wishlist import ( + get_stock_availability as get_stock_availability_from_template, + ) + """Modify item object and add stock details.""" item.in_stock = False warehouse = item.get("website_warehouse") @@ -274,11 +278,7 @@ class ProductQuery: else: item.in_stock = True elif warehouse: - # stock item and has warehouse - actual_qty = frappe.db.get_value( - "Bin", {"item_code": item.item_code, "warehouse": item.get("website_warehouse")}, "actual_qty" - ) - item.in_stock = bool(flt(actual_qty)) + item.in_stock = get_stock_availability_from_template(item.item_code, warehouse) def get_cart_items(self): customer = get_customer(silent=True) diff --git a/erpnext/e_commerce/shopping_cart/cart.py b/erpnext/e_commerce/shopping_cart/cart.py index 57746a234bc..030b439ae4b 100644 --- a/erpnext/e_commerce/shopping_cart/cart.py +++ b/erpnext/e_commerce/shopping_cart/cart.py @@ -111,8 +111,8 @@ def place_order(): item_stock = get_web_item_qty_in_stock(item.item_code, "website_warehouse") if not cint(item_stock.in_stock): throw(_("{0} Not in Stock").format(item.item_code)) - if item.qty > item_stock.stock_qty[0][0]: - throw(_("Only {0} in Stock for item {1}").format(item_stock.stock_qty[0][0], item.item_code)) + if item.qty > item_stock.stock_qty: + throw(_("Only {0} in Stock for item {1}").format(item_stock.stock_qty, item.item_code)) sales_order.flags.ignore_permissions = True sales_order.insert() @@ -150,6 +150,10 @@ def update_cart(item_code, qty, additional_notes=None, with_items=False): empty_card = True else: + warehouse = frappe.get_cached_value( + "Website Item", {"item_code": item_code}, "website_warehouse" + ) + quotation_items = quotation.get("items", {"item_code": item_code}) if not quotation_items: quotation.append( @@ -159,11 +163,13 @@ def update_cart(item_code, qty, additional_notes=None, with_items=False): "item_code": item_code, "qty": qty, "additional_notes": additional_notes, + "warehouse": warehouse, }, ) else: quotation_items[0].qty = qty quotation_items[0].additional_notes = additional_notes + quotation_items[0].warehouse = warehouse apply_cart_settings(quotation=quotation) @@ -322,6 +328,10 @@ def decorate_quotation_doc(doc): fields = fields[2:] d.update(frappe.db.get_value("Website Item", {"item_code": item_code}, fields, as_dict=True)) + website_warehouse = frappe.get_cached_value( + "Website Item", {"item_code": item_code}, "website_warehouse" + ) + d.warehouse = website_warehouse return doc diff --git a/erpnext/e_commerce/variant_selector/utils.py b/erpnext/e_commerce/variant_selector/utils.py index 4466c457436..88356f5e909 100644 --- a/erpnext/e_commerce/variant_selector/utils.py +++ b/erpnext/e_commerce/variant_selector/utils.py @@ -104,6 +104,8 @@ def get_attributes_and_values(item_code): @frappe.whitelist(allow_guest=True) def get_next_attribute_and_values(item_code, selected_attributes): + from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses + """Find the count of Items that match the selected attributes. Also, find the attribute values that are not applicable for further searching. If less than equal to 10 items are found, return item_codes of those items. @@ -168,7 +170,7 @@ def get_next_attribute_and_values(item_code, selected_attributes): product_info = None product_id = "" - website_warehouse = "" + warehouse = "" if exact_match or filtered_items: if exact_match and len(exact_match) == 1: product_id = exact_match[0] @@ -176,16 +178,19 @@ def get_next_attribute_and_values(item_code, selected_attributes): product_id = list(filtered_items)[0] if product_id: - website_warehouse = frappe.get_cached_value( + warehouse = frappe.get_cached_value( "Website Item", {"item_code": product_id}, "website_warehouse" ) available_qty = 0.0 - if website_warehouse: - available_qty = flt( - frappe.db.get_value( - "Bin", {"item_code": product_id, "warehouse": website_warehouse}, "actual_qty" - ) + if warehouse and frappe.get_cached_value("Warehouse", warehouse, "is_group") == 1: + warehouses = get_child_warehouses(warehouse) + else: + warehouses = [warehouse] if warehouse else [] + + for warehouse in warehouses: + available_qty += flt( + frappe.db.get_value("Bin", {"item_code": product_id, "warehouse": warehouse}, "actual_qty") ) return { diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 78080645050..9ea746cbd8b 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1039,6 +1039,59 @@ class TestProductionPlan(FrappeTestCase): self.assertEqual(after_qty, before_qty) + def test_resered_qty_for_production_plan_for_work_order(self): + from erpnext.stock.utils import get_or_make_bin + + bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC") + before_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + + pln = create_production_plan(item_code="Test Production Item 1") + + bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC") + after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + + self.assertEqual(after_qty - before_qty, 1) + + pln.make_work_order() + + work_orders = [] + for row in frappe.get_all("Work Order", filters={"production_plan": pln.name}, fields=["name"]): + wo_doc = frappe.get_doc("Work Order", row.name) + wo_doc.source_warehouse = "_Test Warehouse - _TC" + wo_doc.wip_warehouse = "_Test Warehouse 1 - _TC" + wo_doc.fg_warehouse = "_Test Warehouse - _TC" + for d in wo_doc.required_items: + d.source_warehouse = "_Test Warehouse - _TC" + make_stock_entry( + item_code=d.item_code, + qty=d.required_qty, + rate=100, + target="_Test Warehouse - _TC", + ) + + wo_doc.submit() + work_orders.append(wo_doc) + + bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC") + after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + + self.assertEqual(after_qty, before_qty) + + rm_work_order = None + for wo_doc in work_orders: + for d in wo_doc.required_items: + if d.item_code == "Raw Material Item 1": + rm_work_order = wo_doc + break + + if rm_work_order: + s = frappe.get_doc(make_se_from_wo(rm_work_order.name, "Material Transfer for Manufacture", 1)) + s.submit() + bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC") + after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + + self.assertEqual(after_qty, before_qty) + def test_resered_qty_for_production_plan_for_material_requests_with_multi_UOM(self): from erpnext.stock.utils import get_or_make_bin diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index c4b6846376f..070aaf00459 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1497,16 +1497,17 @@ def get_reserved_qty_for_production( wo = frappe.qb.DocType("Work Order") wo_item = frappe.qb.DocType("Work Order Item") + if check_production_plan: + qty_field = wo_item.required_qty + else: + qty_field = Case() + qty_field = qty_field.when(wo.skip_transfer == 0, wo_item.required_qty - wo_item.transferred_qty) + qty_field = qty_field.else_(wo_item.required_qty - wo_item.consumed_qty) + query = ( frappe.qb.from_(wo) .from_(wo_item) - .select( - Sum( - Case() - .when(wo.skip_transfer == 0, wo_item.required_qty - wo_item.transferred_qty) - .else_(wo_item.required_qty - wo_item.consumed_qty) - ) - ) + .select(Sum(qty_field)) .where( (wo_item.item_code == item_code) & (wo_item.parent == wo.name) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 19f8dab9a17..37e70967388 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -269,6 +269,7 @@ erpnext.patches.v13_0.show_hr_payroll_deprecation_warning erpnext.patches.v13_0.reset_corrupt_defaults erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair erpnext.patches.v14_0.update_reference_due_date_in_journal_entry +erpnext.patches.v14_0.france_depreciation_warning [post_model_sync] execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings') @@ -333,7 +334,7 @@ erpnext.patches.v14_0.update_company_in_ldc erpnext.patches.v14_0.set_packed_qty_in_draft_delivery_notes erpnext.patches.v14_0.cleanup_workspaces erpnext.patches.v14_0.enable_allow_existing_serial_no -erpnext.patches.v14_0.set_report_in_process_SOA +erpnext.patches.v14_0.set_report_in_process_SOA erpnext.patches.v14_0.create_accounting_dimensions_for_closing_balance erpnext.patches.v14_0.update_closing_balances #15-07-2023 execute:frappe.defaults.clear_default("fiscal_year") diff --git a/erpnext/patches/v14_0/france_depreciation_warning.py b/erpnext/patches/v14_0/france_depreciation_warning.py new file mode 100644 index 00000000000..5efbc5bd7fb --- /dev/null +++ b/erpnext/patches/v14_0/france_depreciation_warning.py @@ -0,0 +1,12 @@ +import click +import frappe + + +def execute(): + if "erpnext_france" in frappe.get_installed_apps(): + return + click.secho( + "Feature for region France will be remove in version-15 and moved to a separate app\n" + "Please install the app to continue using the regionnal France features: https://github.com/scopen-coop/erpnext_france.git", + fg="yellow", + ) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 082ba915207..5c351faae1a 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -67,6 +67,7 @@ class Project(Document): tmp_task_details.append(template_task_details) task = self.create_task_from_template(template_task_details) project_tasks.append(task) + self.dependency_mapping(tmp_task_details, project_tasks) def create_task_from_template(self, task_details): @@ -105,36 +106,28 @@ class Project(Document): def dependency_mapping(self, template_tasks, project_tasks): for project_task in project_tasks: - if project_task.get("template_task"): - template_task = frappe.get_doc("Task", project_task.template_task) - else: - template_task = list(filter(lambda x: x.subject == project_task.subject, template_tasks))[0] - template_task = frappe.get_doc("Task", template_task.name) + template_task = frappe.get_doc("Task", project_task.template_task) self.check_depends_on_value(template_task, project_task, project_tasks) self.check_for_parent_tasks(template_task, project_task, project_tasks) def check_depends_on_value(self, template_task, project_task, project_tasks): if template_task.get("depends_on") and not project_task.get("depends_on"): + project_template_map = {pt.template_task: pt for pt in project_tasks} + for child_task in template_task.get("depends_on"): - child_task_subject = frappe.db.get_value("Task", child_task.task, "subject") - corresponding_project_task = list( - filter(lambda x: x.subject == child_task_subject, project_tasks) - ) - if len(corresponding_project_task): + if project_template_map and project_template_map.get(child_task.task): project_task.reload() # reload, as it might have been updated in the previous iteration - project_task.append("depends_on", {"task": corresponding_project_task[0].name}) + project_task.append("depends_on", {"task": project_template_map.get(child_task.task).name}) project_task.save() def check_for_parent_tasks(self, template_task, project_task, project_tasks): if template_task.get("parent_task") and not project_task.get("parent_task"): - parent_task_subject = frappe.db.get_value("Task", template_task.get("parent_task"), "subject") - corresponding_project_task = list( - filter(lambda x: x.subject == parent_task_subject, project_tasks) - ) - if len(corresponding_project_task): - project_task.parent_task = corresponding_project_task[0].name - project_task.save() + for pt in project_tasks: + if pt.template_task == template_task.parent_task: + project_task.parent_task = pt.name + project_task.save() + break def is_row_updated(self, row, existing_task_data, fields): if self.get("__islocal") or not existing_task_data: diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index ba6e247d2e4..115827a60cb 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -138,7 +138,9 @@ class DeliveryNote(SellingController): self.validate_uom_is_integer("stock_uom", "stock_qty") self.validate_uom_is_integer("uom", "qty") self.validate_with_previous_doc() - self.validate_duplicate_serial_nos() + + if self.get("_action") == "submit": + self.validate_duplicate_serial_nos() from erpnext.stock.doctype.packed_item.packed_item import make_packing_list diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 2acfd84d944..093e16c1cf8 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1234,14 +1234,10 @@ class TestDeliveryNote(FrappeTestCase): ) dn.items[0].serial_no = "\n".join(serial_nos[:2]) dn.append("items", dn.items[0].as_dict()) + dn.save() # Test - 1: ValidationError should be raised - self.assertRaises(frappe.ValidationError, dn.save) - - # Step - 4: Submit Delivery Note with unique Serial Nos - dn.items[1].serial_no = "\n".join(serial_nos[2:]) - dn.save() - dn.submit() + self.assertRaises(frappe.ValidationError, dn.submit) def tearDown(self): frappe.db.rollback() diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index b096b024f44..2632501b718 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -218,7 +218,8 @@ frappe.ui.form.on('Material Request', { plc_conversion_rate: 1, rate: item.rate, uom: item.uom, - conversion_factor: item.conversion_factor + conversion_factor: item.conversion_factor, + project: item.project, }, overwrite_warehouse: overwrite_warehouse }, diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index f7eb859f830..13f484b1c85 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1392,6 +1392,9 @@ def get_default_bom(item_code=None): @frappe.whitelist() def get_valuation_rate(item_code, company, warehouse=None): + if frappe.get_cached_value("Warehouse", warehouse, "is_group"): + return {"valuation_rate": 0.0} + item = get_item_defaults(item_code, company) item_group = get_item_group_defaults(item_code, company) brand = get_brand_defaults(item_code, company) diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js index 74849058c3c..94e0b2dce3b 100644 --- a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js @@ -2,7 +2,7 @@ // For license information, please see license.txt /* eslint-disable */ -const DIFFERNCE_FIELD_NAMES = [ +const DIFFERENCE_FIELD_NAMES = [ 'difference_in_qty', 'fifo_qty_diff', 'fifo_value_diff', @@ -37,7 +37,7 @@ frappe.query_reports['Stock Ledger Invariant Check'] = { formatter (value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); - if (DIFFERNCE_FIELD_NAMES.includes(column.fieldname) && Math.abs(data[column.fieldname]) > 0.001) { + if (DIFFERENCE_FIELD_NAMES.includes(column.fieldname) && Math.abs(data[column.fieldname]) > 0.001) { value = '' + value + ''; } return value; diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py index e11c9bb7891..ca15afe444d 100644 --- a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py @@ -186,7 +186,7 @@ def get_columns(): { "fieldname": "fifo_queue_qty", "fieldtype": "Float", - "label": _("(C) Total qty in queue"), + "label": _("(C) Total Qty in Queue"), }, { "fieldname": "fifo_qty_diff", @@ -211,52 +211,52 @@ def get_columns(): { "fieldname": "stock_value_difference", "fieldtype": "Float", - "label": _("(F) Stock Value Difference"), + "label": _("(F) Change in Stock Value"), }, { "fieldname": "stock_value_from_diff", "fieldtype": "Float", - "label": _("Balance Stock Value using (F)"), + "label": _("(G) Sum of Change in Stock Value"), }, { "fieldname": "diff_value_diff", "fieldtype": "Float", - "label": _("K - D"), + "label": _("G - D"), }, { "fieldname": "fifo_stock_diff", "fieldtype": "Float", - "label": _("(G) Stock Value difference (FIFO queue)"), + "label": _("(H) Change in Stock Value (FIFO Queue)"), }, { "fieldname": "fifo_difference_diff", "fieldtype": "Float", - "label": _("F - G"), + "label": _("H - F"), }, { "fieldname": "valuation_rate", "fieldtype": "Float", - "label": _("(H) Valuation Rate"), + "label": _("(I) Valuation Rate"), }, { "fieldname": "fifo_valuation_rate", "fieldtype": "Float", - "label": _("(I) Valuation Rate as per FIFO"), + "label": _("(J) Valuation Rate as per FIFO"), }, { "fieldname": "fifo_valuation_diff", "fieldtype": "Float", - "label": _("H - I"), + "label": _("I - J"), }, { "fieldname": "balance_value_by_qty", "fieldtype": "Float", - "label": _("(J) Valuation = Value (D) ÷ Qty (A)"), + "label": _("(K) Valuation = Value (D) ÷ Qty (A)"), }, { "fieldname": "valuation_diff", "fieldtype": "Float", - "label": _("H - J"), + "label": _("I - K"), }, ] diff --git a/erpnext/stock/report/stock_ledger_variance/__init__.py b/erpnext/stock/report/stock_ledger_variance/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js new file mode 100644 index 00000000000..b1e4a74571e --- /dev/null +++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js @@ -0,0 +1,101 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +const DIFFERENCE_FIELD_NAMES = [ + "difference_in_qty", + "fifo_qty_diff", + "fifo_value_diff", + "fifo_valuation_diff", + "valuation_diff", + "fifo_difference_diff", + "diff_value_diff" +]; + +frappe.query_reports["Stock Ledger Variance"] = { + "filters": [ + { + "fieldname": "item_code", + "fieldtype": "Link", + "label": "Item", + "options": "Item", + get_query: function() { + return { + filters: {is_stock_item: 1, has_serial_no: 0} + } + } + }, + { + "fieldname": "warehouse", + "fieldtype": "Link", + "label": "Warehouse", + "options": "Warehouse", + get_query: function() { + return { + filters: {is_group: 0, disabled: 0} + } + } + }, + { + "fieldname": "difference_in", + "fieldtype": "Select", + "label": "Difference In", + "options": [ + "", + "Qty", + "Value", + "Valuation", + ], + }, + { + "fieldname": "include_disabled", + "fieldtype": "Check", + "label": "Include Disabled", + } + ], + + formatter (value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + + if (DIFFERENCE_FIELD_NAMES.includes(column.fieldname) && Math.abs(data[column.fieldname]) > 0.001) { + value = "" + value + ""; + } + + return value; + }, + + get_datatable_options(options) { + return Object.assign(options, { + checkboxColumn: true, + }); + }, + + onload(report) { + report.page.add_inner_button(__('Create Reposting Entries'), () => { + let message = ` +
+ Reposting Entries will change the value of + accounts Stock In Hand, and Stock Expenses + in the Trial Balance report and will also change + the Balance Value in the Stock Balance report. +
+Are you sure you want to create Reposting Entries?
+