From 41b3a59aae49646f0822b92eec738098cc89b69f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 27 Mar 2024 13:29:22 +0530 Subject: [PATCH 01/33] fix: rate changed on changing of the qty (cherry picked from commit cd36a1051f43a35ab4d45ac75db2d3dadd814008) --- erpnext/public/js/controllers/transaction.js | 21 ++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 40c67927e18..53edceee627 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1239,19 +1239,20 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe this.frm.fields_dict.items.grid.toggle_enable("conversion_factor", ((item.uom != item.stock_uom) && !frappe.meta.get_docfield(cur_frm.fields_dict.items.grid.doctype, "conversion_factor").read_only)? true: false); } - } qty(doc, cdt, cdn) { - let item = frappe.get_doc(cdt, cdn); - // item.pricing_rules = '' - frappe.run_serially([ - () => this.remove_pricing_rule_for_item(item), - () => this.conversion_factor(doc, cdt, cdn, true), - () => this.apply_price_list(item, true), //reapply price list before applying pricing rule - () => this.calculate_stock_uom_rate(doc, cdt, cdn), - () => this.apply_pricing_rule(item, true) - ]); + if (!this.frm.doc.__onload?.load_after_mapping) { + let item = frappe.get_doc(cdt, cdn); + // item.pricing_rules = '' + frappe.run_serially([ + () => this.remove_pricing_rule_for_item(item), + () => this.conversion_factor(doc, cdt, cdn, true), + () => this.apply_price_list(item, true), //reapply price list before applying pricing rule + () => this.calculate_stock_uom_rate(doc, cdt, cdn), + () => this.apply_pricing_rule(item, true) + ]); + } } stock_qty(doc, cdt, cdn) { From a3a6bf6ae1776669f4b87c814e28cdb48cd29dc4 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 27 Mar 2024 15:07:44 +0530 Subject: [PATCH 02/33] fix: Priority not copied from project template (cherry picked from commit 33fd7b8a1f125e0387a7a7851a149f91297b9958) --- erpnext/projects/doctype/project/project.py | 1 + erpnext/projects/doctype/project/test_project.py | 9 +++++++-- erpnext/projects/doctype/task/test_task.py | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 656550aaafc..abfb4062af3 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -145,6 +145,7 @@ class Project(Document): is_group=task_details.is_group, color=task_details.color, template_task=task_details.name, + priority=task_details.priority, ) ).insert() diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index e49fecd1f47..b8340ffe193 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -23,7 +23,11 @@ class TestProject(FrappeTestCase): task1 = task_exists("Test Template Task with No Parent and Dependency") if not task1: task1 = create_task( - subject="Test Template Task with No Parent and Dependency", is_template=1, begin=5, duration=3 + subject="Test Template Task with No Parent and Dependency", + is_template=1, + begin=5, + duration=3, + priority="High", ) template = make_project_template( @@ -32,11 +36,12 @@ class TestProject(FrappeTestCase): project = get_project(project_name, template) tasks = frappe.get_all( "Task", - ["subject", "exp_end_date", "depends_on_tasks"], + ["subject", "exp_end_date", "depends_on_tasks", "priority"], dict(project=project.name), order_by="creation asc", ) + self.assertEqual(tasks[0].priority, "High") self.assertEqual(tasks[0].subject, "Test Template Task with No Parent and Dependency") self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 5, 3)) self.assertEqual(len(tasks), 1) diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py index c0333f8f590..ea7d6edcdf9 100644 --- a/erpnext/projects/doctype/task/test_task.py +++ b/erpnext/projects/doctype/task/test_task.py @@ -122,6 +122,7 @@ def create_task( begin=0, duration=0, save=True, + priority=None, ): if not frappe.db.exists("Task", subject): task = frappe.new_doc("Task") @@ -139,6 +140,7 @@ def create_task( task.duration = duration task.is_group = is_group task.parent_task = parent_task + task.priority = priority if save: task.save() else: From 3761e74436727fc54376669784940a470b16c351 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 27 Mar 2024 20:05:27 +0530 Subject: [PATCH 03/33] fix: do not fetch received items in purchase receipt (cherry picked from commit 5bff4349020f3c54fdd7e30a294f1042b714a6fc) --- erpnext/controllers/stock_controller.py | 2 +- erpnext/stock/doctype/delivery_note/delivery_note.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 1104204d856..80f5108944e 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -913,7 +913,7 @@ class StockController(AccountsController): self.validate_multi_currency() self.validate_packed_items() - if self.get("is_internal_supplier"): + if self.get("is_internal_supplier") and self.docstatus == 1: self.validate_internal_transfer_qty() else: self.validate_internal_transfer_warehouse() diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index fb19d0ec20b..4b8c1e9d2c6 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -1287,6 +1287,9 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): for tax in get_taxes_and_charges(master_doctype, target.get("taxes_and_charges")): target.append("taxes", tax) + if not target.get("items"): + frappe.throw(_("All items have already been received")) + def update_details(source_doc, target_doc, source_parent): target_doc.inter_company_invoice_reference = source_doc.name if target_doc.doctype == "Purchase Receipt": @@ -1342,6 +1345,10 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): shipping_address_name=target_doc.shipping_address_name, ) + def update_item(source, target, source_parent): + if source_parent.doctype == "Delivery Note" and source.received_qty: + target.qty = flt(source.qty) + flt(source.returned_qty) - flt(source.received_qty) + doclist = get_mapped_doc( doctype, source_name, @@ -1363,6 +1370,8 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): "Material_request_item": "material_request_item", }, "field_no_map": ["warehouse"], + "condition": lambda item: item.received_qty < item.qty + item.returned_qty, + "postprocess": update_item, }, }, target_doc, From b089eda8bfbd312a6274107853d32105fc4614f4 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 27 Mar 2024 16:17:22 +0530 Subject: [PATCH 04/33] fix: enable list view for the issues on the portal (cherry picked from commit bddc1e397494cefd77311436a7c01fab778c7c57) --- erpnext/support/web_form/issues/issues.json | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/erpnext/support/web_form/issues/issues.json b/erpnext/support/web_form/issues/issues.json index 1df9fb79696..f3834c73a7c 100644 --- a/erpnext/support/web_form/issues/issues.json +++ b/erpnext/support/web_form/issues/issues.json @@ -1,14 +1,14 @@ { - "accept_payment": 0, "allow_comments": 1, "allow_delete": 1, "allow_edit": 1, "allow_incomplete": 0, "allow_multiple": 1, "allow_print": 0, - "amount": 0.0, - "amount_based_on_field": 0, + "anonymous": 0, + "apply_document_permissions": 0, "breadcrumbs": "[{\"label\":_(\"Issues\"), \"route\":\"issues\"}]", + "condition_json": "[]", "creation": "2016-06-24 15:50:33.186483", "doc_type": "Issue", "docstatus": 0, @@ -16,20 +16,19 @@ "idx": 0, "introduction_text": "", "is_standard": 1, + "list_columns": [], "login_required": 1, "max_attachment_size": 0, - "modified": "2020-05-19 13:01:10.729088", + "modified": "2024-03-27 16:16:03.621730", "modified_by": "Administrator", "module": "Support", "name": "issues", "owner": "Administrator", "published": 1, "route": "issues", - "route_to_success_link": 0, "show_attachments": 0, - "show_in_grid": 0, + "show_list": 1, "show_sidebar": 1, - "sidebar_items": [], "success_message": "", "success_url": "/issues", "title": "Issue", From 5fb642831c67f3cead934913c6aa135f37c86e24 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Thu, 28 Mar 2024 10:30:13 +0530 Subject: [PATCH 05/33] fix: markdown to text editor set for supplier quotation (cherry picked from commit 9828d34b19e575697b4d5bbc624689535160aa69) # Conflicts: # erpnext/buying/doctype/supplier_quotation/supplier_quotation.json --- .../doctype/supplier_quotation/supplier_quotation.json | 6 +++++- .../buying/doctype/supplier_quotation/supplier_quotation.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 9f89078de67..c898d91405e 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -462,7 +462,7 @@ }, { "fieldname": "other_charges_calculation", - "fieldtype": "Markdown Editor", + "fieldtype": "Text Editor", "label": "Taxes and Charges Calculation", "no_copy": 1, "oldfieldtype": "HTML", @@ -928,7 +928,11 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], +<<<<<<< HEAD "modified": "2024-03-20 16:03:59.069145", +======= + "modified": "2024-03-28 10:20:30.231915", +>>>>>>> 9828d34b19 (fix: markdown to text editor set for supplier quotation) "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py index b716f7f0427..4e67eee2da6 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py @@ -71,7 +71,7 @@ class SupplierQuotation(BuyingController): naming_series: DF.Literal["PUR-SQTN-.YYYY.-"] net_total: DF.Currency opportunity: DF.Link | None - other_charges_calculation: DF.MarkdownEditor | None + other_charges_calculation: DF.TextEditor | None plc_conversion_rate: DF.Float price_list_currency: DF.Link | None pricing_rules: DF.Table[PricingRuleDetail] From 1fe80c2d02766afff9e30d0451bee03d5a23028c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 20 Oct 2023 17:16:54 +0530 Subject: [PATCH 06/33] refactor: gain_loss posting date fields in the allocation table (cherry picked from commit 55dbcee36a2acf4aa41c66147c263a85ef606f81) # Conflicts: # erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json --- .../payment_reconciliation_allocation.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json index a553b982d14..6aaa58aedf7 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json +++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json @@ -158,6 +158,7 @@ "fieldname": "gain_loss_posting_date", "fieldtype": "Date", "label": "Difference Posting Date" +<<<<<<< HEAD }, { "fieldname": "accounting_dimensions_section", @@ -167,12 +168,18 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" +======= +>>>>>>> 55dbcee36a (refactor: gain_loss posting date fields in the allocation table) } ], "is_virtual": 1, "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2023-12-14 13:38:26.104150", +======= + "modified": "2023-10-23 10:44:56.066303", +>>>>>>> 55dbcee36a (refactor: gain_loss posting date fields in the allocation table) "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation Allocation", From 39571d828edbfe50a689a58006e71f313fd5efdd Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 22 Oct 2023 08:59:52 +0530 Subject: [PATCH 07/33] refactor: introduce fields in popup (cherry picked from commit 5323bb7beeb6526d16bcb19fc2f3acd3a95927e6) # Conflicts: # erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js --- .../payment_reconciliation.js | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index e0d142288e8..a5d080760b2 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -254,6 +254,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo this.data = []; const dialog = new frappe.ui.Dialog({ title: __("Select Difference Account"), + size: 'extra-large', fields: [ { fieldname: "allocation", @@ -265,6 +266,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo get_data: () => { return this.data; }, +<<<<<<< HEAD fields: [ { fieldtype: "Data", @@ -303,6 +305,48 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo read_only: 1, }, ], +======= + fields: [{ + fieldtype:'Data', + fieldname:"docname", + in_list_view: 1, + hidden: 1 + }, { + fieldtype:'Data', + fieldname:"reference_name", + label: __("Voucher No"), + in_list_view: 1, + read_only: 1 + }, { + fieldtype:'Date', + fieldname:"gain_loss_posting_date", + label: __("Posting Date"), + in_list_view: 1, + reqd: 1, + }, { + + fieldtype:'Link', + options: 'Account', + in_list_view: 1, + label: __("Difference Account"), + fieldname: 'difference_account', + reqd: 1, + get_query: () => { + return { + filters: { + company: this.frm.doc.company, + is_group: 0 + } + } + } + }, { + fieldtype:'Currency', + in_list_view: 1, + label: __("Difference Amount"), + fieldname: 'difference_amount', + read_only: 1 + }] +>>>>>>> 5323bb7bee (refactor: introduce fields in popup) }, { fieldtype: "HTML", @@ -312,6 +356,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo primary_action: () => { const args = dialog.get_values()["allocation"]; +<<<<<<< HEAD args.forEach((d) => { frappe.model.set_value( "Payment Reconciliation Allocation", @@ -319,6 +364,12 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo "difference_account", d.difference_account ); +======= + args.forEach(d => { + frappe.model.set_value("Payment Reconciliation Allocation", d.docname, + "difference_account", d.difference_account); + +>>>>>>> 5323bb7bee (refactor: introduce fields in popup) }); this.reconcile_payment_entries(); @@ -330,10 +381,18 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo this.frm.doc.allocation.forEach((d) => { if (d.difference_amount) { dialog.fields_dict.allocation.df.data.push({ +<<<<<<< HEAD docname: d.name, reference_name: d.reference_name, difference_amount: d.difference_amount, difference_account: d.difference_account, +======= + 'docname': d.name, + 'reference_name': d.reference_name, + 'difference_amount': d.difference_amount, + 'difference_account': d.difference_account, + 'gain_loss_posting_date': d.gain_loss_posting_date +>>>>>>> 5323bb7bee (refactor: introduce fields in popup) }); } }); From 125722ae4150d1d443b402119312b797c765c712 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 22 Oct 2023 20:26:45 +0530 Subject: [PATCH 08/33] refactor: pass gain loss posting date to controller (cherry picked from commit 7e600a6494d7f07c6fd2b8f1cc71857801a2573c) # Conflicts: # erpnext/accounts/utils.py --- .../payment_reconciliation/payment_reconciliation.js | 2 ++ .../payment_reconciliation/payment_reconciliation.py | 2 ++ .../payment_reconciliation_allocation.json | 1 + erpnext/accounts/utils.py | 4 ++++ erpnext/controllers/accounts_controller.py | 6 ++++-- 5 files changed, 13 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index a5d080760b2..042a6123ebd 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -368,6 +368,8 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo args.forEach(d => { frappe.model.set_value("Payment Reconciliation Allocation", d.docname, "difference_account", d.difference_account); + frappe.model.set_value("Payment Reconciliation Allocation", d.docname, + "gain_loss_posting_date", d.gain_loss_posting_date); >>>>>>> 5323bb7bee (refactor: introduce fields in popup) }); diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 2c4952a0c66..dcb1a16dba4 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -446,6 +446,7 @@ class PaymentReconciliation(Document): res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"]) res.difference_account = default_exchange_gain_loss_account res.exchange_rate = inv.get("exchange_rate") + res.update({"gain_loss_posting_date": pay.get("posting_date")}) if pay.get("amount") == 0: entries.append(res) @@ -562,6 +563,7 @@ class PaymentReconciliation(Document): "allocated_amount": flt(row.get("allocated_amount")), "difference_amount": flt(row.get("difference_amount")), "difference_account": row.get("difference_account"), + "difference_posting_date": row.get("gain_loss_posting_date"), "cost_center": row.get("cost_center"), } ) diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json index 6aaa58aedf7..c40d0ce29d3 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json +++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json @@ -19,6 +19,7 @@ "is_advance", "section_break_5", "difference_amount", + "gain_loss_posting_date", "column_break_7", "difference_account", "exchange_rate", diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 0e6c041d24d..c2653237e5a 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -726,7 +726,11 @@ def update_reference_in_payment_entry( payment_entry.set_missing_ref_details(ref_exchange_rate=d.exchange_rate or None) payment_entry.set_amounts() payment_entry.make_exchange_gain_loss_journal( +<<<<<<< HEAD frappe._dict({"difference_posting_date": d.difference_posting_date}), dimensions_dict +======= + frappe._dict({"difference_posting_date": d.difference_posting_date}) +>>>>>>> 7e600a6494 (refactor: pass gain loss posting date to controller) ) if not do_not_save: diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index fe4500a41e5..185ef4c22f0 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1360,7 +1360,9 @@ class AccountsController(TransactionBase): self.name, arg.get("referenced_row"), ): - posting_date = frappe.db.get_value(arg.voucher_type, arg.voucher_no, "posting_date") + posting_date = arg.get("difference_posting_date") or frappe.db.get_value( + arg.voucher_type, arg.voucher_no, "posting_date" + ) je = create_gain_loss_journal( self.company, posting_date, @@ -1444,7 +1446,7 @@ class AccountsController(TransactionBase): je = create_gain_loss_journal( self.company, - self.posting_date, + args.get("difference_posting_date") if args else self.posting_date, self.party_type, self.party, party_account, From fd33437d7a9ccb9ac023662654608a979376d4cd Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 23 Oct 2023 12:32:10 +0530 Subject: [PATCH 09/33] test: varying posting date for gain loss journal (cherry picked from commit 514d5434a3ae24e2c7839fbd76a115d6c0841513) --- .../tests/test_accounts_controller.py | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index 65b2696e79a..2170628361d 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -7,7 +7,7 @@ import frappe from frappe import qb from frappe.query_builder.functions import Sum from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import add_days, flt, nowdate +from frappe.utils import add_days, flt, getdate, nowdate from erpnext import get_default_cost_center from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry @@ -616,6 +616,73 @@ class TestAccountsController(FrappeTestCase): self.assertEqual(exc_je_for_si, []) self.assertEqual(exc_je_for_pe, []) + def test_15_gain_loss_on_different_posting_date(self): + # Invoice in Foreign Currency + si = self.create_sales_invoice( + posting_date=add_days(nowdate(), -2), qty=2, conversion_rate=80, rate=1 + ) + # Payment + pe = ( + self.create_payment_entry(posting_date=add_days(nowdate(), -1), amount=2, source_exc_rate=75) + .save() + .submit() + ) + + # There should be outstanding in both currencies + si.reload() + self.assertEqual(si.outstanding_amount, 2) + self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0) + + # Reconcile the remaining amount + pr = frappe.get_doc("Payment Reconciliation") + pr.company = self.company + pr.party_type = "Customer" + pr.party = self.customer + pr.receivable_payable_account = self.debit_usd + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + invoices = [x.as_dict() for x in pr.invoices] + payments = [x.as_dict() for x in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.allocation[0].gain_loss_posting_date = add_days(nowdate(), 1) + pr.reconcile() + + # Exchange Gain/Loss Journal should've been created. + exc_je_for_si = self.get_journals_for(si.doctype, si.name) + exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name) + self.assertNotEqual(exc_je_for_si, []) + self.assertEqual(len(exc_je_for_si), 1) + self.assertEqual(len(exc_je_for_pe), 1) + self.assertEqual(exc_je_for_si[0], exc_je_for_pe[0]) + + self.assertEqual( + frappe.db.get_value("Journal Entry", exc_je_for_si[0].parent, "posting_date"), + getdate(add_days(nowdate(), 1)), + ) + + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 0) + + # There should be no outstanding + si.reload() + self.assertEqual(si.outstanding_amount, 0) + self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0) + + # Cancel Payment + pe.reload() + pe.cancel() + + si.reload() + self.assertEqual(si.outstanding_amount, 2) + self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0) + + # Exchange Gain/Loss Journal should've been cancelled + exc_je_for_si = self.get_journals_for(si.doctype, si.name) + exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name) + self.assertEqual(exc_je_for_si, []) + self.assertEqual(exc_je_for_pe, []) + def test_20_journal_against_sales_invoice(self): # Invoice in Foreign Currency si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1) From 565bfa16f2766aa4cbbe194dc0669ec16b5e9109 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 28 Mar 2024 17:18:13 +0530 Subject: [PATCH 10/33] chore: resolve conflicts --- .../payment_reconciliation.js | 76 ++++--------------- .../payment_reconciliation_allocation.json | 7 -- erpnext/accounts/utils.py | 4 - 3 files changed, 15 insertions(+), 72 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 042a6123ebd..cc99fe7b583 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -254,7 +254,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo this.data = []; const dialog = new frappe.ui.Dialog({ title: __("Select Difference Account"), - size: 'extra-large', + size: "extra-large", fields: [ { fieldname: "allocation", @@ -266,7 +266,6 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo get_data: () => { return this.data; }, -<<<<<<< HEAD fields: [ { fieldtype: "Data", @@ -281,6 +280,13 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo in_list_view: 1, read_only: 1, }, + { + fieldtype: "Date", + fieldname: "gain_loss_posting_date", + label: __("Posting Date"), + in_list_view: 1, + reqd: 1, + }, { fieldtype: "Link", options: "Account", @@ -305,48 +311,6 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo read_only: 1, }, ], -======= - fields: [{ - fieldtype:'Data', - fieldname:"docname", - in_list_view: 1, - hidden: 1 - }, { - fieldtype:'Data', - fieldname:"reference_name", - label: __("Voucher No"), - in_list_view: 1, - read_only: 1 - }, { - fieldtype:'Date', - fieldname:"gain_loss_posting_date", - label: __("Posting Date"), - in_list_view: 1, - reqd: 1, - }, { - - fieldtype:'Link', - options: 'Account', - in_list_view: 1, - label: __("Difference Account"), - fieldname: 'difference_account', - reqd: 1, - get_query: () => { - return { - filters: { - company: this.frm.doc.company, - is_group: 0 - } - } - } - }, { - fieldtype:'Currency', - in_list_view: 1, - label: __("Difference Amount"), - fieldname: 'difference_amount', - read_only: 1 - }] ->>>>>>> 5323bb7bee (refactor: introduce fields in popup) }, { fieldtype: "HTML", @@ -356,7 +320,6 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo primary_action: () => { const args = dialog.get_values()["allocation"]; -<<<<<<< HEAD args.forEach((d) => { frappe.model.set_value( "Payment Reconciliation Allocation", @@ -364,14 +327,12 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo "difference_account", d.difference_account ); -======= - args.forEach(d => { - frappe.model.set_value("Payment Reconciliation Allocation", d.docname, - "difference_account", d.difference_account); - frappe.model.set_value("Payment Reconciliation Allocation", d.docname, - "gain_loss_posting_date", d.gain_loss_posting_date); - ->>>>>>> 5323bb7bee (refactor: introduce fields in popup) + frappe.model.set_value( + "Payment Reconciliation Allocation", + d.docname, + "gain_loss_posting_date", + d.gain_loss_posting_date + ); }); this.reconcile_payment_entries(); @@ -383,18 +344,11 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo this.frm.doc.allocation.forEach((d) => { if (d.difference_amount) { dialog.fields_dict.allocation.df.data.push({ -<<<<<<< HEAD docname: d.name, reference_name: d.reference_name, difference_amount: d.difference_amount, difference_account: d.difference_account, -======= - 'docname': d.name, - 'reference_name': d.reference_name, - 'difference_amount': d.difference_amount, - 'difference_account': d.difference_account, - 'gain_loss_posting_date': d.gain_loss_posting_date ->>>>>>> 5323bb7bee (refactor: introduce fields in popup) + gain_loss_posting_date: d.gain_loss_posting_date, }); } }); diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json index c40d0ce29d3..3f85b213500 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json +++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json @@ -159,7 +159,6 @@ "fieldname": "gain_loss_posting_date", "fieldtype": "Date", "label": "Difference Posting Date" -<<<<<<< HEAD }, { "fieldname": "accounting_dimensions_section", @@ -169,18 +168,12 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" -======= ->>>>>>> 55dbcee36a (refactor: gain_loss posting date fields in the allocation table) } ], "is_virtual": 1, "istable": 1, "links": [], -<<<<<<< HEAD "modified": "2023-12-14 13:38:26.104150", -======= - "modified": "2023-10-23 10:44:56.066303", ->>>>>>> 55dbcee36a (refactor: gain_loss posting date fields in the allocation table) "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation Allocation", diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index c2653237e5a..0e6c041d24d 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -726,11 +726,7 @@ def update_reference_in_payment_entry( payment_entry.set_missing_ref_details(ref_exchange_rate=d.exchange_rate or None) payment_entry.set_amounts() payment_entry.make_exchange_gain_loss_journal( -<<<<<<< HEAD frappe._dict({"difference_posting_date": d.difference_posting_date}), dimensions_dict -======= - frappe._dict({"difference_posting_date": d.difference_posting_date}) ->>>>>>> 7e600a6494 (refactor: pass gain loss posting date to controller) ) if not do_not_save: From f50a2586b6cd785bb340427e01353b2c09630eab Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 28 Mar 2024 13:28:59 +0530 Subject: [PATCH 11/33] refactor: use sql to clear comments (cherry picked from commit 1f46c1530e68263ca2b5f9f86f59bb06063cd05e) --- .../transaction_deletion_record.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 00fad5f0fa6..5eaabe52b6b 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -418,17 +418,11 @@ class TransactionDeletionRecord(Document): frappe.delete_doc("Communication", batch, ignore_permissions=True) def delete_comments(self, doctype, reference_doc_names): - comments = frappe.get_all( - "Comment", - filters={"reference_doctype": doctype, "reference_name": ["in", reference_doc_names]}, - ) - comment_names = [c.name for c in comments] - - if not comment_names: - return - - for batch in create_batch(comment_names, self.batch_size): - frappe.delete_doc("Comment", batch, ignore_permissions=True) + if reference_doc_names: + comment = qb.DocType("Comment") + qb.from_(comment).delete().where( + (comment.reference_doctype == doctype) & (comment.reference_name.isin(reference_doc_names)) + ).run() def unlink_attachments(self, doctype, reference_doc_names): files = frappe.get_all( From 7a21997701b37ad83540289c22e518e210ab72b9 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 28 Mar 2024 14:55:31 +0530 Subject: [PATCH 12/33] fix: sales / prchase return validation issue (cherry picked from commit 59dc4a96e18357ee52f0f25137e9c824971cc029) --- .../delivery_note/test_delivery_note.py | 92 +++++++++++++++++++ .../serial_and_batch_bundle.py | 60 ++++++++++-- 2 files changed, 142 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 24544070d3b..c4852d2cf15 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -461,6 +461,98 @@ class TestDeliveryNote(FrappeTestCase): self.assertEqual(return_dn.items[0].incoming_rate, 150) + def test_sales_return_against_serial_batch_bundle(self): + frappe.db.set_single_value( + "Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle", 1 + ) + + batch_item = make_item( + "Test Sales Return Against Batch Item", + properties={ + "has_batch_no": 1, + "is_stock_item": 1, + "create_new_batch": 1, + "batch_number_series": "BATCH-TSRABII.#####", + }, + ).name + + serial_item = make_item( + "Test Sales Return Against Serial NO Item", + properties={ + "has_serial_no": 1, + "is_stock_item": 1, + "serial_no_series": "SN-TSRABII.#####", + }, + ).name + + make_stock_entry(item_code=batch_item, target="_Test Warehouse - _TC", qty=5, basic_rate=100) + make_stock_entry(item_code=serial_item, target="_Test Warehouse - _TC", qty=5, basic_rate=100) + + dn = create_delivery_note( + item_code=batch_item, + qty=5, + rate=500, + warehouse="_Test Warehouse - _TC", + expense_account="Cost of Goods Sold - _TC", + cost_center="Main - _TC", + use_serial_batch_fields=0, + do_not_submit=1, + ) + + dn.append( + "items", + { + "item_code": serial_item, + "qty": 5, + "rate": 500, + "warehouse": "_Test Warehouse - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "Main - _TC", + "use_serial_batch_fields": 0, + }, + ) + + dn.save() + for row in dn.items: + self.assertFalse(row.use_serial_batch_fields) + + dn.submit() + dn.reload() + for row in dn.items: + self.assertTrue(row.serial_and_batch_bundle) + self.assertFalse(row.use_serial_batch_fields) + self.assertFalse(row.serial_no) + self.assertFalse(row.batch_no) + + from erpnext.controllers.sales_and_purchase_return import make_return_doc + + return_dn = make_return_doc(dn.doctype, dn.name) + for row in return_dn.items: + row.qty = -2 + row.use_serial_batch_fields = 0 + return_dn.save().submit() + + for row in return_dn.items: + total_qty = frappe.db.get_value( + "Serial and Batch Bundle", row.serial_and_batch_bundle, "total_qty" + ) + + self.assertEqual(total_qty, 2) + + doc = frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle) + if doc.has_serial_no: + self.assertEqual(len(doc.entries), 2) + + for entry in doc.entries: + if doc.has_batch_no: + self.assertEqual(entry.qty, 2) + else: + self.assertEqual(entry.qty, 1) + + frappe.db.set_single_value( + "Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle", 0 + ) + def test_return_single_item_from_bundled_items(self): company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company") diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 58971e8f19d..11018c27f67 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -384,6 +384,9 @@ class SerialandBatchBundle(Document): if self.docstatus == 0: self.set_incoming_rate(save=True, row=row) + if self.docstatus == 0 and parent.get("is_return") and parent.is_new(): + self.reset_qty(row, qty_field=qty_field) + self.calculate_qty_and_amount(save=True) self.validate_quantity(row, qty_field=qty_field) self.set_warranty_expiry_date() @@ -417,7 +420,11 @@ class SerialandBatchBundle(Document): if not (self.voucher_type and self.voucher_no): return - if self.voucher_no and not frappe.db.exists(self.voucher_type, self.voucher_no): + if ( + self.docstatus == 1 + and self.voucher_no + and not frappe.db.exists(self.voucher_type, self.voucher_no) + ): self.throw_error_message(f"The {self.voucher_type} # {self.voucher_no} does not exist") if self.flags.ignore_voucher_validation: @@ -481,24 +488,57 @@ class SerialandBatchBundle(Document): frappe.throw(_(msg), title=_(title), exc=SerialNoExistsInFutureTransactionError) + def reset_qty(self, row, qty_field=None): + qty_field = self.get_qty_field(row, qty_field=qty_field) + qty = abs(row.get(qty_field)) + + idx = None + while qty > 0: + for d in self.entries: + row_qty = abs(d.qty) + if row_qty >= qty: + d.db_set("qty", qty if self.type_of_transaction == "Inward" else qty * -1) + qty = 0 + idx = d.idx + break + else: + qty -= row_qty + idx = d.idx + + if idx and len(self.entries) > idx: + remove_rows = [] + for d in self.entries: + if d.idx > idx: + remove_rows.append(d) + + for d in remove_rows: + self.entries.remove(d) + + self.flags.ignore_links = True + self.save() + def validate_quantity(self, row, qty_field=None): + qty_field = self.get_qty_field(row, qty_field=qty_field) + qty = row.get(qty_field) + if qty_field == "qty" and row.get("stock_qty"): + qty = row.get("stock_qty") + + precision = row.precision + if abs(abs(flt(self.total_qty, precision)) - abs(flt(qty, precision))) > 0.01: + self.throw_error_message( + f"Total quantity {abs(flt(self.total_qty))} in the Serial and Batch Bundle {bold(self.name)} does not match with the quantity {abs(flt(row.get(qty_field)))} for the Item {bold(self.item_code)} in the {self.voucher_type} # {self.voucher_no}" + ) + + def get_qty_field(self, row, qty_field=None) -> str: if not qty_field: qty_field = "qty" - precision = row.precision if row.get("doctype") == "Subcontracting Receipt Supplied Item": qty_field = "consumed_qty" elif row.get("doctype") == "Stock Entry Detail": qty_field = "transfer_qty" - qty = row.get(qty_field) - if qty_field == "qty" and row.get("stock_qty"): - qty = row.get("stock_qty") - - if abs(abs(flt(self.total_qty, precision)) - abs(flt(qty, precision))) > 0.01: - self.throw_error_message( - f"Total quantity {abs(flt(self.total_qty))} in the Serial and Batch Bundle {bold(self.name)} does not match with the quantity {abs(flt(row.get(qty_field)))} for the Item {bold(self.item_code)} in the {self.voucher_type} # {self.voucher_no}" - ) + return qty_field def set_is_outward(self): for row in self.entries: From 8ca4f635728df84f9696f305e039c6caa0d680ca Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 27 Mar 2024 11:52:06 +0530 Subject: [PATCH 13/33] chore: allow to override serial batch selector (cherry picked from commit 0d9d42ae61efe7390a5eff0d7a848b26fbf378db) --- erpnext/public/js/controllers/buying.js | 86 +++++++++---------- erpnext/public/js/controllers/transaction.js | 36 ++++---- erpnext/public/js/erpnext.bundle.js | 1 + erpnext/public/js/utils.js | 32 ++++--- erpnext/public/js/utils/sales_common.js | 37 ++++---- .../page/point_of_sale/pos_item_details.js | 22 +++-- erpnext/stock/doctype/pick_list/pick_list.js | 22 ++--- .../stock/doctype/stock_entry/stock_entry.js | 22 +++-- 8 files changed, 119 insertions(+), 139 deletions(-) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 1e94c0032ab..1c493660afa 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -342,7 +342,6 @@ erpnext.buying = { add_serial_batch_bundle(doc, cdt, cdn) { let item = locals[cdt][cdn]; let me = this; - let path = "assets/erpnext/js/utils/serial_no_batch_selector.js"; frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]) .then((r) => { @@ -352,30 +351,28 @@ erpnext.buying = { item.type_of_transaction = item.qty > 0 ? "Inward" : "Outward"; item.is_rejected = false; - frappe.require(path, function() { - new erpnext.SerialBatchPackageSelector( - me.frm, item, (r) => { - if (r) { - let qty = Math.abs(r.total_qty); - if (doc.is_return) { - qty = qty * -1; - } - - let update_values = { - "serial_and_batch_bundle": r.name, - "use_serial_batch_fields": 0, - "qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item)) - } - - if (r.warehouse) { - update_values["warehouse"] = r.warehouse; - } - - frappe.model.set_value(item.doctype, item.name, update_values); + new erpnext.SerialBatchPackageSelector( + me.frm, item, (r) => { + if (r) { + let qty = Math.abs(r.total_qty); + if (doc.is_return) { + qty = qty * -1; } + + let update_values = { + "serial_and_batch_bundle": r.name, + "use_serial_batch_fields": 0, + "qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item)) + } + + if (r.warehouse) { + update_values["warehouse"] = r.warehouse; + } + + frappe.model.set_value(item.doctype, item.name, update_values); } - ); - }); + } + ); } }); } @@ -383,7 +380,6 @@ erpnext.buying = { add_serial_batch_for_rejected_qty(doc, cdt, cdn) { let item = locals[cdt][cdn]; let me = this; - let path = "assets/erpnext/js/utils/serial_no_batch_selector.js"; frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]) .then((r) => { @@ -393,30 +389,28 @@ erpnext.buying = { item.type_of_transaction = item.qty > 0 ? "Inward" : "Outward"; item.is_rejected = true; - frappe.require(path, function() { - new erpnext.SerialBatchPackageSelector( - me.frm, item, (r) => { - if (r) { - let qty = Math.abs(r.total_qty); - if (doc.is_return) { - qty = qty * -1; - } - - let update_values = { - "serial_and_batch_bundle": r.name, - "use_serial_batch_fields": 0, - "rejected_qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item)) - } - - if (r.warehouse) { - update_values["rejected_warehouse"] = r.warehouse; - } - - frappe.model.set_value(item.doctype, item.name, update_values); + new erpnext.SerialBatchPackageSelector( + me.frm, item, (r) => { + if (r) { + let qty = Math.abs(r.total_qty); + if (doc.is_return) { + qty = qty * -1; } + + let update_values = { + "serial_and_batch_bundle": r.name, + "use_serial_batch_fields": 0, + "rejected_qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item)) + } + + if (r.warehouse) { + update_values["rejected_warehouse"] = r.warehouse; + } + + frappe.model.set_value(item.doctype, item.name, update_values); } - ); - }); + } + ); } }); } diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 53edceee627..47ee74f3da5 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -2499,27 +2499,25 @@ erpnext.show_serial_batch_selector = function (frm, item_row, callback, on_close } } - frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() { - if (["Sales Invoice", "Delivery Note"].includes(frm.doc.doctype)) { - item_row.type_of_transaction = frm.doc.is_return ? "Inward" : "Outward"; - } else { - item_row.type_of_transaction = frm.doc.is_return ? "Outward" : "Inward"; - } + if (["Sales Invoice", "Delivery Note"].includes(frm.doc.doctype)) { + item_row.type_of_transaction = frm.doc.is_return ? "Inward" : "Outward"; + } else { + item_row.type_of_transaction = frm.doc.is_return ? "Outward" : "Inward"; + } - new erpnext.SerialBatchPackageSelector(frm, item_row, (r) => { - if (r) { - let update_values = { - "serial_and_batch_bundle": r.name, - "qty": Math.abs(r.total_qty) - } - - if (r.warehouse) { - update_values[warehouse_field] = r.warehouse; - } - - frappe.model.set_value(item_row.doctype, item_row.name, update_values); + new erpnext.SerialBatchPackageSelector(frm, item_row, (r) => { + if (r) { + let update_values = { + "serial_and_batch_bundle": r.name, + "qty": Math.abs(r.total_qty) } - }); + + if (r.warehouse) { + update_values[warehouse_field] = r.warehouse; + } + + frappe.model.set_value(item_row.doctype, item_row.name, update_values); + } }); } diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js index b75a21bfc97..527d452a450 100644 --- a/erpnext/public/js/erpnext.bundle.js +++ b/erpnext/public/js/erpnext.bundle.js @@ -4,6 +4,7 @@ import "./queries"; import "./sms_manager"; import "./utils/party"; import "./controllers/stock_controller"; +import "./utils/serial_no_batch_selector"; import "./payment/payments"; import "./templates/visual_plant_floor_template.html"; import "./plant_floor_visual/visual_plant"; diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 03bf648b60d..7bf8b9bed6d 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -430,25 +430,23 @@ $.extend(erpnext.utils, { item_row.has_batch_no = r.message.has_batch_no; item_row.has_serial_no = r.message.has_serial_no; - frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function () { - new erpnext.SerialBatchPackageSelector(frm, item_row, (r) => { - if (r) { - let update_values = { - serial_and_batch_bundle: r.name, - qty: Math.abs(r.total_qty), - }; + new erpnext.SerialBatchPackageSelector(frm, item_row, (r) => { + if (r) { + let update_values = { + serial_and_batch_bundle: r.name, + qty: Math.abs(r.total_qty), + }; - if (!warehouse_field) { - warehouse_field = "warehouse"; - } - - if (r.warehouse) { - update_values[warehouse_field] = r.warehouse; - } - - frappe.model.set_value(item_row.doctype, item_row.name, update_values); + if (!warehouse_field) { + warehouse_field = "warehouse"; } - }); + + if (r.warehouse) { + update_values[warehouse_field] = r.warehouse; + } + + frappe.model.set_value(item_row.doctype, item_row.name, update_values); + } }); }); }, diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js index 00df1c5c191..5bc4ffe1bff 100644 --- a/erpnext/public/js/utils/sales_common.js +++ b/erpnext/public/js/utils/sales_common.js @@ -350,7 +350,6 @@ erpnext.sales_common = { pick_serial_and_batch(doc, cdt, cdn) { let item = locals[cdt][cdn]; let me = this; - let path = "assets/erpnext/js/utils/serial_no_batch_selector.js"; frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]).then((r) => { if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) { @@ -364,26 +363,24 @@ erpnext.sales_common = { item.title = __("Select Serial and Batch"); } - frappe.require(path, function () { - new erpnext.SerialBatchPackageSelector(me.frm, item, (r) => { - if (r) { - let qty = Math.abs(r.total_qty); - if (doc.is_return) { - qty = qty * -1; - } - - frappe.model.set_value(item.doctype, item.name, { - serial_and_batch_bundle: r.name, - use_serial_batch_fields: 0, - qty: - qty / - flt( - item.conversion_factor || 1, - precision("conversion_factor", item) - ), - }); + new erpnext.SerialBatchPackageSelector(me.frm, item, (r) => { + if (r) { + let qty = Math.abs(r.total_qty); + if (doc.is_return) { + qty = qty * -1; } - }); + + frappe.model.set_value(item.doctype, item.name, { + serial_and_batch_bundle: r.name, + use_serial_batch_fields: 0, + qty: + qty / + flt( + item.conversion_factor || 1, + precision("conversion_factor", item) + ), + }); + } }); } }); diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index 9646a04e400..f89b70ef45b 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -394,19 +394,17 @@ erpnext.PointOfSale.ItemDetails = class { bind_auto_serial_fetch_event() { this.$form_container.on("click", ".auto-fetch-btn", () => { - frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", () => { - let frm = this.events.get_frm(); - let item_row = this.item_row; - item_row.type_of_transaction = "Outward"; + let frm = this.events.get_frm(); + let item_row = this.item_row; + item_row.type_of_transaction = "Outward"; - new erpnext.SerialBatchPackageSelector(frm, item_row, (r) => { - if (r) { - frappe.model.set_value(item_row.doctype, item_row.name, { - serial_and_batch_bundle: r.name, - qty: Math.abs(r.total_qty), - }); - } - }); + new erpnext.SerialBatchPackageSelector(frm, item_row, (r) => { + if (r) { + frappe.model.set_value(item_row.doctype, item_row.name, { + serial_and_batch_bundle: r.name, + qty: Math.abs(r.total_qty), + }); + } }); }); } diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js index 43519e76467..518782d759b 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.js +++ b/erpnext/stock/doctype/pick_list/pick_list.js @@ -355,19 +355,15 @@ frappe.ui.form.on("Pick List Item", { item.title = __("Select Serial and Batch"); } - frappe.require(path, function () { - new erpnext.SerialBatchPackageSelector(frm, item, (r) => { - if (r) { - let qty = Math.abs(r.total_qty); - frappe.model.set_value(item.doctype, item.name, { - serial_and_batch_bundle: r.name, - use_serial_batch_fields: 0, - qty: - qty / - flt(item.conversion_factor || 1, precision("conversion_factor", item)), - }); - } - }); + new erpnext.SerialBatchPackageSelector(frm, item, (r) => { + if (r) { + let qty = Math.abs(r.total_qty); + frappe.model.set_value(item.doctype, item.name, { + serial_and_batch_bundle: r.name, + use_serial_batch_fields: 0, + qty: qty / flt(item.conversion_factor || 1, precision("conversion_factor", item)), + }); + } }); } }); diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 8348804ae57..9b929f9f1b8 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -1335,18 +1335,16 @@ erpnext.stock.select_batch_and_serial_no = (frm, item) => { item.has_batch_no = r.message.has_batch_no; item.type_of_transaction = item.s_warehouse ? "Outward" : "Inward"; - frappe.require(path, function () { - new erpnext.SerialBatchPackageSelector(frm, item, (r) => { - if (r) { - frappe.model.set_value(item.doctype, item.name, { - serial_and_batch_bundle: r.name, - use_serial_batch_fields: 0, - qty: - Math.abs(r.total_qty) / - flt(item.conversion_factor || 1, precision("conversion_factor", item)), - }); - } - }); + new erpnext.SerialBatchPackageSelector(frm, item, (r) => { + if (r) { + frappe.model.set_value(item.doctype, item.name, { + serial_and_batch_bundle: r.name, + use_serial_batch_fields: 0, + qty: + Math.abs(r.total_qty) / + flt(item.conversion_factor || 1, precision("conversion_factor", item)), + }); + } }); } }); From 0b4b0874f42b5cf35f75f9006aa21bee47b65f17 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 29 Mar 2024 10:27:44 +0530 Subject: [PATCH 14/33] chore: show item name in the validation message (cherry picked from commit d05412f96a02352d1203758a52fb80a8327df45c) --- erpnext/public/js/utils/serial_no_batch_selector.js | 2 +- .../doctype/serial_and_batch_bundle/serial_and_batch_bundle.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 42d37bf493b..1edeca95018 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -421,7 +421,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { let { qty, based_on } = this.dialog.get_values(); if (this.item.serial_and_batch_bundle || this.item.rejected_serial_and_batch_bundle) { - if (qty === this.qty) { + if (this.qty && qty === Math.abs(this.qty)) { return; } } diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 11018c27f67..a5da262242d 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -273,7 +273,8 @@ class SerialandBatchBundle(Document): def validate_negative_batch(self, batch_no, available_qty): if available_qty < 0: - msg = f"""Batch No {bold(batch_no)} has negative stock + msg = f"""Batch No {bold(batch_no)} of an Item {bold(self.item_code)} + has negative stock of quantity {bold(available_qty)} in the warehouse {self.warehouse}""" From 71704beb6f9e15474d5579ccb8cd1d06d7d89f5d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 29 Mar 2024 11:21:27 +0530 Subject: [PATCH 15/33] fix: cost center shouldn't update debit/credit in Exc gain/loss JV (cherry picked from commit 398d3022eff1df4d7179bf795570eab02fc0816b) --- erpnext/accounts/doctype/journal_entry/journal_entry.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 3186d07adcc..2c831cf7c57 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -453,7 +453,10 @@ frappe.ui.form.on("Journal Entry Account", { } }, cost_center: function (frm, dt, dn) { - erpnext.journal_entry.set_account_details(frm, dt, dn); + // Don't reset for Gain/Loss type journals, as it will make Debit and Credit values '0' + if (frm.doc.voucher_type != "Exchange Gain Or Loss") { + erpnext.journal_entry.set_account_details(frm, dt, dn); + } }, account: function (frm, dt, dn) { From 2dd697ee7702cc76d9d67ea7786cc0474ec65860 Mon Sep 17 00:00:00 2001 From: Ashish Shah Date: Fri, 29 Mar 2024 10:52:52 +0530 Subject: [PATCH 16/33] fix: payment_order_status should be no_copy payment_order_status should be no_copy (cherry picked from commit adee2ba5413f5f8c73cbc30204b9ba507866f4ec) --- erpnext/accounts/doctype/payment_entry/payment_entry.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 7c2e14ea751..a37a6fe5465 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -579,6 +579,7 @@ "fieldtype": "Select", "hidden": 1, "label": "Payment Order Status", + "no_copy": 1, "options": "Initiated\nPayment Ordered", "read_only": 1 }, @@ -821,4 +822,4 @@ "states": [], "title_field": "title", "track_changes": 1 -} \ No newline at end of file +} From 33b6244ae66d59b97f2c4b1d889044b2146ac1e9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 30 Mar 2024 10:18:33 +0530 Subject: [PATCH 17/33] fix: Warehouse linked company name in multicompany setup (backport #40779) (#40781) fix: Warehouse linked company name in multicompany setup (#40779) (cherry picked from commit 679aea607b1bf7c9303a2cbe1a7b5629a03f3f4f) Co-authored-by: jeshani <56191568+jeshani@users.noreply.github.com> --- erpnext/stock/doctype/warehouse/warehouse.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/warehouse/warehouse.js b/erpnext/stock/doctype/warehouse/warehouse.js index 195ecb646a2..96cac9c06b2 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.js +++ b/erpnext/stock/doctype/warehouse/warehouse.js @@ -49,6 +49,7 @@ frappe.ui.form.on("Warehouse", { frm.add_custom_button(__("Stock Balance"), function () { frappe.set_route("query-report", "Stock Balance", { warehouse: frm.doc.name, + company: frm.doc.company, }); }); From d2a6509950af39fedd2a7e70fcad4be26b460e1e Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 30 Mar 2024 10:46:56 +0530 Subject: [PATCH 18/33] fix: button to select serial / batch bundle in the subcontracting receipt (cherry picked from commit d73b1f3e6a3b5cf0fa82ec72adf51df5d4ff9437) # Conflicts: # erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json # erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json --- erpnext/controllers/selling_controller.py | 4 +- erpnext/public/js/controllers/buying.js | 2 +- erpnext/stock/serial_batch_bundle.py | 3 + .../subcontracting_receipt.js | 103 ++++++++++++++++++ .../subcontracting_receipt_item.json | 18 +++ .../subcontracting_receipt_supplied_item.json | 11 ++ 6 files changed, 138 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 747b4e061b6..c1b3a6fcedf 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -750,12 +750,12 @@ def get_serial_and_batch_bundle(child, parent): "item_code": child.item_code, "warehouse": child.warehouse, "voucher_type": parent.doctype, - "voucher_no": parent.name, + "voucher_no": parent.name if parent.docstatus < 2 else None, "voucher_detail_no": child.name, "posting_date": parent.posting_date, "posting_time": parent.posting_time, "qty": child.qty, - "type_of_transaction": "Outward" if child.qty > 0 else "Inward", + "type_of_transaction": "Outward" if child.qty > 0 and parent.docstatus < 2 else "Inward", "company": parent.company, "do_not_submit": "True", } diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 1c493660afa..b5a8b757706 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -386,7 +386,7 @@ erpnext.buying = { if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) { item.has_serial_no = r.message.has_serial_no; item.has_batch_no = r.message.has_batch_no; - item.type_of_transaction = item.qty > 0 ? "Inward" : "Outward"; + item.type_of_transaction = item.rejected_qty > 0 ? "Inward" : "Outward"; item.is_rejected = true; new erpnext.SerialBatchPackageSelector( diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 9625b20da41..24e2ee10ab1 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -856,6 +856,9 @@ class SerialBatchCreation: if not doc.get("entries"): return frappe._dict({}) + if doc.voucher_no and frappe.get_cached_value(doc.voucher_type, doc.voucher_no, "docstatus") == 2: + doc.voucher_no = "" + doc.save() self.validate_qty(doc) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index aeff2f6d58f..d407d9c82d7 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -335,12 +335,115 @@ frappe.ui.form.on("Subcontracting Receipt Item", { items_remove: (frm) => { set_missing_values(frm); }, + + add_serial_batch_bundle(frm, cdt, cdn) { + let item = locals[cdt][cdn]; + + frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]).then((r) => { + if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) { + item.has_serial_no = r.message.has_serial_no; + item.has_batch_no = r.message.has_batch_no; + item.type_of_transaction = item.qty > 0 ? "Inward" : "Outward"; + item.is_rejected = false; + + new erpnext.SerialBatchPackageSelector(frm, item, (r) => { + if (r) { + let qty = Math.abs(r.total_qty); + if (frm.doc.is_return) { + qty = qty * -1; + } + + let update_values = { + serial_and_batch_bundle: r.name, + use_serial_batch_fields: 0, + qty: qty / flt(item.conversion_factor || 1, precision("conversion_factor", item)), + }; + + if (r.warehouse) { + update_values["warehouse"] = r.warehouse; + } + + frappe.model.set_value(item.doctype, item.name, update_values); + } + }); + } + }); + }, + + add_serial_batch_for_rejected_qty(frm, cdt, cdn) { + let item = locals[cdt][cdn]; + + frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]).then((r) => { + if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) { + item.has_serial_no = r.message.has_serial_no; + item.has_batch_no = r.message.has_batch_no; + item.type_of_transaction = item.rejected_qty > 0 ? "Inward" : "Outward"; + item.is_rejected = true; + + new erpnext.SerialBatchPackageSelector(frm, item, (r) => { + if (r) { + let qty = Math.abs(r.total_qty); + if (frm.doc.is_return) { + qty = qty * -1; + } + + let update_values = { + serial_and_batch_bundle: r.name, + use_serial_batch_fields: 0, + rejected_qty: + qty / flt(item.conversion_factor || 1, precision("conversion_factor", item)), + }; + + if (r.warehouse) { + update_values["rejected_warehouse"] = r.warehouse; + } + + frappe.model.set_value(item.doctype, item.name, update_values); + } + }); + } + }); + }, }); frappe.ui.form.on("Subcontracting Receipt Supplied Item", { consumed_qty(frm) { set_missing_values(frm); }, + + add_serial_batch_bundle(frm, cdt, cdn) { + let item = locals[cdt][cdn]; + + item.item_code = item.rm_item_code; + item.qty = item.consumed_qty; + item.warehouse = frm.doc.supplier_warehouse; + frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]).then((r) => { + if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) { + item.has_serial_no = r.message.has_serial_no; + item.has_batch_no = r.message.has_batch_no; + item.type_of_transaction = item.qty > 0 ? "Outward" : "Inward"; + item.is_rejected = false; + + new erpnext.SerialBatchPackageSelector(frm, item, (r) => { + if (r) { + let qty = Math.abs(r.total_qty); + if (frm.doc.is_return) { + qty = qty * -1; + } + + let update_values = { + serial_and_batch_bundle: r.name, + use_serial_batch_fields: 0, + consumed_qty: + qty / flt(item.conversion_factor || 1, precision("conversion_factor", item)), + }; + + frappe.model.set_value(item.doctype, item.name, update_values); + } + }); + } + }); + }, }); let set_warehouse_in_children = (child_table, warehouse_field, warehouse) => { diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json index ca14f09c9c5..97545a3ffca 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -47,9 +47,11 @@ "schedule_date", "reference_name", "section_break_45", + "add_serial_batch_bundle", "serial_and_batch_bundle", "use_serial_batch_fields", "col_break5", + "add_serial_batch_for_rejected_qty", "rejected_serial_and_batch_bundle", "section_break_jshh", "serial_no", @@ -563,12 +565,28 @@ { "fieldname": "column_break_henr", "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.use_serial_batch_fields === 0", + "fieldname": "add_serial_batch_bundle", + "fieldtype": "Button", + "label": "Add Serial / Batch Bundle" + }, + { + "depends_on": "eval:doc.use_serial_batch_fields === 0", + "fieldname": "add_serial_batch_for_rejected_qty", + "fieldtype": "Button", + "label": "Add Serial / Batch No (Rejected Qty)" } ], "idx": 1, "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2024-03-07 11:43:38.954262", +======= + "modified": "2024-03-29 15:42:43.425544", +>>>>>>> d73b1f3e6a (fix: button to select serial / batch bundle in the subcontracting receipt) "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item", diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json index 957b6a2a654..f4109ad6d1c 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json @@ -25,6 +25,7 @@ "consumed_qty", "current_stock", "secbreak_3", + "add_serial_batch_bundle", "serial_and_batch_bundle", "use_serial_batch_fields", "col_break4", @@ -224,12 +225,22 @@ { "fieldname": "column_break_qibi", "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.use_serial_batch_fields === 0", + "fieldname": "add_serial_batch_bundle", + "fieldtype": "Button", + "label": "Add Serial / Batch Bundle" } ], "idx": 1, "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2024-02-04 16:32:17.534162", +======= + "modified": "2024-03-30 10:26:27.237371", +>>>>>>> d73b1f3e6a (fix: button to select serial / batch bundle in the subcontracting receipt) "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Supplied Item", From b397535392b5bfb57f6468d79ecaf7ca5d63f8c3 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 30 Mar 2024 20:04:53 +0530 Subject: [PATCH 19/33] chore: fix conflicts --- .../subcontracting_receipt_item.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json index 97545a3ffca..75e263e2c1c 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -582,11 +582,7 @@ "idx": 1, "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2024-03-07 11:43:38.954262", -======= "modified": "2024-03-29 15:42:43.425544", ->>>>>>> d73b1f3e6a (fix: button to select serial / batch bundle in the subcontracting receipt) "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item", @@ -597,4 +593,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} From 04da610288555454f3695fc0e2e06617bf41ad51 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 30 Mar 2024 20:05:21 +0530 Subject: [PATCH 20/33] chore: fix conflicts --- .../subcontracting_receipt_supplied_item.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json index f4109ad6d1c..e1927a5467a 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json @@ -236,11 +236,7 @@ "idx": 1, "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2024-02-04 16:32:17.534162", -======= "modified": "2024-03-30 10:26:27.237371", ->>>>>>> d73b1f3e6a (fix: button to select serial / batch bundle in the subcontracting receipt) "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Supplied Item", @@ -251,4 +247,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} From 6ecafe7606a596b0e088f4e2ae5f98a343d0263a Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sun, 31 Mar 2024 11:39:55 +0530 Subject: [PATCH 21/33] chore: fix linter issue --- erpnext/stock/serial_batch_bundle.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 24e2ee10ab1..fb63b0181d3 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -856,7 +856,9 @@ class SerialBatchCreation: if not doc.get("entries"): return frappe._dict({}) - if doc.voucher_no and frappe.get_cached_value(doc.voucher_type, doc.voucher_no, "docstatus") == 2: + if ( + doc.voucher_no and frappe.get_cached_value(doc.voucher_type, doc.voucher_no, "docstatus") == 2 + ): doc.voucher_no = "" doc.save() From 390b4986f9b210a5c45866fbb644e440dda0f056 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sun, 31 Mar 2024 11:41:00 +0530 Subject: [PATCH 22/33] chore: fix conflicts --- .../buying/doctype/supplier_quotation/supplier_quotation.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index c898d91405e..4a8cd8bf9e6 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -928,11 +928,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], -<<<<<<< HEAD - "modified": "2024-03-20 16:03:59.069145", -======= "modified": "2024-03-28 10:20:30.231915", ->>>>>>> 9828d34b19 (fix: markdown to text editor set for supplier quotation) "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", From ed4c6ef1a74adad9e832e4552420899778b553d6 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Wed, 28 Feb 2024 20:33:16 -0500 Subject: [PATCH 23/33] test: show future payments allocated sales returns is considered as payment (cherry picked from commit daaa00bd4aa7414fde65bfc0e23538678f38aa12) --- .../test_accounts_receivable.py | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index de49139adc1..01129824bca 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -469,11 +469,30 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): ) def test_future_payments(self): + sr = self.create_sales_invoice(do_not_submit=True) + sr.is_return = 1 + sr.items[0].qty = -1 + sr.items[0].rate = 10 + sr.calculate_taxes_and_totals() + sr.submit() + si = self.create_sales_invoice() pe = get_payment_entry(si.doctype, si.name) + pe.append( + "references", + { + "reference_doctype": sr.doctype, + "reference_name": sr.name, + "due_date": sr.due_date, + "total_amount": sr.grand_total, + "outstanding_amount": sr.outstanding_amount, + "allocated_amount": sr.outstanding_amount, + }, + ) + pe.posting_date = add_days(today(), 1) - pe.paid_amount = 90.0 - pe.references[0].allocated_amount = 90.0 + pe.paid_amount = 80 + pe.references[0].allocated_amount = 90.0 # pe.paid_amount + sr.grand_total pe.save().submit() filters = { "company": self.company, @@ -485,16 +504,21 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): "show_future_payments": True, } report = execute(filters)[1] - self.assertEqual(len(report), 1) + self.assertEqual(len(report), 2) - expected_data = [100.0, 100.0, 10.0, 90.0] + expected_data = {sr.name: [10.0, -10.0, 0.0, -10], si.name: [100.0, 100.0, 10.0, 90.0]} - row = report[0] - self.assertEqual( - expected_data, [row.invoiced, row.outstanding, row.remaining_balance, row.future_amount] - ) + rows = report[:2] + for row in rows: + self.assertEqual( + expected_data[row.voucher_no], + [row.invoiced or row.paid, row.outstanding, row.remaining_balance, row.future_amount], + ) pe.cancel() + sr.load_from_db() # Outstanding amount is updated so a updated timestamp is needed. + sr.cancel() + # full payment in future date pe = get_payment_entry(si.doctype, si.name) pe.posting_date = add_days(today(), 1) From 0561d0a81c646253d4fd165b39906019027d3e87 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Wed, 28 Feb 2024 20:33:25 -0500 Subject: [PATCH 24/33] fix: show future payments allocated sales returns is considered as payment (cherry picked from commit 3381d0d94525cc2900ba23a69be644b6dae7a483) --- .../accounts/report/accounts_receivable/accounts_receivable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 6ed6ca22e63..b8c7271ae4e 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -671,7 +671,7 @@ class ReceivablePayableReport(object): else: future_amount_field = "future_amount_in_base_currency" - if row.remaining_balance > 0 and future.get(future_amount_field): + if row.remaining_balance != 0 and future.get(future_amount_field): if future.get(future_amount_field) > row.outstanding: row.future_amount = row.outstanding future[future_amount_field] = future.get(future_amount_field) - row.outstanding From 2185a2955535f3b00f2629ada1b63e6fcfa2ebea Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 28 Mar 2024 12:24:09 +0530 Subject: [PATCH 25/33] fix: Show Stock Ageing Data filter not working in stock balance report (cherry picked from commit 621421bda2d0584b7f8531fed17a82db8397bfee) # Conflicts: # erpnext/stock/report/stock_ageing/stock_ageing.py --- erpnext/public/js/controllers/transaction.js | 1 - .../stock/report/stock_ageing/stock_ageing.py | 63 +++++++++++++++++++ .../report/stock_balance/stock_balance.py | 2 + 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 47ee74f3da5..24e85295f4b 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -415,7 +415,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe let row = locals[cdt][cdn]; if (row.barcode) { erpnext.stock.utils.set_item_details_using_barcode(this.frm, row, (r) => { - debugger frappe.model.set_value(cdt, cdn, { "item_code": r.message.item_code, "qty": 1, diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index d0929a082ce..ad4e232313e 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -234,6 +234,7 @@ class FIFOSlots: if self.sle is None: self.sle = self.__get_stock_ledger_entries() +<<<<<<< HEAD for d in self.sle: key, fifo_queue, transferred_item_key = self.__init_key_stores(d) @@ -241,6 +242,21 @@ class FIFOSlots: # get difference in qty shift as actual qty prev_balance_qty = self.item_details[key].get("qty_after_transaction", 0) d.actual_qty = flt(d.qty_after_transaction) - flt(prev_balance_qty) +======= + from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import ( + get_serial_nos_from_bundle, + ) + + stock_ledger_entries = self.sle + + bundle_wise_serial_nos = frappe._dict({}) + if stock_ledger_entries is None: + bundle_wise_serial_nos = self.__get_bundle_wise_serial_nos() + + with frappe.db.unbuffered_cursor(): + if stock_ledger_entries is None: + stock_ledger_entries = self.__get_stock_ledger_entries() +>>>>>>> 621421bda2 (fix: Show Stock Ageing Data filter not working in stock balance report) serial_nos = get_serial_nos(d.serial_no) if d.serial_no else [] @@ -249,7 +265,26 @@ class FIFOSlots: else: self.__compute_outgoing_stock(d, fifo_queue, transferred_item_key, serial_nos) +<<<<<<< HEAD self.__update_balances(d, key) +======= + serial_nos = get_serial_nos(d.serial_no) if d.serial_no else [] + if d.serial_and_batch_bundle and d.has_serial_no: + if bundle_wise_serial_nos: + serial_nos = bundle_wise_serial_nos.get(d.serial_and_batch_bundle) or [] + else: + serial_nos = get_serial_nos_from_bundle(d.serial_and_batch_bundle) or [] + + if d.actual_qty > 0: + self.__compute_incoming_stock(d, fifo_queue, transferred_item_key, serial_nos) + else: + self.__compute_outgoing_stock(d, fifo_queue, transferred_item_key, serial_nos) + + self.__update_balances(d, key) + + # Note that stock_ledger_entries is an iterator, you can not reuse it like a list + del stock_ledger_entries +>>>>>>> 621421bda2 (fix: Show Stock Ageing Data filter not working in stock balance report) if not self.filters.get("show_warehouse_wise_stock"): # (Item 1, WH 1), (Item 1, WH 2) => (Item 1) @@ -403,6 +438,7 @@ class FIFOSlots: sle.serial_no, sle.batch_no, sle.qty_after_transaction, + sle.serial_and_batch_bundle, sle.warehouse, ) .where( @@ -420,6 +456,33 @@ class FIFOSlots: return sle_query.run(as_dict=True) + def __get_bundle_wise_serial_nos(self) -> dict: + bundle = frappe.qb.DocType("Serial and Batch Bundle") + entry = frappe.qb.DocType("Serial and Batch Entry") + + query = ( + frappe.qb.from_(bundle) + .join(entry) + .on(bundle.name == entry.parent) + .select(bundle.name, entry.serial_no) + .where( + (bundle.docstatus == 1) + & (entry.serial_no.isnotnull()) + & (bundle.company == self.filters.get("company")) + & (bundle.posting_date <= self.filters.get("to_date")) + ) + ) + + for field in ["item_code", "warehouse"]: + if self.filters.get(field): + query = query.where(bundle[field] == self.filters.get(field)) + + bundle_wise_serial_nos = frappe._dict({}) + for bundle_name, serial_no in query.run(): + bundle_wise_serial_nos.setdefault(bundle_name, []).append(serial_no) + + return bundle_wise_serial_nos + def __get_item_query(self) -> str: item_table = frappe.qb.DocType("Item") diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 500affa51e2..5f85a672ea0 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -295,6 +295,8 @@ class StockBalanceReport(object): sle.stock_value, sle.batch_no, sle.serial_no, + sle.serial_and_batch_bundle, + sle.has_serial_no, item_table.item_group, item_table.stock_uom, item_table.item_name, From f32de045e3d1fdbafee81ff9929dd09bd24f1309 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 1 Apr 2024 18:53:04 +0530 Subject: [PATCH 26/33] chore: fix conflicts --- .../stock/report/stock_ageing/stock_ageing.py | 83 ++++++++----------- 1 file changed, 33 insertions(+), 50 deletions(-) diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index ad4e232313e..c4156e7e64e 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -2,8 +2,8 @@ # License: GNU General Public License v3. See license.txt +from collections.abc import Iterator from operator import itemgetter -from typing import Dict, List, Tuple, Union import frappe from frappe import _ @@ -14,7 +14,7 @@ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos Filters = frappe._dict -def execute(filters: Filters = None) -> Tuple: +def execute(filters: Filters = None) -> tuple: to_date = filters["to_date"] columns = get_columns(filters) @@ -26,14 +26,14 @@ def execute(filters: Filters = None) -> Tuple: return columns, data, None, chart_data -def format_report_data(filters: Filters, item_details: Dict, to_date: str) -> List[Dict]: +def format_report_data(filters: Filters, item_details: dict, to_date: str) -> list[dict]: "Returns ordered, formatted data with ranges." _func = itemgetter(1) data = [] precision = cint(frappe.db.get_single_value("System Settings", "float_precision", cache=True)) - for item, item_dict in item_details.items(): + for _item, item_dict in item_details.items(): if not flt(item_dict.get("total_qty"), precision): continue @@ -74,12 +74,12 @@ def format_report_data(filters: Filters, item_details: Dict, to_date: str) -> Li return data -def get_average_age(fifo_queue: List, to_date: str) -> float: +def get_average_age(fifo_queue: list, to_date: str) -> float: batch_age = age_qty = total_qty = 0.0 for batch in fifo_queue: batch_age = date_diff(to_date, batch[1]) - if isinstance(batch[0], (int, float)): + if isinstance(batch[0], int | float): age_qty += batch_age * batch[0] total_qty += batch[0] else: @@ -89,8 +89,7 @@ def get_average_age(fifo_queue: List, to_date: str) -> float: return flt(age_qty / total_qty, 2) if total_qty else 0.0 -def get_range_age(filters: Filters, fifo_queue: List, to_date: str, item_dict: Dict) -> Tuple: - +def get_range_age(filters: Filters, fifo_queue: list, to_date: str, item_dict: dict) -> tuple: precision = cint(frappe.db.get_single_value("System Settings", "float_precision", cache=True)) range1 = range2 = range3 = above_range3 = 0.0 @@ -111,7 +110,7 @@ def get_range_age(filters: Filters, fifo_queue: List, to_date: str, item_dict: D return range1, range2, range3, above_range3 -def get_columns(filters: Filters) -> List[Dict]: +def get_columns(filters: Filters) -> list[dict]: range_columns = [] setup_ageing_columns(filters, range_columns) columns = [ @@ -169,7 +168,7 @@ def get_columns(filters: Filters) -> List[Dict]: return columns -def get_chart_data(data: List, filters: Filters) -> Dict: +def get_chart_data(data: list, filters: Filters) -> dict: if not data: return [] @@ -193,7 +192,7 @@ def get_chart_data(data: List, filters: Filters) -> Dict: } -def setup_ageing_columns(filters: Filters, range_columns: List): +def setup_ageing_columns(filters: Filters, range_columns: list): ranges = [ f"0 - {filters['range1']}", f"{cint(filters['range1']) + 1} - {cint(filters['range2'])}", @@ -205,23 +204,21 @@ def setup_ageing_columns(filters: Filters, range_columns: List): add_column(range_columns, label=_("Age ({0})").format(label), fieldname=fieldname) -def add_column( - range_columns: List, label: str, fieldname: str, fieldtype: str = "Float", width: int = 140 -): +def add_column(range_columns: list, label: str, fieldname: str, fieldtype: str = "Float", width: int = 140): range_columns.append(dict(label=label, fieldname=fieldname, fieldtype=fieldtype, width=width)) class FIFOSlots: "Returns FIFO computed slots of inwarded stock as per date." - def __init__(self, filters: Dict = None, sle: List = None): + def __init__(self, filters: dict | None = None, sle: list | None = None): self.item_details = {} self.transferred_item_details = {} self.serial_no_batch_purchase_details = {} self.filters = filters self.sle = sle - def generate(self) -> Dict: + def generate(self) -> dict: """ Returns dict of the foll.g structure: Key = Item A / (Item A, Warehouse A) @@ -231,18 +228,7 @@ class FIFOSlots: consumed/updated and maintained via FIFO. ** } """ - if self.sle is None: - self.sle = self.__get_stock_ledger_entries() -<<<<<<< HEAD - for d in self.sle: - key, fifo_queue, transferred_item_key = self.__init_key_stores(d) - - if d.voucher_type == "Stock Reconciliation": - # get difference in qty shift as actual qty - prev_balance_qty = self.item_details[key].get("qty_after_transaction", 0) - d.actual_qty = flt(d.qty_after_transaction) - flt(prev_balance_qty) -======= from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import ( get_serial_nos_from_bundle, ) @@ -256,18 +242,15 @@ class FIFOSlots: with frappe.db.unbuffered_cursor(): if stock_ledger_entries is None: stock_ledger_entries = self.__get_stock_ledger_entries() ->>>>>>> 621421bda2 (fix: Show Stock Ageing Data filter not working in stock balance report) - serial_nos = get_serial_nos(d.serial_no) if d.serial_no else [] + for d in stock_ledger_entries: + key, fifo_queue, transferred_item_key = self.__init_key_stores(d) - if d.actual_qty > 0: - self.__compute_incoming_stock(d, fifo_queue, transferred_item_key, serial_nos) - else: - self.__compute_outgoing_stock(d, fifo_queue, transferred_item_key, serial_nos) + if d.voucher_type == "Stock Reconciliation": + # get difference in qty shift as actual qty + prev_balance_qty = self.item_details[key].get("qty_after_transaction", 0) + d.actual_qty = flt(d.qty_after_transaction) - flt(prev_balance_qty) -<<<<<<< HEAD - self.__update_balances(d, key) -======= serial_nos = get_serial_nos(d.serial_no) if d.serial_no else [] if d.serial_and_batch_bundle and d.has_serial_no: if bundle_wise_serial_nos: @@ -284,7 +267,6 @@ class FIFOSlots: # Note that stock_ledger_entries is an iterator, you can not reuse it like a list del stock_ledger_entries ->>>>>>> 621421bda2 (fix: Show Stock Ageing Data filter not working in stock balance report) if not self.filters.get("show_warehouse_wise_stock"): # (Item 1, WH 1), (Item 1, WH 2) => (Item 1) @@ -292,7 +274,7 @@ class FIFOSlots: return self.item_details - def __init_key_stores(self, row: Dict) -> Tuple: + def __init_key_stores(self, row: dict) -> tuple: "Initialise keys and FIFO Queue." key = (row.name, row.warehouse) @@ -304,9 +286,7 @@ class FIFOSlots: return key, fifo_queue, transferred_item_key - def __compute_incoming_stock( - self, row: Dict, fifo_queue: List, transfer_key: Tuple, serial_nos: List - ): + def __compute_incoming_stock(self, row: dict, fifo_queue: list, transfer_key: tuple, serial_nos: list): "Update FIFO Queue on inward stock." transfer_data = self.transferred_item_details.get(transfer_key) @@ -332,9 +312,7 @@ class FIFOSlots: self.serial_no_batch_purchase_details.setdefault(serial_no, row.posting_date) fifo_queue.append([serial_no, row.posting_date]) - def __compute_outgoing_stock( - self, row: Dict, fifo_queue: List, transfer_key: Tuple, serial_nos: List - ): + def __compute_outgoing_stock(self, row: dict, fifo_queue: list, transfer_key: tuple, serial_nos: list): "Update FIFO Queue on outward stock." if serial_nos: fifo_queue[:] = [serial_no for serial_no in fifo_queue if serial_no[0] not in serial_nos] @@ -360,7 +338,7 @@ class FIFOSlots: self.transferred_item_details[transfer_key].append([qty_to_pop, slot[1]]) qty_to_pop = 0 - def __adjust_incoming_transfer_qty(self, transfer_data: Dict, fifo_queue: List, row: Dict): + def __adjust_incoming_transfer_qty(self, transfer_data: dict, fifo_queue: list, row: dict): "Add previously removed stock back to FIFO Queue." transfer_qty_to_pop = flt(row.actual_qty) @@ -387,7 +365,7 @@ class FIFOSlots: add_to_fifo_queue([transfer_qty_to_pop, transfer_data[0][1]]) transfer_qty_to_pop = 0 - def __update_balances(self, row: Dict, key: Union[Tuple, str]): + def __update_balances(self, row: dict, key: tuple | str): self.item_details[key]["qty_after_transaction"] = row.qty_after_transaction if "total_qty" not in self.item_details[key]: @@ -397,7 +375,7 @@ class FIFOSlots: self.item_details[key]["has_serial_no"] = row.has_serial_no - def __aggregate_details_by_item(self, wh_wise_data: Dict) -> Dict: + def __aggregate_details_by_item(self, wh_wise_data: dict) -> dict: "Aggregate Item-Wh wise data into single Item entry." item_aggregated_data = {} for key, row in wh_wise_data.items(): @@ -405,7 +383,12 @@ class FIFOSlots: if not item_aggregated_data.get(item): item_aggregated_data.setdefault( item, - {"details": frappe._dict(), "fifo_queue": [], "qty_after_transaction": 0.0, "total_qty": 0.0}, + { + "details": frappe._dict(), + "fifo_queue": [], + "qty_after_transaction": 0.0, + "total_qty": 0.0, + }, ) item_row = item_aggregated_data.get(item) item_row["details"].update(row["details"]) @@ -416,7 +399,7 @@ class FIFOSlots: return item_aggregated_data - def __get_stock_ledger_entries(self) -> List[Dict]: + def __get_stock_ledger_entries(self) -> Iterator[dict]: sle = frappe.qb.DocType("Stock Ledger Entry") item = self.__get_item_query() # used as derived table in sle query @@ -454,7 +437,7 @@ class FIFOSlots: sle_query = sle_query.orderby(sle.posting_date, sle.posting_time, sle.creation, sle.actual_qty) - return sle_query.run(as_dict=True) + return sle_query.run(as_dict=True, as_iterator=True) def __get_bundle_wise_serial_nos(self) -> dict: bundle = frappe.qb.DocType("Serial and Batch Bundle") From 2b1b51b5bb3a42bc808d4f035ada9e9475ca1ed7 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 1 Apr 2024 22:40:53 +0530 Subject: [PATCH 27/33] fix: barcode not fetched on selection of item (cherry picked from commit b0730293e2b9aa89ed5904256d7846cef45cb4fe) --- erpnext/stock/doctype/item/test_item.py | 10 +++++++++- .../stock/doctype/stock_entry/stock_entry.py | 14 +++++++++++++- .../doctype/stock_entry/test_stock_entry.py | 6 ++++++ .../stock_reconciliation.js | 19 +++++++++++++++++++ erpnext/stock/get_item_details.py | 11 ++++++++++- 5 files changed, 57 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index b237f73026b..b369b2e37f6 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -32,7 +32,7 @@ test_ignore = ["BOM"] test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand", "Item Attribute"] -def make_item(item_code=None, properties=None, uoms=None): +def make_item(item_code=None, properties=None, uoms=None, barcode=None): if not item_code: item_code = frappe.generate_hash(length=16) @@ -61,6 +61,14 @@ def make_item(item_code=None, properties=None, uoms=None): for uom in uoms: item.append("uoms", uom) + if barcode: + item.append( + "barcodes", + { + "barcode": barcode, + }, + ) + item.insert() return item diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index e6214290fda..3c19eb99350 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -31,6 +31,7 @@ from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import ( OpeningEntryAccountError, ) from erpnext.stock.get_item_details import ( + get_barcode_data, get_bin_details, get_conversion_factor, get_default_cost_center, @@ -428,7 +429,14 @@ class StockEntry(StockController): for field in reset_fields: item.set(field, item_details.get(field)) - update_fields = ("uom", "description", "expense_account", "cost_center", "conversion_factor") + update_fields = ( + "uom", + "description", + "expense_account", + "cost_center", + "conversion_factor", + "barcode", + ) for field in update_fields: if not item.get(field): @@ -1609,6 +1617,10 @@ class StockEntry(StockController): if subcontract_items and len(subcontract_items) == 1: ret["subcontracted_item"] = subcontract_items[0].main_item_code + barcode_data = get_barcode_data(item_code=item.name) + if barcode_data and len(barcode_data.get(item.name)) == 1: + ret["barcode"] = barcode_data.get(item.name)[0] + return ret @frappe.whitelist() diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index ce3c4958cec..1fde55f8af4 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -98,6 +98,12 @@ class TestStockEntry(FrappeTestCase): self._test_auto_material_request("_Test Item") self._test_auto_material_request("_Test Item", material_request_type="Transfer") + def test_barcode_item_stock_entry(self): + item_code = make_item("_Test Item Stock Entry For Barcode", barcode="BDD-1234567890") + + se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=100) + self.assertEqual(se.items[0].barcode, "BDD-1234567890") + def test_auto_material_request_for_variant(self): fields = [{"field_name": "reorder_levels"}] set_item_variant_settings(fields) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index 4a85360235a..8532b60d59c 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -81,6 +81,18 @@ frappe.ui.form.on("Stock Reconciliation", { if (frm.doc.company) { frm.trigger("toggle_display_account_head"); } + + frm.events.set_fields_onload_for_line_item(frm); + }, + + set_fields_onload_for_line_item(frm) { + if (frm.is_new() && frm.doc?.items && cint(frappe.user_defaults?.use_serial_batch_fields) === 1) { + frm.doc.items.forEach((item) => { + if (!item.serial_and_batch_bundle) { + frappe.model.set_value(item.doctype, item.name, "use_serial_batch_fields", 1); + } + }); + } }, scan_barcode: function (frm) { @@ -155,6 +167,9 @@ frappe.ui.form.on("Stock Reconciliation", { item.qty = item.qty || 0; item.valuation_rate = item.valuation_rate || 0; + item.use_serial_batch_fields = cint( + frappe.user_defaults?.use_serial_batch_fields + ); }); frm.refresh_field("items"); }, @@ -298,6 +313,10 @@ frappe.ui.form.on("Stock Reconciliation Item", { if (!item.warehouse && frm.doc.set_warehouse) { frappe.model.set_value(cdt, cdn, "warehouse", frm.doc.set_warehouse); } + + if (item.docstatus === 0 && cint(frappe.user_defaults?.use_serial_batch_fields) === 1) { + frappe.model.set_value(item.doctype, item.name, "use_serial_batch_fields", 1); + } }, add_serial_batch_bundle(frm, cdt, cdn) { diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index e13d7a354ad..f01f170b90a 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -499,12 +499,21 @@ def update_barcode_value(out): out["barcode"] = barcode_data.get(out.item_code)[0] -def get_barcode_data(items_list): +def get_barcode_data(items_list=None, item_code=None): # get item-wise batch no data # example: {'LED-GRE': [Batch001, Batch002]} # where LED-GRE is item code, SN0001 is serial no and Pune is warehouse itemwise_barcode = {} + if not items_list and item_code: + _dict_item_code = frappe._dict( + { + "item_code": item_code, + } + ) + + items_list = [frappe._dict(_dict_item_code)] + for item in items_list: barcodes = frappe.db.get_all( "Item Barcode", filters={"parent": item.item_code}, fields="barcode" From 87ce9d834f05c3e6bf471e6644e4f04ef3828a30 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 2 Apr 2024 11:14:51 +0530 Subject: [PATCH 28/33] fix: added validation if parent id is missing in BOM Creator (cherry picked from commit 0a1fab762fa48a344bc75dfe01c7da9108b886d2) --- erpnext/manufacturing/doctype/bom_creator/bom_creator.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py index 1709a1f71af..3c4c863f1ce 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py @@ -240,6 +240,9 @@ class BOMCreator(Document): (row.item_code, row.name), frappe._dict({"items": [], "bom_no": "", "fg_item_data": row}) ) + if not row.fg_reference_id and production_item_wise_rm.get((row.fg_item, row.fg_reference_id)): + frappe.throw(_("Please set Parent Row No for item {0}").format(row.fg_item)) + production_item_wise_rm[(row.fg_item, row.fg_reference_id)]["items"].append(row) reverse_tree = OrderedDict(reversed(list(production_item_wise_rm.items()))) From ecc317d690debe888674c5c8b94e0aad958e9d8b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 2 Apr 2024 16:24:02 +0530 Subject: [PATCH 29/33] chore: remove rate of material based on Manual (cherry picked from commit 0b63dbf221e18e19ae1caa021fdf0b0a4d373bc6) # Conflicts: # erpnext/manufacturing/doctype/bom/bom.json # erpnext/manufacturing/doctype/bom_creator/bom_creator.json --- erpnext/manufacturing/doctype/bom/bom.json | 6 +++++- erpnext/manufacturing/doctype/bom/bom.py | 7 +++---- erpnext/manufacturing/doctype/bom_creator/bom_creator.json | 6 +++++- erpnext/manufacturing/doctype/bom_creator/bom_creator.py | 6 +----- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index 50838736816..2bd9b1f2158 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -238,7 +238,7 @@ "fieldname": "rm_cost_as_per", "fieldtype": "Select", "label": "Rate Of Materials Based On", - "options": "Valuation Rate\nLast Purchase Rate\nPrice List\nManual" + "options": "Valuation Rate\nLast Purchase Rate\nPrice List" }, { "allow_on_submit": 1, @@ -637,7 +637,11 @@ "image_field": "image", "is_submittable": 1, "links": [], +<<<<<<< HEAD "modified": "2023-12-26 19:34:08.159312", +======= + "modified": "2024-04-02 16:22:47.518411", +>>>>>>> 0b63dbf221 (chore: remove rate of material based on Manual) "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 8c4574ed90a..d544272e437 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -150,7 +150,7 @@ class BOM(WebsiteGenerator): quality_inspection_template: DF.Link | None quantity: DF.Float raw_material_cost: DF.Currency - rm_cost_as_per: DF.Literal["Valuation Rate", "Last Purchase Rate", "Price List", "Manual"] + rm_cost_as_per: DF.Literal["Valuation Rate", "Last Purchase Rate", "Price List"] route: DF.SmallText | None routing: DF.Link | None scrap_items: DF.Table[BOMScrapItem] @@ -742,6 +742,7 @@ class BOM(WebsiteGenerator): def calculate_rm_cost(self, save=False): """Fetch RM rate as per today's valuation rate and calculate totals""" + total_rm_cost = 0 base_total_rm_cost = 0 @@ -750,7 +751,7 @@ class BOM(WebsiteGenerator): continue old_rate = d.rate - if self.rm_cost_as_per != "Manual": + if not self.bom_creator: d.rate = self.get_rm_rate( { "company": self.company, @@ -1022,8 +1023,6 @@ def get_bom_item_rate(args, bom_doc): item_doc = frappe.get_cached_doc("Item", args.get("item_code")) price_list_data = get_price_list_rate(bom_args, item_doc) rate = price_list_data.price_list_rate - elif bom_doc.rm_cost_as_per == "Manual": - return return flt(rate) diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.json b/erpnext/manufacturing/doctype/bom_creator/bom_creator.json index fb4c6c5c95a..32565bb7850 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.json +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.json @@ -66,7 +66,7 @@ "fieldname": "rm_cost_as_per", "fieldtype": "Select", "label": "Rate Of Materials Based On", - "options": "Valuation Rate\nLast Purchase Rate\nPrice List\nManual", + "options": "Valuation Rate\nLast Purchase Rate\nPrice List", "reqd": 1 }, { @@ -288,7 +288,11 @@ "link_fieldname": "bom_creator" } ], +<<<<<<< HEAD "modified": "2023-08-07 15:45:06.176313", +======= + "modified": "2024-04-02 16:30:59.779190", +>>>>>>> 0b63dbf221 (chore: remove rate of material based on Manual) "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Creator", diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py index 3c4c863f1ce..d2f0c29fe63 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py @@ -59,7 +59,7 @@ class BOMCreator(Document): qty: DF.Float raw_material_cost: DF.Currency remarks: DF.TextEditor | None - rm_cost_as_per: DF.Literal["Valuation Rate", "Last Purchase Rate", "Price List", "Manual"] + rm_cost_as_per: DF.Literal["Valuation Rate", "Last Purchase Rate", "Price List"] set_rate_based_on_warehouse: DF.Check status: DF.Literal["Draft", "Submitted", "In Progress", "Completed", "Failed", "Cancelled"] uom: DF.Link | None @@ -143,9 +143,6 @@ class BOMCreator(Document): self.submit() def set_rate_for_items(self): - if self.rm_cost_as_per == "Manual": - return - amount = self.get_raw_material_cost() self.raw_material_cost = amount @@ -286,7 +283,6 @@ class BOMCreator(Document): "allow_alternative_item": 1, "bom_creator": self.name, "bom_creator_item": bom_creator_item, - "rm_cost_as_per": "Manual", } ) From 152921b82a954cb002cc157cb7291d247aa1c26a Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 2 Apr 2024 19:17:41 +0530 Subject: [PATCH 30/33] chore: fix conflicts --- erpnext/manufacturing/doctype/bom/bom.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index 2bd9b1f2158..4ce8342af6e 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -637,11 +637,7 @@ "image_field": "image", "is_submittable": 1, "links": [], -<<<<<<< HEAD - "modified": "2023-12-26 19:34:08.159312", -======= "modified": "2024-04-02 16:22:47.518411", ->>>>>>> 0b63dbf221 (chore: remove rate of material based on Manual) "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", @@ -680,4 +676,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} From ca6caa86bca149eb2e6846f2aee35de0f06aa75f Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 2 Apr 2024 19:18:02 +0530 Subject: [PATCH 31/33] chore: fix conflicts --- erpnext/manufacturing/doctype/bom_creator/bom_creator.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.json b/erpnext/manufacturing/doctype/bom_creator/bom_creator.json index 32565bb7850..9d2384e31a0 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.json +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.json @@ -288,11 +288,7 @@ "link_fieldname": "bom_creator" } ], -<<<<<<< HEAD - "modified": "2023-08-07 15:45:06.176313", -======= "modified": "2024-04-02 16:30:59.779190", ->>>>>>> 0b63dbf221 (chore: remove rate of material based on Manual) "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Creator", @@ -331,4 +327,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} From 5ea11af36b5f5f3e0ada7ecc94800683e053abf1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 27 Mar 2024 22:53:42 +0530 Subject: [PATCH 32/33] feat: allow to pick manually qty / batches / serial nos (cherry picked from commit 50dd9fa8a32b8cd29b50d1c6181dfd8f6f5d27b6) # Conflicts: # erpnext/stock/doctype/pick_list/pick_list.json --- erpnext/stock/doctype/pick_list/pick_list.json | 12 ++++++++++++ erpnext/stock/doctype/pick_list/pick_list.py | 5 ++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json index 0c474342a97..927d1a61edc 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.json +++ b/erpnext/stock/doctype/pick_list/pick_list.json @@ -18,6 +18,7 @@ "parent_warehouse", "consider_rejected_warehouses", "get_item_locations", + "pick_manually", "section_break_6", "scan_barcode", "column_break_13", @@ -192,11 +193,22 @@ "fieldname": "consider_rejected_warehouses", "fieldtype": "Check", "label": "Consider Rejected Warehouses" + }, + { + "default": "0", + "description": "If enabled then system won't override the picked qty / batches / serial numbers.", + "fieldname": "pick_manually", + "fieldtype": "Check", + "label": "Pick Manually" } ], "is_submittable": 1, "links": [], +<<<<<<< HEAD "modified": "2024-02-02 16:17:44.877426", +======= + "modified": "2024-03-27 22:49:16.954637", +>>>>>>> 50dd9fa8a3 (feat: allow to pick manually qty / batches / serial nos) "modified_by": "Administrator", "module": "Stock", "name": "Pick List", diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 4eab7e8a0cf..e6fb984785d 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -42,6 +42,7 @@ class PickList(Document): amended_from: DF.Link | None company: DF.Link + consider_rejected_warehouses: DF.Check customer: DF.Link | None customer_name: DF.Data | None for_qty: DF.Float @@ -50,6 +51,7 @@ class PickList(Document): material_request: DF.Link | None naming_series: DF.Literal["STO-PICK-.YYYY.-"] parent_warehouse: DF.Link | None + pick_manually: DF.Check prompt_qty: DF.Check purpose: DF.Literal["Material Transfer for Manufacture", "Material Transfer", "Delivery"] scan_barcode: DF.Data | None @@ -71,7 +73,8 @@ class PickList(Document): def before_save(self): self.update_status() - self.set_item_locations() + if not self.pick_manually: + self.set_item_locations() if self.get("locations"): self.validate_sales_order_percentage() From 52c4157723663e638068493fb15c0208e9f8531d Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 2 Apr 2024 22:23:22 +0530 Subject: [PATCH 33/33] chore: fix conflicts --- erpnext/stock/doctype/pick_list/pick_list.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json index 927d1a61edc..0e466e44d1b 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.json +++ b/erpnext/stock/doctype/pick_list/pick_list.json @@ -204,11 +204,7 @@ ], "is_submittable": 1, "links": [], -<<<<<<< HEAD - "modified": "2024-02-02 16:17:44.877426", -======= "modified": "2024-03-27 22:49:16.954637", ->>>>>>> 50dd9fa8a3 (feat: allow to pick manually qty / batches / serial nos) "modified_by": "Administrator", "module": "Stock", "name": "Pick List",