From e278688a4bff5daf32fd8eff10555941b43b015d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 11 Apr 2024 11:30:56 +0530 Subject: [PATCH 01/31] fix: Allow updating cost center and project for repostable doctypes (cherry picked from commit c3845ac0f1f726ae8f5d9f20211b6f50955e997e) # Conflicts: # erpnext/accounts/doctype/payment_entry/payment_entry.json # erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json # erpnext/accounts/doctype/sales_invoice/sales_invoice.json --- .../doctype/accounting_dimension/accounting_dimension.js | 5 ++++- erpnext/accounts/doctype/payment_entry/payment_entry.json | 8 +++++++- .../doctype/purchase_invoice/purchase_invoice.json | 6 ++++++ .../accounts/doctype/purchase_invoice/purchase_invoice.py | 8 ++------ erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 6 ++++++ erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 8 ++------ 6 files changed, 27 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js index cd883e5bf72..4e45dede1d5 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js @@ -57,9 +57,12 @@ frappe.ui.form.on("Accounting Dimension", { } }, + label: function (frm) { + frm.set_value("fieldname", frappe.model.scrub(frm.doc.label)); + }, + document_type: function (frm) { frm.set_value("label", frm.doc.document_type); - frm.set_value("fieldname", frappe.model.scrub(frm.doc.document_type)); frappe.db.get_value( "Accounting Dimension", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index a37a6fe5465..4808c37ef02 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -150,6 +150,7 @@ "reqd": 1 }, { + "allow_on_submit": 1, "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", @@ -476,6 +477,7 @@ "label": "More Information" }, { + "allow_on_submit": 1, "fieldname": "project", "fieldtype": "Link", "label": "Project", @@ -776,7 +778,11 @@ "table_fieldname": "payment_entries" } ], +<<<<<<< HEAD "modified": "2024-01-03 12:46:41.759121", +======= + "modified": "2024-04-11 11:25:07.366347", +>>>>>>> c3845ac0f1 (fix: Allow updating cost center and project for repostable doctypes) "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", @@ -822,4 +828,4 @@ "states": [], "title_field": "title", "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 9e8bf78a2f1..7f89594ca70 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -299,6 +299,7 @@ "remember_last_selected_value": 1 }, { + "allow_on_submit": 1, "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", @@ -1367,6 +1368,7 @@ "read_only": 1 }, { + "allow_on_submit": 1, "fieldname": "project", "fieldtype": "Link", "label": "Project", @@ -1637,7 +1639,11 @@ "idx": 204, "is_submittable": 1, "links": [], +<<<<<<< HEAD "modified": "2024-03-20 15:57:00.736868", +======= + "modified": "2024-04-11 11:28:42.802211", +>>>>>>> c3845ac0f1 (fix: Allow updating cost center and project for repostable doctypes) "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index aa7573e21c1..dbc9ab474d2 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -68,15 +68,11 @@ class PurchaseInvoice(BuyingController): from erpnext.accounts.doctype.purchase_invoice_advance.purchase_invoice_advance import ( PurchaseInvoiceAdvance, ) - from erpnext.accounts.doctype.purchase_invoice_item.purchase_invoice_item import ( - PurchaseInvoiceItem, - ) + from erpnext.accounts.doctype.purchase_invoice_item.purchase_invoice_item import PurchaseInvoiceItem from erpnext.accounts.doctype.purchase_taxes_and_charges.purchase_taxes_and_charges import ( PurchaseTaxesandCharges, ) - from erpnext.accounts.doctype.tax_withheld_vouchers.tax_withheld_vouchers import ( - TaxWithheldVouchers, - ) + from erpnext.accounts.doctype.tax_withheld_vouchers.tax_withheld_vouchers import TaxWithheldVouchers from erpnext.buying.doctype.purchase_receipt_item_supplied.purchase_receipt_item_supplied import ( PurchaseReceiptItemSupplied, ) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 4a3e1758752..b1688eabe01 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -289,6 +289,7 @@ "read_only": 1 }, { + "allow_on_submit": 1, "fieldname": "project", "fieldtype": "Link", "hide_days": 1, @@ -354,6 +355,7 @@ "reqd": 1 }, { + "allow_on_submit": 1, "fieldname": "cost_center", "fieldtype": "Link", "hide_days": 1, @@ -2185,7 +2187,11 @@ "link_fieldname": "consolidated_invoice" } ], +<<<<<<< HEAD "modified": "2024-03-22 17:50:34.395602", +======= + "modified": "2024-04-11 11:30:26.272441", +>>>>>>> c3845ac0f1 (fix: Allow updating cost center and project for repostable doctypes) "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 530bea416bf..abdfcaeb512 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -55,13 +55,9 @@ class SalesInvoice(SellingController): from erpnext.accounts.doctype.payment_schedule.payment_schedule import PaymentSchedule from erpnext.accounts.doctype.pricing_rule_detail.pricing_rule_detail import PricingRuleDetail - from erpnext.accounts.doctype.sales_invoice_advance.sales_invoice_advance import ( - SalesInvoiceAdvance, - ) + from erpnext.accounts.doctype.sales_invoice_advance.sales_invoice_advance import SalesInvoiceAdvance from erpnext.accounts.doctype.sales_invoice_item.sales_invoice_item import SalesInvoiceItem - from erpnext.accounts.doctype.sales_invoice_payment.sales_invoice_payment import ( - SalesInvoicePayment, - ) + from erpnext.accounts.doctype.sales_invoice_payment.sales_invoice_payment import SalesInvoicePayment from erpnext.accounts.doctype.sales_invoice_timesheet.sales_invoice_timesheet import ( SalesInvoiceTimesheet, ) From 5f41036f4a74076bb80df270171a6c0fb6f84f9a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 17 Apr 2024 11:38:13 +0530 Subject: [PATCH 02/31] chore: resolve conflicts --- erpnext/accounts/doctype/payment_entry/payment_entry.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 4808c37ef02..d6ba193aa0e 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -778,11 +778,7 @@ "table_fieldname": "payment_entries" } ], -<<<<<<< HEAD - "modified": "2024-01-03 12:46:41.759121", -======= "modified": "2024-04-11 11:25:07.366347", ->>>>>>> c3845ac0f1 (fix: Allow updating cost center and project for repostable doctypes) "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", @@ -828,4 +824,4 @@ "states": [], "title_field": "title", "track_changes": 1 -} \ No newline at end of file +} From 6c10783823bc9adbea25c9cd76a46d64867eac17 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 17 Apr 2024 11:47:52 +0530 Subject: [PATCH 03/31] chore: resolve conflicts --- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index b1688eabe01..51da848d9da 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -2187,11 +2187,7 @@ "link_fieldname": "consolidated_invoice" } ], -<<<<<<< HEAD - "modified": "2024-03-22 17:50:34.395602", -======= "modified": "2024-04-11 11:30:26.272441", ->>>>>>> c3845ac0f1 (fix: Allow updating cost center and project for repostable doctypes) "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", @@ -2246,4 +2242,4 @@ "title_field": "title", "track_changes": 1, "track_seen": 1 -} \ No newline at end of file +} From 116a429c453cf46719a9050cd0ba73afe3ba2a69 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 17 Apr 2024 12:09:05 +0530 Subject: [PATCH 04/31] chore: resolve conflicts --- .../accounts/doctype/purchase_invoice/purchase_invoice.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 7f89594ca70..cf08d08ce4b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1639,11 +1639,7 @@ "idx": 204, "is_submittable": 1, "links": [], -<<<<<<< HEAD - "modified": "2024-03-20 15:57:00.736868", -======= "modified": "2024-04-11 11:28:42.802211", ->>>>>>> c3845ac0f1 (fix: Allow updating cost center and project for repostable doctypes) "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", From c10c21157db10b2892c8b1c7e9bd40f954e7a92c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 17 Apr 2024 12:43:31 +0530 Subject: [PATCH 05/31] fix: duplicate serial and batch bundle in stock entry and stock reco (cherry picked from commit 732b6e14176ff3b5fd5205d5b6f11463934ec6c4) --- erpnext/public/js/utils.js | 33 +------------------ .../stock/doctype/stock_entry/stock_entry.py | 1 + .../stock_reconciliation.py | 1 + 3 files changed, 3 insertions(+), 32 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 5456b6553ae..42d4cc51c40 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -51,38 +51,7 @@ $.extend(erpnext, { }, setup_serial_or_batch_no: function () { - let grid_row = cur_frm.open_grid_row(); - if ( - !grid_row || - !grid_row.grid_form.fields_dict.serial_no || - grid_row.grid_form.fields_dict.serial_no.get_status() !== "Write" - ) - return; - - frappe.model.get_value( - "Item", - { name: grid_row.doc.item_code }, - ["has_serial_no", "has_batch_no"], - ({ has_serial_no, has_batch_no }) => { - Object.assign(grid_row.doc, { has_serial_no, has_batch_no }); - - if (has_serial_no) { - attach_selector_button( - __("Add Serial No"), - grid_row.grid_form.fields_dict.serial_no.$wrapper, - this, - grid_row - ); - } else if (has_batch_no) { - attach_selector_button( - __("Pick Batch No"), - grid_row.grid_form.fields_dict.batch_no.$wrapper, - this, - grid_row - ); - } - } - ); + // Deprecated in v15 }, route_to_adjustment_jv: (args) => { diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 0ba1b56272d..569d4e9c0a0 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -194,6 +194,7 @@ class StockEntry(StockController): if self.work_order: self.pro_doc = frappe.get_doc("Work Order", self.work_order) + self.validate_duplicate_serial_and_batch_bundle("items") self.validate_posting_time() self.validate_purpose() self.validate_item() diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index ccd7f64e6e7..64a6b6503cc 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -70,6 +70,7 @@ class StockReconciliation(StockController): self.validate_posting_time() self.set_current_serial_and_batch_bundle() self.set_new_serial_and_batch_bundle() + self.validate_duplicate_serial_and_batch_bundle("items") self.remove_items_with_no_change() self.validate_data() self.validate_expense_account() From 1ae447e4fc62e8a8da18d68a1c02e483b7b86571 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 17 Apr 2024 17:30:43 +0530 Subject: [PATCH 06/31] fix: do not add qty to supplied items (backport #41061) (#41066) fix: do not add qty to supplied items (cherry picked from commit 8233c392fb93bc0ee816249cb231f6731f2eb615) Co-authored-by: s-aga-r --- erpnext/controllers/subcontracting_controller.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index fc66345aeee..fb1ed792b5b 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -539,6 +539,9 @@ class SubcontractingController(StockController): return serial_nos def __add_supplied_item(self, item_row, bom_item, qty): + if bom_item.get("qty"): + bom_item.pop("qty") + bom_item.conversion_factor = item_row.conversion_factor rm_obj = self.append(self.raw_material_table, bom_item) if rm_obj.get("qty"): From ee7aaf0ea438d2ad2c78e104212bb5f3c287d529 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 17 Apr 2024 17:53:43 +0200 Subject: [PATCH 07/31] fix: don't attempt to set gender from salutation (backport #40997) (#41073) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> fix: don't attempt to set gender from salutation (#40997) --- erpnext/setup/doctype/employee/employee.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/erpnext/setup/doctype/employee/employee.js b/erpnext/setup/doctype/employee/employee.js index d165d429f44..7a1efa82fa0 100755 --- a/erpnext/setup/doctype/employee/employee.js +++ b/erpnext/setup/doctype/employee/employee.js @@ -18,18 +18,6 @@ erpnext.setup.EmployeeController = class EmployeeController extends frappe.ui.fo refresh() { erpnext.toggle_naming_series(); } - - salutation() { - if (this.frm.doc.salutation) { - this.frm.set_value( - "gender", - { - Mr: "Male", - Ms: "Female", - }[this.frm.doc.salutation] - ); - } - } }; frappe.ui.form.on("Employee", { From 93242ca883520553676cb8d913c9d1187ee76e50 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 18 Apr 2024 07:53:20 +0530 Subject: [PATCH 08/31] fix: validate uom is integer for PR item (backport #41074) (#41077) fix: validate uom is integer for PR item (cherry picked from commit 9a290fdfc969b1b5652d8c68ae6c146d1b21a79a) Co-authored-by: s-aga-r --- erpnext/controllers/subcontracting_controller.py | 3 --- .../stock/doctype/purchase_receipt/purchase_receipt.py | 7 +++++-- erpnext/utilities/transaction_base.py | 9 +++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index fb1ed792b5b..fc66345aeee 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -539,9 +539,6 @@ class SubcontractingController(StockController): return serial_nos def __add_supplied_item(self, item_row, bom_item, qty): - if bom_item.get("qty"): - bom_item.pop("qty") - bom_item.conversion_factor = item_row.conversion_factor rm_obj = self.append(self.raw_material_table, bom_item) if rm_obj.get("qty"): diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 6dc92772d8c..2e751ad5251 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -238,8 +238,7 @@ class PurchaseReceipt(BuyingController): self.po_required() self.validate_items_quality_inspection() self.validate_with_previous_doc() - self.validate_uom_is_integer("uom", ["qty", "received_qty"]) - self.validate_uom_is_integer("stock_uom", "stock_qty") + self.validate_uom_is_integer() self.validate_cwip_accounts() self.validate_provisional_expense_account() @@ -253,6 +252,10 @@ class PurchaseReceipt(BuyingController): self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse") self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse") + def validate_uom_is_integer(self): + super().validate_uom_is_integer("uom", ["qty", "received_qty"], "Purchase Receipt Item") + super().validate_uom_is_integer("stock_uom", "stock_qty", "Purchase Receipt Item") + def validate_cwip_accounts(self): for item in self.get("items"): if item.is_fixed_asset and is_cwip_accounting_enabled(item.asset_category): diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index d89095ef3d3..3b7812f96c2 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -30,8 +30,8 @@ class TransactionBase(StatusUpdater): except ValueError: frappe.throw(_("Invalid Posting Time")) - def validate_uom_is_integer(self, uom_field, qty_fields): - validate_uom_is_integer(self, uom_field, qty_fields) + def validate_uom_is_integer(self, uom_field, qty_fields, child_dt=None): + validate_uom_is_integer(self, uom_field, qty_fields, child_dt) def validate_with_previous_doc(self, ref): self.exclude_fields = ["conversion_factor", "uom"] if self.get("is_return") else [] @@ -210,12 +210,13 @@ def validate_uom_is_integer(doc, uom_field, qty_fields, child_dt=None): for f in qty_fields: qty = d.get(f) if qty: - if abs(cint(qty) - flt(qty, d.precision(f))) > 0.0000001: + precision = d.precision(f) + if abs(cint(qty) - flt(qty, precision)) > 0.0000001: frappe.throw( _( "Row {1}: Quantity ({0}) cannot be a fraction. To allow this, disable '{2}' in UOM {3}." ).format( - flt(qty, d.precision(f)), + flt(qty, precision), d.idx, frappe.bold(_("Must be Whole Number")), frappe.bold(d.get(uom_field)), From ef2553edf967612bdbf580357d5886c6afacaea2 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 18 Apr 2024 14:24:51 +0530 Subject: [PATCH 09/31] fix: validation for fraction number in Work Order (cherry picked from commit f8305c2fc049e99729c8980cb0994342e8805f50) --- .../doctype/work_order/work_order.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index e13a07b83e8..904e42a6a03 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -948,6 +948,21 @@ class WorkOrder(Document): if not self.qty > 0: frappe.throw(_("Quantity to Manufacture must be greater than 0.")) + if ( + self.stock_uom + and frappe.get_cached_value("UOM", self.stock_uom, "must_be_whole_number") + and abs(cint(self.qty) - flt(self.qty, self.precision("qty"))) > 0.0000001 + ): + frappe.throw( + _( + "Qty To Manufacture ({0}) cannot be a fraction for the UOM {2}. To allow this, disable '{1}' in the UOM {2}." + ).format( + flt(self.qty, self.precision("qty")), + frappe.bold(_("Must be Whole Number")), + frappe.bold(self.stock_uom), + ), + ) + if self.production_plan and self.production_plan_item and not self.production_plan_sub_assembly_item: qty_dict = frappe.db.get_value( "Production Plan Item", self.production_plan_item, ["planned_qty", "ordered_qty"], as_dict=1 From 572e844a917067095c2a52688ca0d70d56adca46 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 17 Jan 2024 13:09:51 +0530 Subject: [PATCH 10/31] fix: account and stock manager read perm --- erpnext/accounts/doctype/fiscal_year/fiscal_year.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.json b/erpnext/accounts/doctype/fiscal_year/fiscal_year.json index da7f08fe3d4..fb997847448 100644 --- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.json +++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.json @@ -87,7 +87,7 @@ "module": "Accounts", "name": "Fiscal Year", "naming_rule": "By fieldname", - "owner": "Administrator", + "owner": "Administrator", "permissions": [ { "create": 1, @@ -119,6 +119,14 @@ { "read": 1, "role": "Employee" + }, + { + "read": 1, + "role": "Accounts Manager" + }, + { + "read": 1, + "role": "Stock Manager" } ], "show_name_in_global_search": 1, From 85796b35348d0bbce3c0cfd78096c683e01ec29c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 17 Apr 2024 17:07:19 +0530 Subject: [PATCH 11/31] fix: validation for zero qty in SABB (cherry picked from commit 497f560b4b08e8722d80a4ebb11f66e0ae0c9299) --- .../assets/doctype/asset_repair/test_asset_repair.py | 1 + erpnext/stock/doctype/batch/test_batch.py | 4 ++++ .../purchase_receipt/test_purchase_receipt.py | 1 + .../serial_and_batch_bundle.py | 12 ++++++++++++ .../stock_ledger_entry/test_stock_ledger_entry.py | 2 +- .../test_stock_reconciliation.py | 2 +- 6 files changed, 20 insertions(+), 2 deletions(-) diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index 3a5acbe0322..278da1b08bf 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -305,6 +305,7 @@ def create_asset_repair(**args): "serial_nos": args.serial_no, "posting_date": today(), "posting_time": nowtime(), + "do_not_submit": 1, } ) ).name diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py index 3f37b7bf098..3ef0e57c25a 100644 --- a/erpnext/stock/doctype/batch/test_batch.py +++ b/erpnext/stock/doctype/batch/test_batch.py @@ -86,6 +86,7 @@ class TestBatch(FrappeTestCase): "batches": frappe._dict({batch_no: 20}), "type_of_transaction": "Inward", "company": receipt.company, + "do_not_submit": 1, } ) .make_serial_and_batch_bundle() @@ -176,6 +177,7 @@ class TestBatch(FrappeTestCase): "batches": frappe._dict({batch_no: batch_qty}), "type_of_transaction": "Outward", "company": receipt.company, + "do_not_submit": 1, } ) .make_serial_and_batch_bundle() @@ -249,6 +251,7 @@ class TestBatch(FrappeTestCase): "batches": frappe._dict({batch_no: batch_qty}), "type_of_transaction": "Outward", "company": receipt.company, + "do_not_submit": 1, } ) .make_serial_and_batch_bundle() @@ -341,6 +344,7 @@ class TestBatch(FrappeTestCase): "batches": frappe._dict({batch_name: 90}), "type_of_transaction": "Inward", "company": "_Test Company", + "do_not_submit": 1, } ).make_serial_and_batch_bundle() diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index e67736253cd..54a695126c7 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2968,6 +2968,7 @@ def make_purchase_receipt(**args): "serial_nos": serial_nos, "posting_date": args.posting_date or today(), "posting_time": args.posting_time, + "do_not_submit": 1, } ) ).name 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 286a220c5dd..91951834bf9 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 @@ -596,6 +596,13 @@ class SerialandBatchBundle(Document): serial_batches = {} for row in self.entries: + if not row.qty and row.batch_no and not row.serial_no: + frappe.throw( + _("At row {0}: Qty is mandatory for the batch {1}").format( + bold(row.idx), bold(row.batch_no) + ) + ) + if self.has_serial_no and not row.serial_no: frappe.throw( _("At row {0}: Serial No is mandatory for Item {1}").format( @@ -831,7 +838,12 @@ class SerialandBatchBundle(Document): for batch in batches: frappe.db.set_value("Batch", batch.name, {"reference_name": None, "reference_doctype": None}) + def validate_serial_and_batch_data(self): + if not self.voucher_no: + frappe.throw(_("Voucher No is mandatory")) + def before_submit(self): + self.validate_serial_and_batch_data() self.validate_serial_and_batch_no_for_returned() self.set_purchase_document_no() diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index ce50155ffd5..069192fdb16 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -1483,7 +1483,7 @@ def create_delivery_note_entries_for_batchwise_item_valuation_test(dn_entry_list "posting_date": dn.posting_date, "posting_time": dn.posting_time, "voucher_type": "Delivery Note", - "do_not_submit": dn.name, + "do_not_submit": 1, } ) ).name diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 0ffcdd55fc8..b31ca3046bd 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -1085,7 +1085,7 @@ def create_stock_reconciliation(**args): ) bundle_id = None - if not args.use_serial_batch_fields and (args.batch_no or args.serial_no): + if not args.use_serial_batch_fields and (args.batch_no or args.serial_no) and args.qty: batches = frappe._dict({}) if args.batch_no: batches[args.batch_no] = args.qty From d7ddb00e86fbd0849deb256bf972e49a274e880c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 18 Apr 2024 15:26:03 +0530 Subject: [PATCH 12/31] fix: Permission for lower dedcution certificate (cherry picked from commit f6f118855bb45f54c08229d480a831303746ca70) # Conflicts: # erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json --- .../lower_deduction_certificate.json | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json index d332b4e76bd..044471a86e3 100644 --- a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json +++ b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json @@ -135,14 +135,60 @@ ], "index_web_pages_for_search": 1, "links": [], +<<<<<<< HEAD "modified": "2023-04-18 08:25:35.302081", +======= + "modified": "2024-04-18 15:25:25.808355", +>>>>>>> f6f118855b (fix: Permission for lower dedcution certificate) "modified_by": "Administrator", "module": "Regional", "name": "Lower Deduction Certificate", "naming_rule": "By fieldname", "owner": "Administrator", +<<<<<<< HEAD "permissions": [], "sort_field": "modified", +======= + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "write": 1 + } + ], + "sort_field": "creation", +>>>>>>> f6f118855b (fix: Permission for lower dedcution certificate) "sort_order": "DESC", "states": [], "track_changes": 1 From 74ed656bb93e6027df6682f3c96e49077ddfbec6 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 19 Apr 2024 17:30:27 +0530 Subject: [PATCH 13/31] fix: balance qty for stock ledger report (cherry picked from commit f00ae0b92b4f2f82a5164403ac7f56e04e37fbe0) --- erpnext/stock/report/stock_ledger/stock_ledger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index 97e2f5579d7..04cd1b846b3 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -61,6 +61,8 @@ def execute(filters=None): actual_qty += flt(sle.actual_qty, precision) stock_value += sle.stock_value_difference batch_balance_dict[sle.batch_no] += sle.actual_qty + if filters.get("segregate_serial_batch_bundle"): + actual_qty = batch_balance_dict[sle.batch_no] if sle.voucher_type == "Stock Reconciliation" and not sle.actual_qty: actual_qty = sle.qty_after_transaction From 80891daaed57b28c86df2184a32c153bc0714fb5 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 19 Apr 2024 15:34:26 +0530 Subject: [PATCH 14/31] fix: not able to update default supplier from Supplier Quotation Comparison report (cherry picked from commit ad8e189c2630244f5423d1f26bf442b5e636d9c2) --- .../supplier_quotation_comparison.js | 31 ++++++++++++++++--- .../supplier_quotation_comparison.py | 10 ++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js index f7d0d947b61..9701e147f05 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js @@ -133,6 +133,13 @@ frappe.query_reports["Supplier Quotation Comparison"] = { return row.supplier_name; }); + let items = []; + report.data.forEach((d) => { + if (!items.includes(d.item_code)) { + items.push(d.item_code); + } + }); + // Create a dialog window for the user to pick their supplier let dialog = new frappe.ui.Dialog({ title: __("Select Default Supplier"), @@ -151,20 +158,34 @@ frappe.query_reports["Supplier Quotation Comparison"] = { }; }, }, + { + reqd: 1, + label: "Item", + fieldtype: "Link", + options: "Item", + fieldname: "item_code", + get_query: () => { + return { + filters: { + name: ["in", items], + }, + }; + }, + }, ], }); dialog.set_primary_action(__("Set Default Supplier"), () => { let values = dialog.get_values(); + if (values) { // Set the default_supplier field of the appropriate Item to the selected supplier frappe.call({ - method: "frappe.client.set_value", + method: "erpnext.buying.report.supplier_quotation_comparison.supplier_quotation_comparison.set_default_supplier", args: { - doctype: "Item", - name: item_code, - fieldname: "default_supplier", - value: values.supplier, + item_code: values.item_code, + supplier: values.supplier, + company: filters.company, }, freeze: true, callback: (r) => { diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py index 684cd3a0f9e..085f30f84d9 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py @@ -292,3 +292,13 @@ def get_message(): Expires today / Already Expired """ + + +@frappe.whitelist() +def set_default_supplier(item_code, supplier, company): + frappe.db.set_value( + "Item Default", + {"parent": item_code, "company": company}, + "default_supplier", + supplier, + ) From 544e56a71cac8751f5ff9fb7c32fe3a0e9f3eb85 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 2 Aug 2023 12:47:12 +0530 Subject: [PATCH 15/31] fix: search not working for so in the Production Plan (#36459) fix: search not working for so (cherry picked from commit 8c57d56240d2c35ae09350af5d3598b4773e9ac2) (cherry picked from commit 9a6e762b8b7b50ff43e850c8067ba01c290e2542) --- .../manufacturing/doctype/production_plan/production_plan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 80b156487bd..a378d8ae606 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -1896,7 +1896,7 @@ def sales_order_query(doctype=None, txt=None, searchfield=None, start=None, page query = query.where(so_table.name.isin(filters.get("sales_orders"))) if txt: - query = query.where(table.item_code.like(f"{txt}%")) + query = query.where(table.parent.like(f"%{txt}%")) if page_len: query = query.limit(page_len) From c72478c74d48e5a5b7984633be0380bc3302ca90 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 29 Mar 2024 17:22:48 +0530 Subject: [PATCH 16/31] fix: do not add actual expense twice for validating budget (cherry picked from commit af26ac96e98d7ec6791676fb2bd6860a6332fcd9) --- erpnext/accounts/doctype/budget/budget.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py index d7545ef1520..b0fddbd9365 100644 --- a/erpnext/accounts/doctype/budget/budget.py +++ b/erpnext/accounts/doctype/budget/budget.py @@ -298,12 +298,10 @@ def get_amount(args, budget): amount = 0 if args.get("doctype") == "Material Request" and budget.for_material_request: - amount = ( - get_requested_amount(args, budget) + get_ordered_amount(args, budget) + get_actual_expense(args) - ) + amount = get_requested_amount(args, budget) + get_ordered_amount(args, budget) elif args.get("doctype") == "Purchase Order" and budget.for_purchase_order: - amount = get_ordered_amount(args, budget) + get_actual_expense(args) + amount = get_ordered_amount(args, budget) return amount From e047e1eb15e5f19cf741b8f59af34a31bdfa1dc2 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sun, 14 Apr 2024 17:03:22 +0530 Subject: [PATCH 17/31] feat: show expense breakup (cherry picked from commit 59292a09c4bdf79341c37c00edc177f401404f4f) --- erpnext/accounts/doctype/budget/budget.py | 133 ++++++++++++++++++---- 1 file changed, 109 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py index b0fddbd9365..3171a7cea06 100644 --- a/erpnext/accounts/doctype/budget/budget.py +++ b/erpnext/accounts/doctype/budget/budget.py @@ -219,12 +219,18 @@ def validate_expense_against_budget(args, expense_amount=0): def validate_budget_records(args, budget_records, expense_amount): for budget in budget_records: if flt(budget.budget_amount): - amount = expense_amount or get_amount(args, budget) yearly_action, monthly_action = get_actions(args, budget) + args["for_material_request"] = budget.for_material_request + args["for_purchase_order"] = budget.for_purchase_order if yearly_action in ("Stop", "Warn"): compare_expense_with_budget( - args, flt(budget.budget_amount), _("Annual"), yearly_action, budget.budget_against, amount + args, + flt(budget.budget_amount), + _("Annual"), + yearly_action, + budget.budget_against, + expense_amount, ) if monthly_action in ["Stop", "Warn"]: @@ -240,18 +246,28 @@ def validate_budget_records(args, budget_records, expense_amount): _("Accumulated Monthly"), monthly_action, budget.budget_against, - amount, + expense_amount, ) def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against, amount=0): - actual_expense = get_actual_expense(args) - total_expense = actual_expense + amount + args.actual_expense = get_actual_expense(args) + args.requested_amount, args.ordered_amount = 0, 0 + if not amount: + args.requested_amount, args.ordered_amount = get_requested_amount(args), get_ordered_amount(args) + + if args.get("doctype") == "Material Request" and args.for_material_request: + amount = args.requested_amount + args.ordered_amount + + elif args.get("doctype") == "Purchase Order" and args.for_purchase_order: + amount = args.ordered_amount + + total_expense = args.actual_expense + amount if total_expense > budget_amount: - if actual_expense > budget_amount: + if args.actual_expense > budget_amount: error_tense = _("is already") - diff = actual_expense - budget_amount + diff = args.actual_expense - budget_amount else: error_tense = _("will be") diff = total_expense - budget_amount @@ -268,6 +284,8 @@ def compare_expense_with_budget(args, budget_amount, action_for, action, budget_ frappe.bold(fmt_money(diff, currency=currency)), ) + msg += get_expense_breakup(args, currency, budget_against) + if frappe.flags.exception_approver_role and frappe.flags.exception_approver_role in frappe.get_roles( frappe.session.user ): @@ -279,6 +297,85 @@ def compare_expense_with_budget(args, budget_amount, action_for, action, budget_ frappe.msgprint(msg, indicator="orange", title=_("Budget Exceeded")) +def get_expense_breakup(args, currency, budget_against): + msg = "
Total Expenses booked through -
    " + + common_filters = frappe._dict( + { + args.budget_against_field: budget_against, + "account": args.account, + "company": args.company, + } + ) + + msg += ( + "
  • " + + frappe.utils.get_link_to_report( + "General Ledger", + label="Actual Expenses", + filters=common_filters.copy().update( + { + "from_date": frappe.get_cached_value("Fiscal Year", args.fiscal_year, "year_start_date"), + "to_date": frappe.get_cached_value("Fiscal Year", args.fiscal_year, "year_end_date"), + "is_cancelled": 0, + } + ), + ) + + " - " + + frappe.bold(fmt_money(args.actual_expense, currency=currency)) + + "
  • " + ) + + msg += ( + "
  • " + + frappe.utils.get_link_to_report( + "Material Request", + label="Material Requests", + report_type="Report Builder", + doctype="Material Request", + filters=common_filters.copy().update( + { + "status": [["!=", "Stopped"]], + "docstatus": 1, + "material_request_type": "Purchase", + "schedule_date": [["fiscal year", "2023-2024"]], + "item_code": args.item_code, + "per_ordered": [["<", 100]], + } + ), + ) + + " - " + + frappe.bold(fmt_money(args.requested_amount, currency=currency)) + + "
  • " + ) + + msg += ( + "
  • " + + frappe.utils.get_link_to_report( + "Purchase Order", + label="Unbilled Orders", + report_type="Report Builder", + doctype="Purchase Order", + filters=common_filters.copy().update( + { + "status": [["!=", "Closed"]], + "docstatus": 1, + "transaction_date": [["fiscal year", "2023-2024"]], + "item_code": args.item_code, + "per_billed": [["<", 100]], + } + ), + ) + + " - " + + frappe.bold(fmt_money(args.ordered_amount, currency=currency)) + + "
  • " + ) + + msg += "
" + + return msg + + def get_actions(args, budget): yearly_action = budget.action_if_annual_budget_exceeded monthly_action = budget.action_if_accumulated_monthly_budget_exceeded @@ -294,21 +391,9 @@ def get_actions(args, budget): return yearly_action, monthly_action -def get_amount(args, budget): - amount = 0 - - if args.get("doctype") == "Material Request" and budget.for_material_request: - amount = get_requested_amount(args, budget) + get_ordered_amount(args, budget) - - elif args.get("doctype") == "Purchase Order" and budget.for_purchase_order: - amount = get_ordered_amount(args, budget) - - return amount - - -def get_requested_amount(args, budget): +def get_requested_amount(args): item_code = args.get("item_code") - condition = get_other_condition(args, budget, "Material Request") + condition = get_other_condition(args, "Material Request") data = frappe.db.sql( """ select ifnull((sum(child.stock_qty - child.ordered_qty) * rate), 0) as amount @@ -322,9 +407,9 @@ def get_requested_amount(args, budget): return data[0][0] if data else 0 -def get_ordered_amount(args, budget): +def get_ordered_amount(args): item_code = args.get("item_code") - condition = get_other_condition(args, budget, "Purchase Order") + condition = get_other_condition(args, "Purchase Order") data = frappe.db.sql( f""" select ifnull(sum(child.amount - child.billed_amt), 0) as amount @@ -338,7 +423,7 @@ def get_ordered_amount(args, budget): return data[0][0] if data else 0 -def get_other_condition(args, budget, for_doc): +def get_other_condition(args, for_doc): condition = "expense_account = '%s'" % (args.expense_account) budget_against_field = args.get("budget_against_field") From ba99bc5fffa15ddcbc478bf3acaa1d9334ac0e91 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sun, 14 Apr 2024 17:06:01 +0530 Subject: [PATCH 18/31] refactor: show list for expense breakup (cherry picked from commit 9a12376e29d32ff7acfcd187d8ae52c2eeb323e9) --- erpnext/accounts/doctype/budget/budget.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py index 3171a7cea06..11f78ae1763 100644 --- a/erpnext/accounts/doctype/budget/budget.py +++ b/erpnext/accounts/doctype/budget/budget.py @@ -251,8 +251,7 @@ def validate_budget_records(args, budget_records, expense_amount): def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against, amount=0): - args.actual_expense = get_actual_expense(args) - args.requested_amount, args.ordered_amount = 0, 0 + args.actual_expense, args.requested_amount, args.ordered_amount = get_actual_expense(args), 0, 0 if not amount: args.requested_amount, args.ordered_amount = get_requested_amount(args), get_ordered_amount(args) @@ -368,11 +367,9 @@ def get_expense_breakup(args, currency, budget_against): ) + " - " + frappe.bold(fmt_money(args.ordered_amount, currency=currency)) - + "" + + "" ) - msg += "" - return msg From 83931e872b71bbae6a47693ba26065f9a4ac0e25 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 31 Mar 2024 20:08:39 +0530 Subject: [PATCH 19/31] fix: Party type in Payment Order (cherry picked from commit 91fa41c9ec61b4ff45ada46c396dc3b8a779441e) --- erpnext/accounts/doctype/payment_order/payment_order.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/doctype/payment_order/payment_order.js b/erpnext/accounts/doctype/payment_order/payment_order.js index f009de59592..4033fc08233 100644 --- a/erpnext/accounts/doctype/payment_order/payment_order.js +++ b/erpnext/accounts/doctype/payment_order/payment_order.js @@ -71,6 +71,7 @@ frappe.ui.form.on("Payment Order", { target: frm, date_field: "posting_date", setters: { + party_type: "Supplier", party: frm.doc.supplier || "", }, get_query_filters: { @@ -91,6 +92,7 @@ frappe.ui.form.on("Payment Order", { source_doctype: "Payment Request", target: frm, setters: { + party_type: "Supplier", party: frm.doc.supplier || "", }, get_query_filters: { From 060d46af42679dc571cd4599054a02c5037add00 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 19 Apr 2024 17:38:11 +0530 Subject: [PATCH 20/31] fix: Payment entry against employee (cherry picked from commit 93e6c6ccab02f373c1dbf7aac9c95d63b36f93c9) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 +++- erpnext/accounts/doctype/payment_entry/test_payment_entry.py | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 0d1206b0cc7..9a4fbe10bd1 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -2028,6 +2028,8 @@ def get_negative_outstanding_invoices( @frappe.whitelist() def get_party_details(company, party_type, party, date, cost_center=None): bank_account = "" + party_bank_account = "" + if not frappe.db.exists(party_type, party): frappe.throw(_("{0} {1} does not exist").format(_(party_type), party)) @@ -2039,8 +2041,8 @@ def get_party_details(company, party_type, party, date, cost_center=None): party_balance = get_balance_on(party_type=party_type, party=party, cost_center=cost_center) if party_type in ["Customer", "Supplier"]: party_bank_account = get_party_bank_account(party_type, party) + bank_account = get_default_company_bank_account(company, party_type, party) - bank_account = get_default_company_bank_account(company, party_type, party) return { "party_account": party_account, "party_name": party_name, diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 3e24776cea7..cafdaaaa957 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -10,6 +10,7 @@ from frappe.utils import add_days, flt, nowdate from erpnext.accounts.doctype.account.test_account import create_account from erpnext.accounts.doctype.payment_entry.payment_entry import ( get_outstanding_reference_documents, + get_party_details, get_payment_entry, get_reference_details, ) @@ -1684,6 +1685,10 @@ def create_payment_entry(**args): payment_entry.reference_no = "Test001" payment_entry.reference_date = nowdate() + get_party_details( + payment_entry.company, payment_entry.party_type, payment_entry.party, payment_entry.posting_date + ) + if args.get("save"): payment_entry.save() if args.get("submit"): From 3b4575af3d5dc50c713f742d7674ba2e807f0689 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 22 Apr 2024 16:28:31 +0530 Subject: [PATCH 21/31] fix: Missing args while fetching items from delivery note (cherry picked from commit bbe323fbb42db31ae7d7c0a3b9997ca955dee5fa) --- erpnext/public/js/utils.js | 13 ++++++++----- .../stock/doctype/delivery_note/delivery_note.py | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 42d4cc51c40..288b2f6932d 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -906,11 +906,14 @@ erpnext.utils.map_current_doc = function (opts) { if (opts.source_doctype) { let data_fields = []; if (["Purchase Receipt", "Delivery Note"].includes(opts.source_doctype)) { - data_fields.push({ - fieldname: "merge_taxes", - fieldtype: "Check", - label: __("Merge taxes from multiple documents"), - }); + let target_meta = frappe.get_meta(cur_frm.doc.doctype); + if (target_meta.fields.find((f) => f.fieldname === "taxes")) { + data_fields.push({ + fieldname: "merge_taxes", + fieldtype: "Check", + label: __("Merge taxes from multiple documents"), + }); + } } const d = new frappe.ui.form.MultiSelectDialog({ doctype: opts.source_doctype, diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 1337c4eae16..bce87b80c51 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -1099,7 +1099,7 @@ def make_delivery_trip(source_name, target_doc=None): @frappe.whitelist() -def make_installation_note(source_name, target_doc=None): +def make_installation_note(source_name, target_doc=None, kwargs=None): def update_item(obj, target, source_parent): target.qty = flt(obj.qty) - flt(obj.installed_qty) target.serial_no = obj.serial_no From aee03fe2efb02536bcce8c31e5e2d669e04b3568 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 19 Apr 2024 15:11:28 +0530 Subject: [PATCH 22/31] fix: stock reco negative qty validation (cherry picked from commit 289495c30856579c47109054d33c249153c34636) # Conflicts: # erpnext/stock/stock_ledger.py --- .../test_stock_reconciliation.py | 102 ++++++++++++++++++ erpnext/stock/stock_ledger.py | 17 +++ 2 files changed, 119 insertions(+) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index b31ca3046bd..add8b516e83 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -986,6 +986,108 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): active_serial_no = frappe.get_all("Serial No", filters={"status": "Active", "item_code": item_code}) self.assertEqual(len(active_serial_no), 5) + def test_balance_qty_for_batch_with_backdated_stock_reco_and_future_entries(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + item = self.make_item( + "Test Batch Item Original Test", + { + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TEST-BATCH-SRWFEE-.###", + }, + ) + + warehouse = "_Test Warehouse - _TC" + se1 = make_stock_entry( + item_code=item.name, + target=warehouse, + qty=50, + basic_rate=100, + posting_date=add_days(nowdate(), -2), + ) + batch1 = get_batch_from_bundle(se1.items[0].serial_and_batch_bundle) + + se2 = make_stock_entry( + item_code=item.name, + target=warehouse, + qty=50, + basic_rate=100, + posting_date=add_days(nowdate(), -2), + ) + batch2 = get_batch_from_bundle(se2.items[0].serial_and_batch_bundle) + + se3 = make_stock_entry( + item_code=item.name, + target=warehouse, + qty=100, + basic_rate=100, + posting_date=add_days(nowdate(), -2), + ) + batch3 = get_batch_from_bundle(se3.items[0].serial_and_batch_bundle) + + se3 = make_stock_entry( + item_code=item.name, + target=warehouse, + qty=100, + basic_rate=100, + posting_date=nowdate(), + ) + + sle = frappe.get_all( + "Stock Ledger Entry", + filters={ + "item_code": item.name, + "warehouse": warehouse, + "is_cancelled": 0, + "voucher_no": se3.name, + }, + fields=["qty_after_transaction"], + order_by="posting_time desc, creation desc", + ) + + self.assertEqual(flt(sle[0].qty_after_transaction), flt(300.0)) + + sr = create_stock_reconciliation( + item_code=item.name, + warehouse=warehouse, + qty=0, + batch_no=batch1, + posting_date=add_days(nowdate(), -1), + use_serial_batch_fields=1, + do_not_save=1, + ) + + for batch in [batch2, batch3]: + sr.append( + "items", + { + "item_code": item.name, + "warehouse": warehouse, + "qty": 0, + "batch_no": batch, + "use_serial_batch_fields": 1, + }, + ) + + sr.save() + sr.submit() + + sle = frappe.get_all( + "Stock Ledger Entry", + filters={ + "item_code": item.name, + "warehouse": warehouse, + "is_cancelled": 0, + "voucher_no": se3.name, + }, + fields=["qty_after_transaction"], + order_by="posting_time desc, creation desc", + ) + + self.assertEqual(flt(sle[0].qty_after_transaction), flt(100.0)) + def create_batch_item_with_batch(item_name, batch_id): batch_item_doc = create_item(item_name, is_stock_item=1) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index ec4c3682cb2..e42204978c3 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1728,6 +1728,10 @@ def get_stock_reco_qty_shift(args): stock_reco_qty_shift = flt(args.qty_after_transaction) - flt(last_balance) else: stock_reco_qty_shift = flt(args.actual_qty) + + elif args.get("serial_and_batch_bundle"): + stock_reco_qty_shift = flt(args.actual_qty) + else: # reco is being submitted last_balance = get_previous_sle_of_current_voucher(args, "<=", exclude_current_voucher=True).get( @@ -1799,7 +1803,20 @@ def get_datetime_limit_condition(detail): def validate_negative_qty_in_future_sle(args, allow_negative_stock=False): if allow_negative_stock or is_negative_stock_allowed(item_code=args.item_code): return +<<<<<<< HEAD if not (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation"): +======= + + if ( + args.voucher_type == "Stock Reconciliation" + and args.actual_qty < 0 + and args.get("serial_and_batch_bundle") + and frappe.db.get_value("Stock Reconciliation Item", args.voucher_detail_no, "qty") > 0 + ): + return + + if args.actual_qty >= 0 and args.voucher_type != "Stock Reconciliation": +>>>>>>> 289495c308 (fix: stock reco negative qty validation) return neg_sle = get_future_sle_with_negative_qty(args) From b2a0e4b8102a188641eab4188e6f54e6810f6ee2 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 23 Apr 2024 13:17:23 +0530 Subject: [PATCH 23/31] chore: fix conflicts --- erpnext/stock/stock_ledger.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index e42204978c3..2bd00fe55d5 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1803,9 +1803,6 @@ def get_datetime_limit_condition(detail): def validate_negative_qty_in_future_sle(args, allow_negative_stock=False): if allow_negative_stock or is_negative_stock_allowed(item_code=args.item_code): return -<<<<<<< HEAD - if not (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation"): -======= if ( args.voucher_type == "Stock Reconciliation" @@ -1816,7 +1813,6 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False): return if args.actual_qty >= 0 and args.voucher_type != "Stock Reconciliation": ->>>>>>> 289495c308 (fix: stock reco negative qty validation) return neg_sle = get_future_sle_with_negative_qty(args) From ed5affe25a63bc551d5feb17187690b3b9779ea4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 23 Apr 2024 12:08:07 +0530 Subject: [PATCH 24/31] refactor: better description for advance account (cherry picked from commit de9c8fc9d62879c96a3a9ca361d106c55bf6ece7) # Conflicts: # erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json # erpnext/setup/doctype/company/company.json --- .../payment_reconciliation/payment_reconciliation.json | 6 ++++++ erpnext/setup/doctype/company/company.json | 8 ++++++++ erpnext/setup/doctype/company/company.py | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json index 666926f00eb..9ceafd21bd9 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json @@ -195,6 +195,8 @@ }, { "depends_on": "eval:doc.party", + "description": "Only 'Payment Entries' made against this advance account are supported.", + "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/advance-in-separate-party-account", "fieldname": "default_advance_account", "fieldtype": "Link", "label": "Default Advance Account", @@ -229,7 +231,11 @@ "is_virtual": 1, "issingle": 1, "links": [], +<<<<<<< HEAD "modified": "2023-12-14 13:38:16.264013", +======= + "modified": "2024-04-23 12:38:29.557315", +>>>>>>> de9c8fc9d6 (refactor: better description for advance account) "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation", diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 24d7da45b84..59c53c73b84 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -707,6 +707,8 @@ }, { "depends_on": "eval:doc.book_advance_payments_in_separate_party_account", + "description": "Only 'Payment Entries' made against this advance account are supported.", + "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/advance-in-separate-party-account", "fieldname": "default_advance_received_account", "fieldtype": "Link", "label": "Default Advance Received Account", @@ -715,6 +717,8 @@ }, { "depends_on": "eval:doc.book_advance_payments_in_separate_party_account", + "description": "Only 'Payment Entries' made against this advance account are supported.", + "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/advance-in-separate-party-account", "fieldname": "default_advance_paid_account", "fieldtype": "Link", "label": "Default Advance Paid Account", @@ -782,7 +786,11 @@ "image_field": "company_logo", "is_tree": 1, "links": [], +<<<<<<< HEAD "modified": "2023-09-10 21:53:13.860791", +======= + "modified": "2024-04-23 12:38:33.173938", +>>>>>>> de9c8fc9d6 (refactor: better description for advance account) "modified_by": "Administrator", "module": "Setup", "name": "Company", diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 7181e90139e..3523cac822e 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -35,7 +35,7 @@ class Company(NestedSet): auto_exchange_rate_revaluation: DF.Check book_advance_payments_in_separate_party_account: DF.Check capital_work_in_progress_account: DF.Link | None - chart_of_accounts: DF.Literal + chart_of_accounts: DF.Literal[None] company_description: DF.TextEditor | None company_logo: DF.AttachImage | None company_name: DF.Data From 24954b909b1c9adebd70a24f0894d786d71b5bcb Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 23 Apr 2024 13:57:12 +0530 Subject: [PATCH 25/31] refactor: popup to inform on limited support for Advance accounts (cherry picked from commit 9dbd321133c22e9b9dce247b25780ee9f6e39acf) --- .../doctype/journal_entry/journal_entry.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index fe2ef1c5a91..4de20a17389 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -6,7 +6,7 @@ import json import frappe from frappe import _, msgprint, scrub -from frappe.utils import cstr, flt, fmt_money, formatdate, get_link_to_form, nowdate +from frappe.utils import comma_and, cstr, flt, fmt_money, formatdate, get_link_to_form, nowdate import erpnext from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts @@ -146,6 +146,7 @@ class JournalEntry(AccountsController): self.validate_empty_accounts_table() self.validate_inter_company_accounts() self.validate_depr_entry_voucher_type() + self.validate_advance_accounts() if self.docstatus == 0: self.apply_tax_withholding() @@ -153,6 +154,20 @@ class JournalEntry(AccountsController): if not self.title: self.title = self.get_title() + def validate_advance_accounts(self): + journal_accounts = set([x.account for x in self.accounts]) + advance_accounts = set() + advance_accounts.add( + frappe.get_cached_value("Company", self.company, "default_advance_received_account") + ) + advance_accounts.add(frappe.get_cached_value("Company", self.company, "default_advance_paid_account")) + if advance_accounts_used := journal_accounts & advance_accounts: + frappe.msgprint( + _( + "Making Journal Entries against advance accounts: {0} is not recommended. These Journals won't be available for Reconciliation." + ).format(frappe.bold(comma_and(advance_accounts_used))) + ) + def validate_for_repost(self): validate_docs_for_voucher_types(["Journal Entry"]) validate_docs_for_deferred_accounting([self.name], []) From 2b736b52d6950dc9d396fd0690214c25483a478d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 23 Apr 2024 15:33:25 +0530 Subject: [PATCH 26/31] chore: resolve conflicts --- .../payment_reconciliation/payment_reconciliation.json | 4 ---- erpnext/setup/doctype/company/company.json | 4 ---- 2 files changed, 8 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json index 9ceafd21bd9..219d6089a57 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json @@ -231,11 +231,7 @@ "is_virtual": 1, "issingle": 1, "links": [], -<<<<<<< HEAD - "modified": "2023-12-14 13:38:16.264013", -======= "modified": "2024-04-23 12:38:29.557315", ->>>>>>> de9c8fc9d6 (refactor: better description for advance account) "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation", diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 59c53c73b84..c222d6b96a7 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -786,11 +786,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], -<<<<<<< HEAD - "modified": "2023-09-10 21:53:13.860791", -======= "modified": "2024-04-23 12:38:33.173938", ->>>>>>> de9c8fc9d6 (refactor: better description for advance account) "modified_by": "Administrator", "module": "Setup", "name": "Company", From 05d5c48d29bfcf2edc63eb720c51d9633d3ef930 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 23 Apr 2024 13:51:16 +0530 Subject: [PATCH 27/31] fix: incorrect stock posting for current qty (cherry picked from commit d4fe313de28ebb3910e7f117101f278d54dde9f9) --- .../stock_reconciliation.py | 30 +----- .../test_stock_reconciliation.py | 102 ++++++++---------- erpnext/stock/stock_ledger.py | 2 +- 3 files changed, 44 insertions(+), 90 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 64a6b6503cc..f92d7361f41 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -823,11 +823,9 @@ class StockReconciliation(StockController): else: self._cancel() - def recalculate_current_qty(self, voucher_detail_no, sle_creation, add_new_sle=False): + def recalculate_current_qty(self, voucher_detail_no): from erpnext.stock.stock_ledger import get_valuation_rate - sl_entries = [] - for row in self.items: if voucher_detail_no != row.name: continue @@ -881,32 +879,6 @@ class StockReconciliation(StockController): } ) - if ( - add_new_sle - and not frappe.db.get_value( - "Stock Ledger Entry", - {"voucher_detail_no": row.name, "actual_qty": ("<", 0), "is_cancelled": 0}, - "name", - ) - and (not row.current_serial_and_batch_bundle) - ): - self.set_current_serial_and_batch_bundle(voucher_detail_no, save=True) - row.reload() - - if row.current_qty > 0 and row.current_serial_and_batch_bundle: - new_sle = self.get_sle_for_items(row) - new_sle.actual_qty = row.current_qty * -1 - new_sle.valuation_rate = row.current_valuation_rate - new_sle.creation_time = add_to_date(sle_creation, seconds=-1) - new_sle.serial_and_batch_bundle = row.current_serial_and_batch_bundle - new_sle.qty_after_transaction = 0.0 - sl_entries.append(new_sle) - - if sl_entries: - self.make_sl_entries(sl_entries, allow_negative_stock=self.has_negative_stock_allowed()) - if not frappe.db.exists("Repost Item Valuation", {"voucher_no": self.name, "status": "Queued"}): - self.repost_future_sle_and_gle(force=True) - def has_negative_stock_allowed(self): allow_negative_stock = cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) if allow_negative_stock: diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index add8b516e83..92a931036e9 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -756,66 +756,6 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): self.assertEqual(flt(sle[0].actual_qty), flt(-100.0)) - def test_backdated_stock_reco_entry_with_batch(self): - item_code = self.make_item( - "Test New Batch Item ABCVSD", - { - "is_stock_item": 1, - "has_batch_no": 1, - "batch_number_series": "BNS9.####", - "create_new_batch": 1, - }, - ).name - - warehouse = "_Test Warehouse - _TC" - - # Stock Reco for 100, Balace Qty 100 - stock_reco = create_stock_reconciliation( - item_code=item_code, - posting_date=nowdate(), - posting_time="11:00:00", - warehouse=warehouse, - qty=100, - rate=100, - ) - - sles = frappe.get_all( - "Stock Ledger Entry", - fields=["actual_qty"], - filters={"voucher_no": stock_reco.name, "is_cancelled": 0}, - ) - - self.assertEqual(len(sles), 1) - - stock_reco.reload() - batch_no = get_batch_from_bundle(stock_reco.items[0].serial_and_batch_bundle) - - # Stock Reco for 100, Balace Qty 100 - stock_reco1 = create_stock_reconciliation( - item_code=item_code, - posting_date=add_days(nowdate(), -1), - posting_time="11:00:00", - batch_no=batch_no, - warehouse=warehouse, - qty=60, - rate=100, - ) - - sles = frappe.get_all( - "Stock Ledger Entry", - fields=["actual_qty"], - filters={"voucher_no": stock_reco.name, "is_cancelled": 0}, - ) - - stock_reco1.reload() - get_batch_from_bundle(stock_reco1.items[0].serial_and_batch_bundle) - - self.assertEqual(len(sles), 2) - - for row in sles: - if row.actual_qty < 0: - self.assertEqual(row.actual_qty, -60) - def test_update_stock_reconciliation_while_reposting(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry @@ -1088,6 +1028,48 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): self.assertEqual(flt(sle[0].qty_after_transaction), flt(100.0)) + def test_stock_reco_and_backdated_purchase_receipt(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + item = self.make_item( + "Test Batch Item Original STOCK RECO Test", + { + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TEST-BATCH-SRCOSRWFEE-.###", + }, + ) + + warehouse = "_Test Warehouse - _TC" + + sr = create_stock_reconciliation( + item_code=item.name, + warehouse=warehouse, + qty=100, + rate=100, + ) + + sr.reload() + self.assertTrue(sr.items[0].serial_and_batch_bundle) + self.assertFalse(sr.items[0].current_serial_and_batch_bundle) + batch = get_batch_from_bundle(sr.items[0].serial_and_batch_bundle) + + se1 = make_stock_entry( + item_code=item.name, + target=warehouse, + qty=50, + basic_rate=100, + posting_date=add_days(nowdate(), -2), + ) + + batch1 = get_batch_from_bundle(se1.items[0].serial_and_batch_bundle) + self.assertFalse(batch1 == batch) + + sr.reload() + self.assertTrue(sr.items[0].serial_and_batch_bundle) + self.assertFalse(sr.items[0].current_serial_and_batch_bundle) + def create_batch_item_with_batch(item_name, batch_id): batch_item_doc = create_item(item_name, is_stock_item=1) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 2bd00fe55d5..5c5fd83af2f 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -840,7 +840,7 @@ class update_entries_after: def reset_actual_qty_for_stock_reco(self, sle): doc = frappe.get_cached_doc("Stock Reconciliation", sle.voucher_no) - doc.recalculate_current_qty(sle.voucher_detail_no, sle.creation, sle.actual_qty > 0) + doc.recalculate_current_qty(sle.voucher_detail_no) if sle.actual_qty < 0: sle.actual_qty = ( From 791e4269d2f80759389c80d8f35198e155772027 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 22 Apr 2024 15:23:16 +0530 Subject: [PATCH 28/31] feat: Available batches report as on specific date (cherry picked from commit b8f7979794d21a4fb53ad17b7a84e68be9ea031c) --- .../report/available_batch_report/__init__.py | 0 .../available_batch_report.js | 91 +++++++++ .../available_batch_report.json | 31 +++ .../available_batch_report.py | 178 ++++++++++++++++++ 4 files changed, 300 insertions(+) create mode 100644 erpnext/stock/report/available_batch_report/__init__.py create mode 100644 erpnext/stock/report/available_batch_report/available_batch_report.js create mode 100644 erpnext/stock/report/available_batch_report/available_batch_report.json create mode 100644 erpnext/stock/report/available_batch_report/available_batch_report.py diff --git a/erpnext/stock/report/available_batch_report/__init__.py b/erpnext/stock/report/available_batch_report/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/report/available_batch_report/available_batch_report.js b/erpnext/stock/report/available_batch_report/available_batch_report.js new file mode 100644 index 00000000000..011f7e09ca2 --- /dev/null +++ b/erpnext/stock/report/available_batch_report/available_batch_report.js @@ -0,0 +1,91 @@ +// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.query_reports["Available Batch Report"] = { + filters: [ + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + width: "80", + options: "Company", + default: frappe.defaults.get_default("company"), + }, + { + fieldname: "to_date", + label: __("On This Date"), + fieldtype: "Date", + width: "80", + reqd: 1, + default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), + }, + { + fieldname: "item_code", + label: __("Item"), + fieldtype: "Link", + width: "80", + options: "Item", + get_query: () => { + return { + filters: { + has_batch_no: 1, + disabled: 0, + }, + }; + }, + }, + { + fieldname: "warehouse", + label: __("Warehouse"), + fieldtype: "Link", + width: "80", + options: "Warehouse", + get_query: () => { + let warehouse_type = frappe.query_report.get_filter_value("warehouse_type"); + let company = frappe.query_report.get_filter_value("company"); + + return { + filters: { + ...(warehouse_type && { warehouse_type }), + ...(company && { company }), + }, + }; + }, + }, + { + fieldname: "warehouse_type", + label: __("Warehouse Type"), + fieldtype: "Link", + width: "80", + options: "Warehouse Type", + }, + { + fieldname: "batch_no", + label: __("Batch No"), + fieldtype: "Link", + width: "80", + options: "Batch", + get_query: () => { + let item = frappe.query_report.get_filter_value("item_code"); + + return { + filters: { + ...(item && { item }), + }, + }; + }, + }, + { + fieldname: "include_expired_batches", + label: __("Include Expired Batches"), + fieldtype: "Check", + width: "80", + }, + { + fieldname: "show_item_name", + label: __("Show Item Name"), + fieldtype: "Check", + width: "80", + }, + ], +}; diff --git a/erpnext/stock/report/available_batch_report/available_batch_report.json b/erpnext/stock/report/available_batch_report/available_batch_report.json new file mode 100644 index 00000000000..ddc03120e92 --- /dev/null +++ b/erpnext/stock/report/available_batch_report/available_batch_report.json @@ -0,0 +1,31 @@ +{ + "add_total_row": 1, + "columns": [], + "creation": "2024-04-11 17:03:32.253275", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "letter_head": "", + "letterhead": null, + "modified": "2024-04-23 17:18:19.779036", + "modified_by": "Administrator", + "module": "Stock", + "name": "Available Batch Report", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Stock Ledger Entry", + "report_name": "Available Batch Report", + "report_type": "Script Report", + "roles": [ + { + "role": "Stock User" + }, + { + "role": "Accounts Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/stock/report/available_batch_report/available_batch_report.py b/erpnext/stock/report/available_batch_report/available_batch_report.py new file mode 100644 index 00000000000..07fcf36c827 --- /dev/null +++ b/erpnext/stock/report/available_batch_report/available_batch_report.py @@ -0,0 +1,178 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from collections import defaultdict + +import frappe +from frappe import _ +from frappe.query_builder.functions import Sum +from frappe.utils import flt, today + + +def execute(filters=None): + columns, data = [], [] + data = get_data(filters) + columns = get_columns(filters) + return columns, data + + +def get_columns(filters): + columns = [ + { + "label": _("Item Code"), + "fieldname": "item_code", + "fieldtype": "Link", + "options": "Item", + "width": 200, + } + ] + + if filters.show_item_name: + columns.append( + { + "label": _("Item Name"), + "fieldname": "item_name", + "fieldtype": "Link", + "options": "Item", + "width": 200, + } + ) + + columns.extend( + [ + { + "label": _("Warehouse"), + "fieldname": "warehouse", + "fieldtype": "Link", + "options": "Warehouse", + "width": 200, + }, + { + "label": _("Batch No"), + "fieldname": "batch_no", + "fieldtype": "Link", + "width": 150, + "options": "Batch", + }, + {"label": _("Balance Qty"), "fieldname": "balance_qty", "fieldtype": "Float", "width": 150}, + ] + ) + + return columns + + +def get_data(filters): + data = [] + batchwise_data = get_batchwise_data_from_stock_ledger(filters) + batchwise_data = get_batchwise_data_from_serial_batch_bundle(batchwise_data, filters) + + data = parse_batchwise_data(batchwise_data) + + return data + + +def parse_batchwise_data(batchwise_data): + data = [] + for key in batchwise_data: + d = batchwise_data[key] + if d.balance_qty == 0: + continue + + data.append(d) + + return data + + +def get_batchwise_data_from_stock_ledger(filters): + batchwise_data = frappe._dict({}) + + table = frappe.qb.DocType("Stock Ledger Entry") + batch = frappe.qb.DocType("Batch") + + query = ( + frappe.qb.from_(table) + .inner_join(batch) + .on(table.batch_no == batch.name) + .select( + table.item_code, + table.batch_no, + table.warehouse, + Sum(table.actual_qty).as_("balance_qty"), + ) + .where(table.is_cancelled == 0) + .groupby(table.batch_no, table.item_code, table.warehouse) + ) + + query = get_query_based_on_filters(query, batch, table, filters) + + for d in query.run(as_dict=True): + key = (d.item_code, d.warehouse, d.batch_no) + batchwise_data.setdefault(key, d) + + return batchwise_data + + +def get_batchwise_data_from_serial_batch_bundle(batchwise_data, filters): + table = frappe.qb.DocType("Stock Ledger Entry") + ch_table = frappe.qb.DocType("Serial and Batch Entry") + batch = frappe.qb.DocType("Batch") + + query = ( + frappe.qb.from_(table) + .inner_join(ch_table) + .on(table.serial_and_batch_bundle == ch_table.parent) + .inner_join(batch) + .on(ch_table.batch_no == batch.name) + .select( + table.item_code, + ch_table.batch_no, + table.warehouse, + Sum(ch_table.qty).as_("balance_qty"), + ) + .where((table.is_cancelled == 0) & (table.docstatus == 1)) + .groupby(ch_table.batch_no, table.item_code, ch_table.warehouse) + ) + + query = get_query_based_on_filters(query, batch, table, filters) + + for d in query.run(as_dict=True): + key = (d.item_code, d.warehouse, d.batch_no) + if key in batchwise_data: + batchwise_data[key].balance_qty += flt(d.balance_qty) + else: + batchwise_data.setdefault(key, d) + + return batchwise_data + + +def get_query_based_on_filters(query, batch, table, filters): + if filters.item_code: + query = query.where(table.item_code == filters.item_code) + + if filters.batch_no: + query = query.where(batch.name == filters.batch_no) + + if not filters.include_expired_batches: + query = query.where((batch.expiry_date >= today()) | (batch.expiry_date.isnull())) + if filters.to_date == today(): + query = query.where(batch.batch_qty > 0) + + if filters.warehouse: + lft, rgt = frappe.db.get_value("Warehouse", filters.warehouse, ["lft", "rgt"]) + warehouses = frappe.get_all( + "Warehouse", filters={"lft": (">=", lft), "rgt": ("<=", rgt), "is_group": 0}, pluck="name" + ) + + query = query.where(table.warehouse.isin(warehouses)) + + elif filters.warehouse_type: + warehouses = frappe.get_all( + "Warehouse", filters={"warehouse_type": filters.warehouse_type, "is_group": 0}, pluck="name" + ) + + query = query.where(table.warehouse.isin(warehouses)) + + if filters.show_item_name: + query = query.select(batch.item_name) + + return query From 4f5af0ff9fcb9170b5a3dfc4a8c6270a2ab31008 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 24 Apr 2024 11:32:26 +0530 Subject: [PATCH 29/31] chore: Resolve conflicts --- .../lower_deduction_certificate.json | 9 --------- 1 file changed, 9 deletions(-) diff --git a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json index 044471a86e3..b94cfe673b6 100644 --- a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json +++ b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json @@ -135,20 +135,12 @@ ], "index_web_pages_for_search": 1, "links": [], -<<<<<<< HEAD - "modified": "2023-04-18 08:25:35.302081", -======= "modified": "2024-04-18 15:25:25.808355", ->>>>>>> f6f118855b (fix: Permission for lower dedcution certificate) "modified_by": "Administrator", "module": "Regional", "name": "Lower Deduction Certificate", "naming_rule": "By fieldname", "owner": "Administrator", -<<<<<<< HEAD - "permissions": [], - "sort_field": "modified", -======= "permissions": [ { "create": 1, @@ -188,7 +180,6 @@ } ], "sort_field": "creation", ->>>>>>> f6f118855b (fix: Permission for lower dedcution certificate) "sort_order": "DESC", "states": [], "track_changes": 1 From 90d6e550aafab49678993f7ee9ece3891fe37b15 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 24 Apr 2024 11:43:46 +0530 Subject: [PATCH 30/31] fix: allow Employee role to select cost center & project (accounting dimensions) (backport #41160) (#41162) * fix: allow Employee role to select cost center & project (accounting dimensions) (cherry picked from commit d0d496a5152df5cd361d581e2b6794c6fd099e59) # Conflicts: # erpnext/accounts/doctype/cost_center/cost_center.json # erpnext/projects/doctype/project/project.json * chore: fix conflicts --------- Co-authored-by: Rucha Mahabal --- erpnext/accounts/doctype/cost_center/cost_center.json | 11 ++++++++++- erpnext/projects/doctype/project/project.json | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/cost_center/cost_center.json b/erpnext/accounts/doctype/cost_center/cost_center.json index 2e66db7bcf3..55d4085c823 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.json +++ b/erpnext/accounts/doctype/cost_center/cost_center.json @@ -125,7 +125,7 @@ "idx": 1, "is_tree": 1, "links": [], - "modified": "2022-01-31 13:22:58.916273", + "modified": "2024-04-24 10:55:54.083042", "modified_by": "Administrator", "module": "Accounts", "name": "Cost Center", @@ -163,6 +163,15 @@ { "read": 1, "role": "Purchase User" + }, + { + "email": 1, + "export": 1, + "print": 1, + "report": 1, + "role": "Employee", + "select": 1, + "share": 1 } ], "search_fields": "parent_cost_center, is_group", diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index 5917e9b5d26..464b1c9d7a8 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -454,7 +454,7 @@ "index_web_pages_for_search": 1, "links": [], "max_attachments": 4, - "modified": "2024-01-08 16:01:34.598258", + "modified": "2024-04-24 10:56:16.001032", "modified_by": "Administrator", "module": "Projects", "name": "Project", @@ -489,6 +489,15 @@ "role": "Projects Manager", "share": 1, "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "report": 1, + "role": "Employee", + "select": 1, + "share": 1 } ], "quick_entry": 1, From 410990b2a3a5632504ba67e3654523741da76eba Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 19 Apr 2024 19:09:55 +0530 Subject: [PATCH 31/31] chore: Remove heatmap from party dashboards (cherry picked from commit a8f03e8baa805bfa95aaaa55566d470d4de715e9) --- erpnext/accounts/party.py | 46 ------------------- .../doctype/supplier/supplier_dashboard.py | 4 -- .../doctype/customer/customer_dashboard.py | 4 -- 3 files changed, 54 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 722082fe0a4..70cd8ce1f97 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -751,52 +751,6 @@ def validate_party_frozen_disabled(party_type, party_name): frappe.msgprint(_("{0} {1} is not active").format(party_type, party_name), alert=True) -def get_timeline_data(doctype, name): - """returns timeline data for the past one year""" - from frappe.desk.form.load import get_communication_data - - out = {} - after = add_years(None, -1).strftime("%Y-%m-%d") - - data = get_communication_data( - doctype, - name, - after=after, - group_by="group by communication_date", - fields="C.communication_date as communication_date, count(C.name)", - as_dict=False, - ) - - # fetch and append data from Activity Log - activity_log = frappe.qb.DocType("Activity Log") - data += ( - frappe.qb.from_(activity_log) - .select(activity_log.communication_date, Count(activity_log.name)) - .where( - ( - ((activity_log.reference_doctype == doctype) & (activity_log.reference_name == name)) - | ((activity_log.timeline_doctype == doctype) & (activity_log.timeline_name == name)) - | ( - (activity_log.reference_doctype.isin(["Quotation", "Opportunity"])) - & (activity_log.timeline_name == name) - ) - ) - & (activity_log.status != "Success") - & (activity_log.creation > after) - ) - .groupby(activity_log.communication_date) - .orderby(activity_log.communication_date, order=frappe.qb.desc) - ).run() - - timeline_items = dict(data) - - for date, count in timeline_items.items(): - timestamp = get_timestamp(date) - out.update({timestamp: count}) - - return out - - def get_dashboard_info(party_type, party, loyalty_program=None): current_fiscal_year = get_fiscal_year(nowdate(), as_dict=True) diff --git a/erpnext/buying/doctype/supplier/supplier_dashboard.py b/erpnext/buying/doctype/supplier/supplier_dashboard.py index 3bd306e6591..9f424dd0c7c 100644 --- a/erpnext/buying/doctype/supplier/supplier_dashboard.py +++ b/erpnext/buying/doctype/supplier/supplier_dashboard.py @@ -3,10 +3,6 @@ from frappe import _ def get_data(): return { - "heatmap": True, - "heatmap_message": _( - "This is based on transactions against this Supplier. See timeline below for details" - ), "fieldname": "supplier", "non_standard_fieldnames": {"Payment Entry": "party", "Bank Account": "party"}, "transactions": [ diff --git a/erpnext/selling/doctype/customer/customer_dashboard.py b/erpnext/selling/doctype/customer/customer_dashboard.py index 1b2296381e8..161a3ba0c50 100644 --- a/erpnext/selling/doctype/customer/customer_dashboard.py +++ b/erpnext/selling/doctype/customer/customer_dashboard.py @@ -3,10 +3,6 @@ from frappe import _ def get_data(): return { - "heatmap": True, - "heatmap_message": _( - "This is based on transactions against this Customer. See timeline below for details" - ), "fieldname": "customer", "non_standard_fieldnames": { "Payment Entry": "party",