From 11bddc14bb3169fed080ea7451b6573f1b92a980 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 20 Feb 2024 11:58:47 +0530 Subject: [PATCH 01/50] fix: Delete linked asset movement record on cancellation of purchase receipt/invoice --- erpnext/controllers/buying_controller.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 4a627696032..1abf95f5953 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -811,7 +811,8 @@ class BuyingController(SubcontractingController): if self.doctype == "Purchase Invoice" and not self.get("update_stock"): return - frappe.db.sql("delete from `tabAsset Movement` where reference_name=%s", self.name) + asset_movement = frappe.db.get_value("Asset Movement", {"reference_name": self.name}, "name") + frappe.delete_doc("Asset Movement", asset_movement, force=1) def validate_schedule_date(self): if not self.get("items"): From 46cd929d00a0a71ef067ef66490d999921884110 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 20 Feb 2024 12:11:12 +0530 Subject: [PATCH 02/50] fix: Delete orphaned asset movement item records --- erpnext/patches.txt | 1 + .../v14_0/delete_orphaned_asset_movement_item_records.py | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 erpnext/patches/v14_0/delete_orphaned_asset_movement_item_records.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 6b7b13ff46e..125158a5add 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -358,3 +358,4 @@ erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20 erpnext.patches.v14_0.set_maintain_stock_for_bom_item execute:frappe.db.set_single_value('E Commerce Settings', 'show_actual_qty', 1) +erpnext.patches.v14_0.delete_orphaned_asset_movement_item_records diff --git a/erpnext/patches/v14_0/delete_orphaned_asset_movement_item_records.py b/erpnext/patches/v14_0/delete_orphaned_asset_movement_item_records.py new file mode 100644 index 00000000000..dff04c09458 --- /dev/null +++ b/erpnext/patches/v14_0/delete_orphaned_asset_movement_item_records.py @@ -0,0 +1,7 @@ +import frappe + +def execute(): + frappe.db.sql(""" + DELETE FROM `tabAsset Movement Item` + WHERE parent NOT IN (SELECT name FROM `tabAsset Movement`) + """) \ No newline at end of file From ea1a0b3a28cdb4cc2f6e41c748e9577a49470ac8 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 20 Feb 2024 15:51:29 +0530 Subject: [PATCH 03/50] fix: linter issue --- .../delete_orphaned_asset_movement_item_records.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/patches/v14_0/delete_orphaned_asset_movement_item_records.py b/erpnext/patches/v14_0/delete_orphaned_asset_movement_item_records.py index dff04c09458..a1d7dc9b3d4 100644 --- a/erpnext/patches/v14_0/delete_orphaned_asset_movement_item_records.py +++ b/erpnext/patches/v14_0/delete_orphaned_asset_movement_item_records.py @@ -1,7 +1,11 @@ import frappe + def execute(): - frappe.db.sql(""" - DELETE FROM `tabAsset Movement Item` - WHERE parent NOT IN (SELECT name FROM `tabAsset Movement`) - """) \ No newline at end of file + # nosemgrep + frappe.db.sql( + """ + DELETE FROM `tabAsset Movement Item` + WHERE parent NOT IN (SELECT name FROM `tabAsset Movement`) + """ + ) From c8e1409b803e42f6ecedb4c2bd56dffb147fdbf9 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 21 Feb 2024 14:36:14 +0530 Subject: [PATCH 04/50] fix: Completed Work Orders report not working (cherry picked from commit 11f4cb914a665bbfba61ec76059c8eba48a467be) --- .../completed_work_orders.json | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/erpnext/manufacturing/report/completed_work_orders/completed_work_orders.json b/erpnext/manufacturing/report/completed_work_orders/completed_work_orders.json index be50e93f1ba..7925b8a8ab8 100644 --- a/erpnext/manufacturing/report/completed_work_orders/completed_work_orders.json +++ b/erpnext/manufacturing/report/completed_work_orders/completed_work_orders.json @@ -1,25 +1,28 @@ { - "add_total_row": 0, - "apply_user_permissions": 1, - "creation": "2013-08-12 12:44:27", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 3, - "is_standard": "Yes", - "modified": "2018-02-13 04:58:51.549413", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "Completed Work Orders", - "owner": "Administrator", - "query": "SELECT\n `tabWork Order`.name as \"Work Order:Link/Work Order:200\",\n `tabWork Order`.creation as \"Date:Date:120\",\n `tabWork Order`.production_item as \"Item:Link/Item:150\",\n `tabWork Order`.qty as \"To Produce:Int:100\",\n `tabWork Order`.produced_qty as \"Produced:Int:100\",\n `tabWork Order`.company as \"Company:Link/Company:\"\nFROM\n `tabWork Order`\nWHERE\n `tabWork Order`.docstatus=1\n AND ifnull(`tabWork Order`.produced_qty,0) = `tabWork Order`.qty", - "ref_doctype": "Work Order", - "report_name": "Completed Work Orders", - "report_type": "Query Report", + "add_total_row": 0, + "columns": [], + "creation": "2013-08-12 12:44:27", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 3, + "is_standard": "Yes", + "letterhead": null, + "modified": "2024-02-21 14:35:14.301848", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Completed Work Orders", + "owner": "Administrator", + "prepared_report": 0, + "query": "SELECT\n `tabWork Order`.name as \"Work Order:Link/Work Order:200\",\n `tabWork Order`.creation as \"Date:Date:120\",\n `tabWork Order`.production_item as \"Item:Link/Item:150\",\n `tabWork Order`.qty as \"To Produce:Int:100\",\n `tabWork Order`.produced_qty as \"Produced:Int:100\",\n `tabWork Order`.company as \"Company:Link/Company:\"\nFROM\n `tabWork Order`\nWHERE\n `tabWork Order`.docstatus=1\n AND ifnull(`tabWork Order`.produced_qty,0) >= `tabWork Order`.qty", + "ref_doctype": "Work Order", + "report_name": "Completed Work Orders", + "report_type": "Query Report", "roles": [ { "role": "Manufacturing User" - }, + }, { "role": "Stock User" } From 079ba7670c83b8530b196ae948fb0c402c93f8a3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 09:50:33 +0530 Subject: [PATCH 05/50] fix: incorrect item name in MR (backport #40018) (#40023) fix: incorrect item name in MR (#40018) (cherry picked from commit 864d7ae04c3a2992f94eafb47060468f0cfb3c9c) Co-authored-by: rohitwaghchaure --- .../doctype/material_request/material_request.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index c5bc601e391..20bfbef8f49 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -199,6 +199,7 @@ frappe.ui.form.on('Material Request', { get_item_data: function(frm, item, overwrite_warehouse=false) { if (item && !item.item_code) { return; } + frappe.call({ method: "erpnext.stock.get_item_details.get_item_details", args: { @@ -225,20 +226,22 @@ frappe.ui.form.on('Material Request', { }, callback: function(r) { const d = item; - const qty_fields = ['actual_qty', 'projected_qty', 'min_order_qty']; + const allow_to_change_fields = ['actual_qty', 'projected_qty', 'min_order_qty', 'item_name', 'description', 'stock_uom', 'uom', 'conversion_factor', 'stock_qty']; if(!r.exc) { $.each(r.message, function(key, value) { - if(!d[key] || qty_fields.includes(key)) { + if(!d[key] || allow_to_change_fields.includes(key)) { d[key] = value; } }); if (d.price_list_rate != r.message.price_list_rate) { + d.rate = 0.0; d.price_list_rate = r.message.price_list_rate; - frappe.model.set_value(d.doctype, d.name, "rate", d.price_list_rate); } + + refresh_field("items"); } } }); @@ -435,7 +438,7 @@ frappe.ui.form.on("Material Request Item", { frm.events.get_item_data(frm, item, false); }, - rate: function(frm, doctype, name) { + rate(frm, doctype, name) { const item = locals[doctype][name]; item.amount = flt(item.qty) * flt(item.rate); frappe.model.set_value(doctype, name, "amount", item.amount); From 865cba406f7826501e33853052d15665d6b72448 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 11:39:48 +0530 Subject: [PATCH 06/50] fix: negative stock error while making stock reconciliation (backport #40016) (#40025) * fix: negative stock error while making stock reconciliation (#40016) fix: negative stock error while making stock reco (cherry picked from commit da184d709b9f61bc9bd62cd6beddf5e19c661042) # Conflicts: # erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py * chore: fix conflicts * chore: fix linter issue --------- Co-authored-by: rohitwaghchaure --- .../doctype/stock_reconciliation/stock_reconciliation.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 9a46ae71ad2..44c2d85b4cc 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -699,8 +699,13 @@ class StockReconciliation(StockController): def has_negative_stock_allowed(self): allow_negative_stock = cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) + if allow_negative_stock: + return True - if all(d.batch_no and flt(d.qty) == flt(d.current_qty) for d in self.items): + if any( + (d.batch_no and flt(d.qty) == flt(d.current_qty)) + for d in self.items + ): allow_negative_stock = True return allow_negative_stock From 2a2f314821d38ede4c39ef750ed34503748d623a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 12:29:56 +0530 Subject: [PATCH 07/50] fix: timesheet per billed state edge case (backport #40010) (#40028) fix: timesheet per billed state edge case (#40010) If value is 100.0000x then it won't set status correctly but will set it the next time it's loaded from db. (cherry picked from commit 38e88db2c9245a3fec392941d2937cace7bf8e5f) Co-authored-by: Ankush Menat --- erpnext/projects/doctype/timesheet/timesheet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index 9e52befd22e..4db647eb3e2 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -77,7 +77,7 @@ class Timesheet(Document): def set_status(self): self.status = {"0": "Draft", "1": "Submitted", "2": "Cancelled"}[str(self.docstatus or 0)] - if self.per_billed == 100: + if flt(self.per_billed, self.precision("per_billed")) >= 100.0: self.status = "Billed" if self.sales_invoice: From b364fe704723cfc0e508c8ff9f39645a8625b703 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 16:20:15 +0530 Subject: [PATCH 08/50] fix: communication_date in party dashboards (backport #40005) (#40020) fix: accommodate for changed orderby statement (cherry picked from commit 87df7ff71772474bb858dac6ce9132280ce2ab82) Co-authored-by: Gursheen Anand --- erpnext/accounts/party.py | 41 +++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index c83969b34bc..8f8b9a81f8a 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -9,7 +9,7 @@ from frappe import _, msgprint, qb, scrub from frappe.contacts.doctype.address.address import get_company_address, get_default_address from frappe.core.doctype.user_permission.user_permission import get_permitted_documents from frappe.model.utils import get_fetch_values -from frappe.query_builder.functions import Abs, Date, Sum +from frappe.query_builder.functions import Abs, Count, Date, Sum from frappe.utils import ( add_days, add_months, @@ -761,34 +761,37 @@ def get_timeline_data(doctype, name): from frappe.desk.form.load import get_communication_data out = {} - fields = "creation, count(*)" after = add_years(None, -1).strftime("%Y-%m-%d") - group_by = "group by Date(creation)" data = get_communication_data( doctype, name, after=after, - group_by="group by creation", - fields="C.creation as creation, count(C.name)", + 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 - data += frappe.db.sql( - """select {fields} - from `tabActivity Log` - where (reference_doctype=%(doctype)s and reference_name=%(name)s) - or (timeline_doctype in (%(doctype)s) and timeline_name=%(name)s) - or (reference_doctype in ("Quotation", "Opportunity") and timeline_name=%(name)s) - and status!='Success' and creation > {after} - {group_by} order by creation desc - """.format( - fields=fields, group_by=group_by, after=after - ), - {"doctype": doctype, "name": name}, - as_dict=False, - ) + 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) From d6fad08d20c6d29e799a9dc43c75db4c0c6926fa Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 22 Feb 2024 16:24:30 +0530 Subject: [PATCH 09/50] fix: Webpages not working without login --- erpnext/setup/doctype/item_group/item_group.json | 4 +++- erpnext/setup/doctype/item_group/templates/item_group.html | 7 +++++++ .../setup/doctype/item_group/templates/item_group_row.html | 4 ++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 erpnext/setup/doctype/item_group/templates/item_group.html create mode 100644 erpnext/setup/doctype/item_group/templates/item_group_row.html diff --git a/erpnext/setup/doctype/item_group/item_group.json b/erpnext/setup/doctype/item_group/item_group.json index 2986087277c..71dfdb4f91a 100644 --- a/erpnext/setup/doctype/item_group/item_group.json +++ b/erpnext/setup/doctype/item_group/item_group.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_guest_to_view": 1, "allow_import": 1, "allow_rename": 1, "autoname": "field:item_group_name", @@ -227,13 +228,14 @@ "label": "Include Descendants" } ], + "has_web_view": 1, "icon": "fa fa-sitemap", "idx": 1, "image_field": "image", "is_tree": 1, "links": [], "max_attachments": 3, - "modified": "2023-01-05 12:21:30.458628", + "modified": "2024-02-22 16:23:46.936496", "modified_by": "Administrator", "module": "Setup", "name": "Item Group", diff --git a/erpnext/setup/doctype/item_group/templates/item_group.html b/erpnext/setup/doctype/item_group/templates/item_group.html new file mode 100644 index 00000000000..db123090aae --- /dev/null +++ b/erpnext/setup/doctype/item_group/templates/item_group.html @@ -0,0 +1,7 @@ +{% extends "templates/web.html" %} + +{% block page_content %} +

{{ title }}

+{% endblock %} + + \ No newline at end of file diff --git a/erpnext/setup/doctype/item_group/templates/item_group_row.html b/erpnext/setup/doctype/item_group/templates/item_group_row.html new file mode 100644 index 00000000000..d7014b453ab --- /dev/null +++ b/erpnext/setup/doctype/item_group/templates/item_group_row.html @@ -0,0 +1,4 @@ + + From 3c170b980575f76a3caf3624f0cbee8845a0044f Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Fri, 9 Feb 2024 12:14:55 +0530 Subject: [PATCH 10/50] fix: translatable columns in Sales Pipeline Analytics report (cherry picked from commit c5050c935bb75c5fdf10be4dfea16557da90b487) --- .../sales_pipeline_analytics/sales_pipeline_analytics.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py index dea3f2dd36d..4f7436ff9e4 100644 --- a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py +++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py @@ -41,7 +41,9 @@ class SalesPipelineAnalytics(object): month_list = self.get_month_list() for month in month_list: - self.columns.append({"fieldname": month, "fieldtype": based_on, "label": month, "width": 200}) + self.columns.append( + {"fieldname": month, "fieldtype": based_on, "label": _(month), "width": 200} + ) elif self.filters.get("range") == "Quarterly": for quarter in range(1, 5): @@ -156,7 +158,7 @@ class SalesPipelineAnalytics(object): for column in self.columns: if column["fieldname"] != "opportunity_owner" and column["fieldname"] != "sales_stage": - labels.append(column["fieldname"]) + labels.append(_(column["fieldname"])) self.chart = {"data": {"labels": labels, "datasets": datasets}, "type": "line"} From 2fc490a5d77a3c25d1e17124bdd626442257da4d Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Fri, 9 Feb 2024 16:13:44 +0530 Subject: [PATCH 11/50] fix: remove cancelled payment entry from Payment Period Based On Invoice Date (cherry picked from commit a2a8a8f2e0c7ca3f62c31ac8834e5a6053aeb87d) --- .../payment_period_based_on_invoice_date.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py index 3f178f4715c..0f6ff74e26e 100644 --- a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py +++ b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py @@ -163,7 +163,7 @@ def get_entries(filters): """select voucher_type, voucher_no, party_type, party, posting_date, debit, credit, remarks, against_voucher from `tabGL Entry` - where company=%(company)s and voucher_type in ('Journal Entry', 'Payment Entry') {0} + where company=%(company)s and voucher_type in ('Journal Entry', 'Payment Entry') {0} and is_cancelled = 0 """.format( get_conditions(filters) ), From a1c0c2359f5a3b78006c77f2b8e3eac9c488fa21 Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Fri, 9 Feb 2024 16:27:52 +0530 Subject: [PATCH 12/50] fix: remove cancelled payment entry from PPBOID report (cherry picked from commit 186cc3d7488b126ce5d2d6d2f9095f138fd44de3) --- .../payment_period_based_on_invoice_date.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py index 0f6ff74e26e..eaeaa62d9a2 100644 --- a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py +++ b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py @@ -163,7 +163,7 @@ def get_entries(filters): """select voucher_type, voucher_no, party_type, party, posting_date, debit, credit, remarks, against_voucher from `tabGL Entry` - where company=%(company)s and voucher_type in ('Journal Entry', 'Payment Entry') {0} and is_cancelled = 0 + where company=%(company)s and voucher_type in ('Journal Entry', 'Payment Entry') and is_cancelled = 0 {0} """.format( get_conditions(filters) ), From e5098c521ba5eb0371a902b671b0f3e0f5213d86 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 20 Feb 2024 21:49:28 +0530 Subject: [PATCH 13/50] fix: Cr/Dr notes with POS Payments (cherry picked from commit 68a23730f3fde5d8f306d77c3db5e373b1c6e937) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 19f52ad1e77..288acd9f792 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1293,9 +1293,7 @@ class SalesInvoice(SellingController): "credit_in_account_currency": payment_mode.base_amount if self.party_account_currency == self.company_currency else payment_mode.amount, - "against_voucher": self.return_against - if cint(self.is_return) and self.return_against - else self.name, + "against_voucher": self.name, "against_voucher_type": self.doctype, "cost_center": self.cost_center, }, From a9791c85c7fa047f1c4ddbe45d942bd383904a1c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 22 Feb 2024 20:46:07 +0530 Subject: [PATCH 14/50] test: ledger entries of Cr Note of POS Invoice (cherry picked from commit 4288713abe2e614e2454589fd5e344ba87f38fe1) --- .../sales_invoice/test_sales_invoice.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index abf0fc5c0f3..ce5456da5d3 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1081,6 +1081,44 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(pos.grand_total, 100.0) self.assertEqual(pos.write_off_amount, 10) + def test_ledger_entries_of_return_pos_invoice(self): + make_pos_profile() + + pos = create_sales_invoice(do_not_save=True) + pos.is_pos = 1 + pos.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100}) + pos.save().submit() + self.assertEqual(pos.outstanding_amount, 0.0) + self.assertEqual(pos.status, "Paid") + + from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return + + pos_return = make_sales_return(pos.name) + pos_return.save().submit() + pos_return.reload() + pos.reload() + self.assertEqual(pos_return.is_return, 1) + self.assertEqual(pos_return.return_against, pos.name) + self.assertEqual(pos_return.outstanding_amount, 0.0) + self.assertEqual(pos_return.status, "Return") + self.assertEqual(pos.outstanding_amount, 0.0) + self.assertEqual(pos.status, "Credit Note Issued") + + expected = ( + ("Cash - _TC", 0.0, 100.0, pos_return.name, None), + ("Debtors - _TC", 0.0, 100.0, pos_return.name, pos_return.name), + ("Debtors - _TC", 100.0, 0.0, pos_return.name, pos_return.name), + ("Sales - _TC", 100.0, 0.0, pos_return.name, None), + ) + res = frappe.db.get_all( + "GL Entry", + filters={"voucher_no": pos_return.name, "is_cancelled": 0}, + fields=["account", "debit", "credit", "voucher_no", "against_voucher"], + order_by="account, debit, credit", + as_list=1, + ) + self.assertEqual(expected, res) + def test_pos_with_no_gl_entry_for_change_amount(self): frappe.db.set_value("Accounts Settings", None, "post_change_gl_entries", 0) From 9e9486d5abba143b69e745b3aa929b623d8727e5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 23 Feb 2024 05:45:04 +0530 Subject: [PATCH 15/50] refactor: skip popup for POS invoices (cherry picked from commit 3634c4c284061ecd2fe5576467238532f748b07a) --- erpnext/controllers/accounts_controller.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index f642cde73c3..f096917aa8e 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -201,7 +201,8 @@ class AccountsController(TransactionBase): ) ) - if self.get("is_return") and self.get("return_against"): + if self.get("is_return") and self.get("return_against") and not self.get("is_pos"): + # if self.get("is_return") and self.get("return_against"): document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note" frappe.msgprint( _( From 08459cef4ab4399ec6cce8590c0e4aae7a3d72c7 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 22 Feb 2024 15:12:02 +0530 Subject: [PATCH 16/50] fix: delete PLE containing invoice in against (cherry picked from commit c1e1fd882950352c61e5881076c34fe3436142c6) --- erpnext/controllers/accounts_controller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index f096917aa8e..efca0e2c0c8 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -325,6 +325,7 @@ class AccountsController(TransactionBase): ple = frappe.qb.DocType("Payment Ledger Entry") frappe.qb.from_(ple).delete().where( (ple.voucher_type == self.doctype) & (ple.voucher_no == self.name) + | ((ple.against_voucher_type == self.doctype) & (ple.against_voucher_no == self.name)) ).run() frappe.db.sql( "delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name) From 3d243a7fa5918572eff91c6279c459c8b4c725d6 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 22 Feb 2024 16:00:52 +0530 Subject: [PATCH 17/50] fix: only check for delinked PLEs (cherry picked from commit 146c5b3e16b0521f0590aa6dfa957979fc6f3d9a) --- erpnext/controllers/accounts_controller.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index efca0e2c0c8..560e7715e68 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -325,7 +325,12 @@ class AccountsController(TransactionBase): ple = frappe.qb.DocType("Payment Ledger Entry") frappe.qb.from_(ple).delete().where( (ple.voucher_type == self.doctype) & (ple.voucher_no == self.name) - | ((ple.against_voucher_type == self.doctype) & (ple.against_voucher_no == self.name)) + | ( + (ple.against_voucher_type == self.doctype) + & (ple.against_voucher_no == self.name) + & ple.delinked + == 1 + ) ).run() frappe.db.sql( "delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name) From 96703fb34d0c5017126ccb54591c1c57543fa3d0 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 23 Feb 2024 12:42:10 +0530 Subject: [PATCH 18/50] build: specify frappe dependency (#40060) --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 7d1e7af50e4..7f28903f120 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,3 +43,6 @@ force_grid_wrap = 0 use_parentheses = true ensure_newline_before_comments = true indent = "\t" + +[tool.bench.frappe-dependencies] +frappe = ">=14.0.0,<15.0.0" From 8c7d0d4b85daa7749e5dee1dbe1fc0fcc6f4c158 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 22 Feb 2024 16:08:11 +0530 Subject: [PATCH 19/50] fix: skip max discount validation for rate adjustment (cherry picked from commit 5a3b133d6520661fe28e4c2d73c17ef4eb48be1e) --- erpnext/controllers/selling_controller.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index c9b12f4f723..34abe9f764f 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -31,7 +31,8 @@ class SellingController(StockController): def validate(self): super(SellingController, self).validate() self.validate_items() - self.validate_max_discount() + if not self.get("is_debit_note"): + self.validate_max_discount() self.validate_selling_price() self.set_qty_as_per_stock_uom() self.set_po_nos(for_validate=True) From 882cf98288103ad6ec731a4cfc58357471337287 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 22 Feb 2024 16:24:42 +0530 Subject: [PATCH 20/50] fix: skip SO & DN validation for debit note (cherry picked from commit e2d16955dd6de197ef621e5f8fb3159f5a9bfaaa) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 288acd9f792..730c47569fa 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -90,7 +90,7 @@ class SalesInvoice(SellingController): super(SalesInvoice, self).validate() self.validate_auto_set_posting_time() - if not self.is_pos: + if not (self.is_pos or self.is_debit_note): self.so_dn_required() self.set_tax_withholding() From 3112fca087153d95699ff9ecaa1fc1215d65fa91 Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Fri, 9 Feb 2024 15:18:45 +0530 Subject: [PATCH 21/50] fix: check_credit_limit on_update_after_submit of Sales Order (cherry picked from commit 17452b76933e7edb087741671f876782d0a70859) --- erpnext/selling/doctype/sales_order/sales_order.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index bec24fe5e99..82680368181 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -362,6 +362,9 @@ class SalesOrder(SellingController): def on_update(self): pass + def on_update_after_submit(self): + self.check_credit_limit() + def before_update_after_submit(self): self.validate_po() self.validate_drop_ship() From db7c360fa1d7283cb5c145795e35e50803f6b7d3 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 23 Feb 2024 14:03:09 +0530 Subject: [PATCH 22/50] test: credit limit on update after submit (cherry picked from commit 467c0898e9a2e4fb84049a3a3a20d3aa63c7f5a0) --- .../selling/doctype/customer/test_customer.py | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index 7a601a78876..7e91e6e6599 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -297,11 +297,35 @@ class TestCustomer(FrappeTestCase): if credit_limit > outstanding_amt: set_credit_limit("_Test Customer", "_Test Company", credit_limit) - # Makes Sales invoice from Sales Order - so.save(ignore_permissions=True) - si = make_sales_invoice(so.name) - si.save(ignore_permissions=True) - self.assertRaises(frappe.ValidationError, make_sales_order) + def test_customer_credit_limit_after_submit(self): + from erpnext.controllers.accounts_controller import update_child_qty_rate + from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order + + outstanding_amt = self.get_customer_outstanding_amount() + credit_limit = get_credit_limit("_Test Customer", "_Test Company") + + if outstanding_amt <= 0.0: + item_qty = int((abs(outstanding_amt) + 200) / 100) + make_sales_order(qty=item_qty) + + if credit_limit <= 0.0: + set_credit_limit("_Test Customer", "_Test Company", outstanding_amt + 100) + + so = make_sales_order(rate=100, qty=1) + # Update qty in submitted Sales Order to trigger Credit Limit validation + fields = ["name", "item_code", "delivery_date", "conversion_factor", "qty", "rate", "uom", "idx"] + modified_item = frappe._dict() + for x in fields: + modified_item[x] = so.items[0].get(x) + modified_item["docname"] = so.items[0].name + modified_item["qty"] = 2 + self.assertRaises( + frappe.ValidationError, + update_child_qty_rate, + so.doctype, + frappe.json.dumps([modified_item]), + so.name, + ) def test_customer_credit_limit_on_change(self): outstanding_amt = self.get_customer_outstanding_amount() From 579b27d0102c86c42c32b61c2adbd16280d45532 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 24 Feb 2024 17:22:37 +0530 Subject: [PATCH 23/50] fix: Cannot read properties of undefined (backport #40081) (#40082) * fix: Cannot read properties of undefined (cherry picked from commit 44ed52c5cfa6a2432769bbb6f3c352aa81025de2) * chore: fix linter issue --------- Co-authored-by: Rohit Waghchaure --- erpnext/public/js/utils.js | 5 ++++- .../doctype/stock_reconciliation/stock_reconciliation.py | 5 +---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 4c76e2a869e..27c7444daf4 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -40,7 +40,10 @@ $.extend(erpnext, { is_perpetual_inventory_enabled: function(company) { if(company) { - return frappe.get_doc(":Company", company).enable_perpetual_inventory + let company_local = locals[":Company"] && locals[":Company"][company]; + if(company_local) { + return cint(company_local.enable_perpetual_inventory); + } } }, diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 44c2d85b4cc..878af0e22ca 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -702,10 +702,7 @@ class StockReconciliation(StockController): if allow_negative_stock: return True - if any( - (d.batch_no and flt(d.qty) == flt(d.current_qty)) - for d in self.items - ): + if any((d.batch_no and flt(d.qty) == flt(d.current_qty)) for d in self.items): allow_negative_stock = True return allow_negative_stock From e4611853171bc1fdcd68a3de5f373e82cae4d108 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 24 Feb 2024 21:02:45 +0530 Subject: [PATCH 24/50] fix: do not make MR against raw materials of available sub assemblies (backport #40085) (#40086) fix: do not make MR against raw materials of available sub assemblies (#40085) (cherry picked from commit 4c9048fb3960668edd63a6e716b26d2e444bc1f9) Co-authored-by: rohitwaghchaure --- .../production_plan/production_plan.py | 24 +++++++-------- .../production_plan/test_production_plan.py | 29 +++++++++++++++++++ 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index aea6c987177..5495c49803d 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -1430,19 +1430,17 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get("idx"))) if bom_no: - if ( - data.get("include_exploded_items") - and doc.get("sub_assembly_items") - and doc.get("skip_available_sub_assembly_item") - ): - item_details = get_raw_materials_of_sub_assembly_items( - item_details, - company, - bom_no, - include_non_stock_items, - sub_assembly_items, - planned_qty=planned_qty, - ) + if data.get("include_exploded_items") and doc.get("skip_available_sub_assembly_item"): + item_details = {} + if doc.get("sub_assembly_items"): + item_details = get_raw_materials_of_sub_assembly_items( + item_details, + company, + bom_no, + include_non_stock_items, + sub_assembly_items, + planned_qty=planned_qty, + ) elif data.get("include_exploded_items") and include_subcontracted_items: # fetch exploded items from BOM diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 2b9751926a2..3d878c140f8 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1221,6 +1221,35 @@ class TestProductionPlan(FrappeTestCase): if row.item_code == "SubAssembly2 For SUB Test": self.assertEqual(row.quantity, 10) + def test_sub_assembly_and_their_raw_materials_exists(self): + from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom + + bom_tree = { + "FG1 For SUB Test": { + "SAB1 For SUB Test": {"CP1 For SUB Test": {}}, + "SAB2 For SUB Test": {}, + } + } + + parent_bom = create_nested_bom(bom_tree, prefix="") + for item in ["SAB1 For SUB Test", "SAB2 For SUB Test"]: + make_stock_entry(item_code=item, qty=10, rate=100, target="_Test Warehouse - _TC") + + plan = create_production_plan( + item_code=parent_bom.item, + planned_qty=10, + ignore_existing_ordered_qty=1, + do_not_submit=1, + skip_available_sub_assembly_item=1, + warehouse="_Test Warehouse - _TC", + ) + + items = get_items_for_material_requests( + plan.as_dict(), warehouses=[{"warehouse": "_Test Warehouse - _TC"}] + ) + + self.assertFalse(items) + def test_transfer_and_purchase_mrp_for_purchase_uom(self): from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse From 4bc6c551983121b4db86b5d63df628b859288761 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 23 Feb 2024 17:22:01 +0530 Subject: [PATCH 25/50] refactor: update payments section on item removal (cherry picked from commit 406793a6ff2f4e9aef9fc30d20fa2c3b81ca2f50) --- erpnext/public/js/controllers/transaction.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index d5bc7647647..b4b85c4a2bb 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -140,7 +140,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } if(this.frm.fields_dict["items"]) { - this["items_remove"] = this.calculate_net_weight; + this["items_remove"] = this.process_item_removal; } if(this.frm.fields_dict["recurring_print_format"]) { @@ -1192,6 +1192,11 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } } + process_item_removal() { + this.frm.trigger("calculate_taxes_and_totals"); + this.frm.trigger("calculate_net_weight"); + } + calculate_net_weight(){ /* Calculate Total Net Weight then further applied shipping rule to calculate shipping charges.*/ var me = this; From bb3a7cdb3dfa6db1a219fc80d6c93f6e9f28001d Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sun, 25 Feb 2024 21:32:24 +0530 Subject: [PATCH 26/50] fix: amount label according to party type (cherry picked from commit 9c8d103d8af85e52095071d43d8b8a03bdbf3255) --- .../accounts/report/tds_payable_monthly/tds_payable_monthly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index ba5cdbe6567..c0963150b1f 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -242,7 +242,7 @@ def get_columns(filters): "width": 120, }, { - "label": _("Tax Amount"), + "label": _("TDS Amount") if filters.get("party_type") == "Supplier" else _("TCS Amount"), "fieldname": "tax_amount", "fieldtype": "Float", "width": 120, From 0484857402b987212084a083f15cb759103dca82 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 23 Feb 2024 12:14:54 +0530 Subject: [PATCH 27/50] fix: only consider contributed qty towards achieved targets (cherry picked from commit 339698d172c2c070fb843ac2bbf1e8c6d6c7b8dc) --- .../item_group_wise_sales_target_variance.py | 56 +++++++++---------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py index 7d28f2b90d2..f2f1e4cfbaa 100644 --- a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py +++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py @@ -206,42 +206,36 @@ def prepare_data( def get_actual_data(filters, sales_users_or_territory_data, date_field, sales_field): fiscal_year = get_fiscal_year(fiscal_year=filters.get("fiscal_year"), as_dict=1) - dates = [fiscal_year.year_start_date, fiscal_year.year_end_date] - select_field = "`tab{0}`.{1}".format(filters.get("doctype"), sales_field) - child_table = "`tab{0}`".format(filters.get("doctype") + " Item") + parent_doc = frappe.qb.DocType(filters.get("doctype")) + child_doc = frappe.qb.DocType(filters.get("doctype") + " Item") + sales_team = frappe.qb.DocType("Sales Team") + + query = ( + frappe.qb.from_(parent_doc) + .inner_join(child_doc) + .on(child_doc.parent == parent_doc.name) + .inner_join(sales_team) + .on(sales_team.parent == parent_doc.name) + .select( + child_doc.item_group, + (child_doc.stock_qty * sales_team.allocated_percentage / 100).as_("stock_qty"), + (child_doc.base_net_amount * sales_team.allocated_percentage / 100).as_("base_net_amount"), + sales_team.sales_person, + parent_doc[date_field], + ) + .where( + (parent_doc.docstatus == 1) + & (parent_doc[date_field].between(fiscal_year.year_start_date, fiscal_year.year_end_date)) + ) + ) if sales_field == "sales_person": - select_field = "`tabSales Team`.sales_person" - child_table = "`tab{0}`, `tabSales Team`".format(filters.get("doctype") + " Item") - cond = """`tabSales Team`.parent = `tab{0}`.name and - `tabSales Team`.sales_person in ({1}) """.format( - filters.get("doctype"), ",".join(["%s"] * len(sales_users_or_territory_data)) - ) + query = query.where(sales_team.sales_person.isin(sales_users_or_territory_data)) else: - cond = "`tab{0}`.{1} in ({2})".format( - filters.get("doctype"), sales_field, ",".join(["%s"] * len(sales_users_or_territory_data)) - ) + query = query.where(parent_doc[sales_field].isin(sales_users_or_territory_data)) - return frappe.db.sql( - """ SELECT `tab{child_doc}`.item_group, - `tab{child_doc}`.stock_qty, `tab{child_doc}`.base_net_amount, - {select_field}, `tab{parent_doc}`.{date_field} - FROM `tab{parent_doc}`, {child_table} - WHERE - `tab{child_doc}`.parent = `tab{parent_doc}`.name - and `tab{parent_doc}`.docstatus = 1 and {cond} - and `tab{parent_doc}`.{date_field} between %s and %s""".format( - cond=cond, - date_field=date_field, - select_field=select_field, - child_table=child_table, - parent_doc=filters.get("doctype"), - child_doc=filters.get("doctype") + " Item", - ), - tuple(sales_users_or_territory_data + dates), - as_dict=1, - ) + return query.run(as_dict=True) def get_parents_data(filters, partner_doctype): From 89d19422d1f0236c10ae5165fc682c431c3d076c Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 23 Feb 2024 12:17:54 +0530 Subject: [PATCH 28/50] feat: show contributed qty in transaction summary (cherry picked from commit a823f16dff52ed8a92086aebbfa1b0a326eb2425) --- .../sales_person_wise_transaction_summary.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py index 9f3ba0da8bd..847488f6fbf 100644 --- a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py +++ b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py @@ -36,6 +36,7 @@ def execute(filters=None): d.base_net_amount, d.sales_person, d.allocated_percentage, + (d.stock_qty * d.allocated_percentage / 100), d.contribution_amt, company_currency, ] @@ -103,7 +104,7 @@ def get_columns(filters): "fieldtype": "Link", "width": 140, }, - {"label": _("Qty"), "fieldname": "qty", "fieldtype": "Float", "width": 140}, + {"label": _("SO Total Qty"), "fieldname": "qty", "fieldtype": "Float", "width": 140}, { "label": _("Amount"), "options": "currency", @@ -119,6 +120,12 @@ def get_columns(filters): "width": 140, }, {"label": _("Contribution %"), "fieldname": "contribution", "fieldtype": "Float", "width": 140}, + { + "label": _("Contribution Qty"), + "fieldname": "contribution_qty", + "fieldtype": "Float", + "width": 140, + }, { "label": _("Contribution Amount"), "options": "currency", From 57f4eb121c46ad66138bbdc2f381d9b9f47d8a3c Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 23 Feb 2024 15:05:36 +0530 Subject: [PATCH 29/50] test: sales person target variance (cherry picked from commit 7566c1ee783bc0ea79ed44f4ecd9d9ea8eef8dd0) --- ...son_target_variance_based_on_item_group.py | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 erpnext/selling/report/sales_person_target_variance_based_on_item_group/test_sales_person_target_variance_based_on_item_group.py diff --git a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/test_sales_person_target_variance_based_on_item_group.py b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/test_sales_person_target_variance_based_on_item_group.py new file mode 100644 index 00000000000..4ae5d2bee88 --- /dev/null +++ b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/test_sales_person_target_variance_based_on_item_group.py @@ -0,0 +1,84 @@ +import frappe +from frappe.tests.utils import FrappeTestCase +from frappe.utils import flt, nowdate + +from erpnext.accounts.utils import get_fiscal_year +from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order +from erpnext.selling.report.sales_person_target_variance_based_on_item_group.sales_person_target_variance_based_on_item_group import ( + execute, +) + + +class TestSalesPersonTargetVarianceBasedOnItemGroup(FrappeTestCase): + def setUp(self): + self.fiscal_year = get_fiscal_year(nowdate())[0] + + def tearDown(self): + frappe.db.rollback() + + def test_achieved_target_and_variance(self): + # Create a Target Distribution + distribution = frappe.new_doc("Monthly Distribution") + distribution.distribution_id = "Target Report Distribution" + distribution.fiscal_year = self.fiscal_year + distribution.get_months() + distribution.insert() + + # Create sales people with targets + person_1 = create_sales_person_with_target("Sales Person 1", self.fiscal_year, distribution.name) + person_2 = create_sales_person_with_target("Sales Person 2", self.fiscal_year, distribution.name) + + # Create a Sales Order with 50-50 contribution + so = make_sales_order( + rate=1000, + qty=20, + do_not_submit=True, + ) + so.set( + "sales_team", + [ + { + "sales_person": person_1.name, + "allocated_percentage": 50, + "allocated_amount": 10000, + }, + { + "sales_person": person_2.name, + "allocated_percentage": 50, + "allocated_amount": 10000, + }, + ], + ) + so.submit() + + # Check Achieved Target and Variance + result = execute( + frappe._dict( + { + "fiscal_year": self.fiscal_year, + "doctype": "Sales Order", + "period": "Yearly", + "target_on": "Quantity", + } + ) + )[1] + row = frappe._dict(result[0]) + self.assertSequenceEqual( + [flt(value, 2) for value in (row.total_target, row.total_achieved, row.total_variance)], + [50, 10, -40], + ) + + +def create_sales_person_with_target(sales_person_name, fiscal_year, distribution_id): + sales_person = frappe.new_doc("Sales Person") + sales_person.sales_person_name = sales_person_name + sales_person.append( + "targets", + { + "fiscal_year": fiscal_year, + "target_qty": 50, + "target_amount": 30000, + "distribution_id": distribution_id, + }, + ) + return sales_person.insert() From 6828a2d46d4009db4fa9c386292fcf44e32d899d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 26 Feb 2024 11:31:27 +0530 Subject: [PATCH 30/50] refactor: patch to setup dimensions in Reconciliation tool (cherry picked from commit 461fb183fc5269ca4e77da1115e173b536ca3fd9) --- erpnext/patches.txt | 1 + ...create_accounting_dimensions_in_reconciliation_tool.py | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 erpnext/patches/v14_0/create_accounting_dimensions_in_reconciliation_tool.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 125158a5add..90650a640ad 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -353,6 +353,7 @@ erpnext.patches.v14_0.update_zero_asset_quantity_field execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction") erpnext.patches.v14_0.clear_reconciliation_values_from_singles erpnext.patches.v14_0.update_total_asset_cost_field +erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20 diff --git a/erpnext/patches/v14_0/create_accounting_dimensions_in_reconciliation_tool.py b/erpnext/patches/v14_0/create_accounting_dimensions_in_reconciliation_tool.py new file mode 100644 index 00000000000..4466eaace8d --- /dev/null +++ b/erpnext/patches/v14_0/create_accounting_dimensions_in_reconciliation_tool.py @@ -0,0 +1,8 @@ +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + create_accounting_dimensions_for_doctype, +) + + +def execute(): + create_accounting_dimensions_for_doctype(doctype="Payment Reconciliation") + create_accounting_dimensions_for_doctype(doctype="Payment Reconciliation Allocation") From fe88a110b1c6c7eebc015f4ad90319fcdf1246b1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 26 Feb 2024 17:08:56 +0530 Subject: [PATCH 31/50] fix: on unreconciliation, update advance paid (cherry picked from commit c9e2f03a3a30a382c0fdb2c065e77f43fed2ced3) --- .../doctype/unreconcile_payment/unreconcile_payment.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py index 77906a78332..ae9c19187ba 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py @@ -63,6 +63,11 @@ class UnreconcilePayment(Document): update_voucher_outstanding( alloc.reference_doctype, alloc.reference_name, alloc.account, alloc.party_type, alloc.party ) + if doc.doctype in frappe.get_hooks("advance_payment_payable_doctypes") + frappe.get_hooks( + "advance_payment_receivable_doctypes" + ): + doc.set_total_advance_paid() + frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True) From 58c869c5628b8ef6d26ce076b5e775ae684a4812 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 26 Feb 2024 17:32:25 +0530 Subject: [PATCH 32/50] test: advance paid update on sales/purchase order unreconciliation (cherry picked from commit 1f01ff3487f55a86723106b9f3c6d42cdaa837ba) --- .../test_unreconcile_payment.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py index f404d9981a3..57f66dd21db 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py @@ -8,6 +8,7 @@ from frappe.utils import today from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase): @@ -49,6 +50,16 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase): ) return pe + def create_sales_order(self): + so = make_sales_order( + company=self.company, + customer=self.customer, + item=self.item, + rate=100, + transaction_date=today(), + ) + return so + def test_01_unreconcile_invoice(self): si1 = self.create_sales_invoice() si2 = self.create_sales_invoice() @@ -314,3 +325,41 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase): ), 1, ) + + def test_05_unreconcile_order(self): + so = self.create_sales_order() + + pe = self.create_payment_entry() + # Allocation payment against Sales Order + pe.paid_amount = 100 + pe.append( + "references", + {"reference_doctype": so.doctype, "reference_name": so.name, "allocated_amount": 100}, + ) + pe.save().submit() + + # Assert 'Advance Paid' + so.reload() + self.assertEqual(so.advance_paid, 100) + + unreconcile = frappe.get_doc( + { + "doctype": "Unreconcile Payment", + "company": self.company, + "voucher_type": pe.doctype, + "voucher_no": pe.name, + } + ) + unreconcile.add_references() + self.assertEqual(len(unreconcile.allocations), 1) + allocations = [x.reference_name for x in unreconcile.allocations] + self.assertEquals([so.name], allocations) + # unreconcile so + unreconcile.save().submit() + + # Assert 'Advance Paid' + so.reload() + pe.reload() + self.assertEqual(so.advance_paid, 0) + self.assertEqual(len(pe.references), 0) + self.assertEqual(pe.unallocated_amount, 100) From 8a573a1aff13dd878e5c990fd0b2d8f492ed8ad2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 18:52:05 +0530 Subject: [PATCH 33/50] fix: capacity planning issue in the job card (backport #40092) (#40100) * fix: capacity planning issue in the job card (#40092) * fix: capacity planning issue in the job card * test: test case to test capacity planning for workstation (cherry picked from commit 75f8464724382ac703a16ee3fd73de2c9bb56d13) # Conflicts: # erpnext/manufacturing/doctype/job_card/job_card.py # erpnext/manufacturing/doctype/work_order/test_work_order.py * chore: fix conflicts * chore: fix conflicts * chore: fix test cases --------- Co-authored-by: rohitwaghchaure --- .../doctype/job_card/job_card.py | 25 +++- .../doctype/work_order/test_work_order.py | 107 ++++++++++++++++++ .../doctype/work_order/work_order.py | 19 +++- 3 files changed, 139 insertions(+), 12 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index f47858ba9ff..1823efbbaaa 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -163,7 +163,7 @@ class JobCard(Document): for row in self.sub_operations: self.total_completed_qty += row.completed_qty - def get_overlap_for(self, args, check_next_available_slot=False): + def get_overlap_for(self, args): production_capacity = 1 jc = frappe.qb.DocType("Job Card") @@ -175,9 +175,6 @@ class JobCard(Document): ((jctl.from_time >= args.from_time) & (jctl.to_time <= args.to_time)), ] - if check_next_available_slot: - time_conditions.append(((jctl.from_time >= args.from_time) & (jctl.to_time >= args.to_time))) - query = ( frappe.qb.from_(jctl) .from_(jc) @@ -279,13 +276,29 @@ class JobCard(Document): self.check_workstation_time(row) def validate_overlap_for_workstation(self, args, row): + if args.get("to_time") and get_datetime(args.to_time) < get_datetime(args.from_time): + args.to_time = add_to_date(row.planned_start_time, minutes=row.remaining_time_in_mins) + # get the last record based on the to time from the job card - data = self.get_overlap_for(args, check_next_available_slot=True) + data = self.get_overlap_for(args) + + if not data: + row.planned_start_time = args.from_time + return + if data: if not self.workstation: self.workstation = data.workstation - row.planned_start_time = get_datetime(data.to_time + get_mins_between_operations()) + if data.get("planned_start_time"): + args.planned_start_time = get_datetime(data.planned_start_time) + else: + args.planned_start_time = get_datetime(data.to_time + get_mins_between_operations()) + + args.from_time = args.planned_start_time + args.to_time = add_to_date(args.planned_start_time, minutes=row.remaining_time_in_mins) + + self.validate_overlap_for_workstation(args, row) def check_workstation_time(self, row): workstation_doc = frappe.get_cached_doc("Workstation", self.workstation) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 74c2ece6e7e..8e24d4cd59a 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -1778,6 +1778,113 @@ class TestWorkOrder(FrappeTestCase): "Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies", 0 ) + def test_capcity_planning_for_workstation(self): + frappe.db.set_single_value( + "Manufacturing Settings", + { + "disable_capacity_planning": 0, + "capacity_planning_for_days": 1, + "mins_between_operations": 10, + }, + ) + + properties = {"is_stock_item": 1, "valuation_rate": 100} + fg_item = make_item("Test FG Item For Capacity Planning", properties).name + + rm_item = make_item("Test RM Item For Capacity Planning", properties).name + + workstation = "Test Workstation For Capacity Planning" + if not frappe.db.exists("Workstation", workstation): + make_workstation(workstation=workstation, production_capacity=1) + + operation = "Test Operation For Capacity Planning" + if not frappe.db.exists("Operation", operation): + make_operation(operation=operation, workstation=workstation) + + bom_doc = make_bom( + item=fg_item, + source_warehouse="Stores - _TC", + raw_materials=[rm_item], + with_operations=1, + do_not_submit=True, + ) + + bom_doc.append( + "operations", + {"operation": operation, "time_in_mins": 1420, "hour_rate": 100, "workstation": workstation}, + ) + bom_doc.submit() + + # 1st Work Order, + # Capacity to run parallel the operation 'Test Operation For Capacity Planning' is 2 + wo_doc = make_wo_order_test_record( + production_item=fg_item, qty=1, planned_start_date="2024-02-25 00:00:00", do_not_submit=1 + ) + + wo_doc.submit() + job_cards = frappe.get_all( + "Job Card", + filters={"work_order": wo_doc.name}, + ) + + self.assertEqual(len(job_cards), 1) + + # 2nd Work Order, + wo_doc = make_wo_order_test_record( + production_item=fg_item, qty=1, planned_start_date="2024-02-25 00:00:00", do_not_submit=1 + ) + + wo_doc.submit() + job_cards = frappe.get_all( + "Job Card", + filters={"work_order": wo_doc.name}, + ) + + self.assertEqual(len(job_cards), 1) + + # 3rd Work Order, capacity is full + wo_doc = make_wo_order_test_record( + production_item=fg_item, qty=1, planned_start_date="2024-02-25 00:00:00", do_not_submit=1 + ) + + self.assertRaises(CapacityError, wo_doc.submit) + + frappe.db.set_single_value( + "Manufacturing Settings", {"disable_capacity_planning": 1, "mins_between_operations": 0} + ) + + +def make_operation(**kwargs): + kwargs = frappe._dict(kwargs) + + operation_doc = frappe.get_doc( + { + "doctype": "Operation", + "name": kwargs.operation, + "workstation": kwargs.workstation, + } + ) + operation_doc.insert() + + return operation_doc + + +def make_workstation(**kwargs): + kwargs = frappe._dict(kwargs) + + workstation_doc = frappe.get_doc( + { + "doctype": "Workstation", + "workstation_name": kwargs.workstation, + "workstation_type": kwargs.workstation_type, + "production_capacity": kwargs.production_capacity or 0, + "hour_rate": kwargs.hour_rate or 100, + } + ) + workstation_doc.insert() + + return workstation_doc + def prepare_boms_for_sub_assembly_test(): if not frappe.db.exists("BOM", {"item": "Test Final SF Item 1"}): diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index f95b4f66a33..77a0c54c734 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -172,8 +172,12 @@ class WorkOrder(Document): def calculate_operating_cost(self): self.planned_operating_cost, self.actual_operating_cost = 0.0, 0.0 for d in self.get("operations"): - d.planned_operating_cost = flt(d.hour_rate) * (flt(d.time_in_mins) / 60.0) - d.actual_operating_cost = flt(d.hour_rate) * (flt(d.actual_operation_time) / 60.0) + d.planned_operating_cost = flt( + flt(d.hour_rate) * (flt(d.time_in_mins) / 60.0), d.precision("planned_operating_cost") + ) + d.actual_operating_cost = flt( + flt(d.hour_rate) * (flt(d.actual_operation_time) / 60.0), d.precision("actual_operating_cost") + ) self.planned_operating_cost += flt(d.planned_operating_cost) self.actual_operating_cost += flt(d.actual_operating_cost) @@ -489,7 +493,6 @@ class WorkOrder(Document): def prepare_data_for_job_card(self, row, index, plan_days, enable_capacity_planning): self.set_operation_start_end_time(index, row) - original_start_time = row.planned_start_time job_card_doc = create_job_card( self, row, auto_create=True, enable_capacity_planning=enable_capacity_planning ) @@ -498,11 +501,15 @@ class WorkOrder(Document): row.planned_start_time = job_card_doc.time_logs[-1].from_time row.planned_end_time = job_card_doc.time_logs[-1].to_time - if date_diff(row.planned_start_time, original_start_time) > plan_days: + if date_diff(row.planned_end_time, self.planned_start_date) > plan_days: frappe.message_log.pop() frappe.throw( - _("Unable to find the time slot in the next {0} days for the operation {1}.").format( - plan_days, row.operation + _( + "Unable to find the time slot in the next {0} days for the operation {1}. Please increase the 'Capacity Planning For (Days)' in the {2}." + ).format( + plan_days, + row.operation, + get_link_to_form("Manufacturing Settings", "Manufacturing Settings"), ), CapacityError, ) From 812eebf1ab7c71ca8daea273684dec5d0f4a539b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 26 Feb 2024 20:32:48 +0530 Subject: [PATCH 34/50] fix: use correct variable name for hotfix branches --- .../doctype/unreconcile_payment/unreconcile_payment.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py index ae9c19187ba..dd714573b1b 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py @@ -63,9 +63,7 @@ class UnreconcilePayment(Document): update_voucher_outstanding( alloc.reference_doctype, alloc.reference_name, alloc.account, alloc.party_type, alloc.party ) - if doc.doctype in frappe.get_hooks("advance_payment_payable_doctypes") + frappe.get_hooks( - "advance_payment_receivable_doctypes" - ): + if doc.doctype in frappe.get_hooks("advance_payment_doctypes"): doc.set_total_advance_paid() frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True) From 193cd0db96d45c9bdd377a29e7ae530ed18ec0aa Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Mon, 26 Feb 2024 23:01:41 +0530 Subject: [PATCH 35/50] fix: sales funnel text color in light/dark theme (#40132) * fix: sales funnel text color in light/dark theme * fix: sales funnel text color in light/dark theme --- erpnext/selling/page/sales_funnel/sales_funnel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/page/sales_funnel/sales_funnel.js b/erpnext/selling/page/sales_funnel/sales_funnel.js index e3d0a55c3a0..c37c7266362 100644 --- a/erpnext/selling/page/sales_funnel/sales_funnel.js +++ b/erpnext/selling/page/sales_funnel/sales_funnel.js @@ -229,7 +229,7 @@ erpnext.SalesFunnel = class SalesFunnel { context.fill(); // draw text - context.fillStyle = "black"; + context.fillStyle = ""; context.textBaseline = "middle"; context.font = "1.1em sans-serif"; context.fillText(__(title), width + 20, y_mid); From e289aef1b4a6a0ab4827214422486f16efd7d4cd Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 27 Feb 2024 09:59:08 +0530 Subject: [PATCH 36/50] fix: linting issue --- .../doctype/stock_reconciliation/stock_reconciliation.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 44c2d85b4cc..878af0e22ca 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -702,10 +702,7 @@ class StockReconciliation(StockController): if allow_negative_stock: return True - if any( - (d.batch_no and flt(d.qty) == flt(d.current_qty)) - for d in self.items - ): + if any((d.batch_no and flt(d.qty) == flt(d.current_qty)) for d in self.items): allow_negative_stock = True return allow_negative_stock From 65a2adfc551546744ad97194b5d72fc0b8f42237 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 27 Feb 2024 12:16:08 +0530 Subject: [PATCH 37/50] fix: linting issue --- .../doctype/stock_reconciliation/stock_reconciliation.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 44c2d85b4cc..878af0e22ca 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -702,10 +702,7 @@ class StockReconciliation(StockController): if allow_negative_stock: return True - if any( - (d.batch_no and flt(d.qty) == flt(d.current_qty)) - for d in self.items - ): + if any((d.batch_no and flt(d.qty) == flt(d.current_qty)) for d in self.items): allow_negative_stock = True return allow_negative_stock From 8638d14babaf4c846a7f6222c7018fc8f59545b2 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sun, 25 Feb 2024 13:17:41 +0530 Subject: [PATCH 38/50] fix: unique gl account for plaid bank accounts (cherry picked from commit bf6e32a960e761fa869b44db15cf61fabb5c7aef) --- .../doctype/plaid_settings/plaid_settings.py | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index 5354d0d6c13..af1052a3700 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -10,7 +10,6 @@ from frappe.model.document import Document from frappe.utils import add_months, formatdate, getdate, sbool, today from plaid.errors import ItemError -from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_connector import PlaidConnector @@ -74,9 +73,15 @@ def add_bank_accounts(response, bank, company): bank = json.loads(bank) result = [] - default_gl_account = get_default_bank_cash_account(company, "Bank") - if not default_gl_account: - frappe.throw(_("Please setup a default bank account for company {0}").format(company)) + parent_gl_account = frappe.db.get_all( + "Account", {"company": company, "account_type": "Bank", "is_group": 1, "disabled": 0} + ) + if not parent_gl_account: + frappe.throw( + _( + "Please setup and enable a group account with the Account Type - {0} for the company {1}" + ).format(frappe.bold("Bank"), company) + ) for account in response["accounts"]: acc_type = frappe.db.get_value("Bank Account Type", account["type"]) @@ -92,11 +97,22 @@ def add_bank_accounts(response, bank, company): if not existing_bank_account: try: + gl_account = frappe.get_doc( + { + "doctype": "Account", + "account_name": account["name"] + " - " + response["institution"]["name"], + "parent_account": parent_gl_account[0].name, + "account_type": "Bank", + "company": company, + } + ) + gl_account.insert(ignore_if_duplicate=True) + new_account = frappe.get_doc( { "doctype": "Bank Account", "bank": bank["bank_name"], - "account": default_gl_account.account, + "account": gl_account.name, "account_name": account["name"], "account_type": account.get("type", ""), "account_subtype": account.get("subtype", ""), From ad6067795f87140c3252c9053d01e22f735dce49 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sun, 25 Feb 2024 13:18:30 +0530 Subject: [PATCH 39/50] fix: remove config for default bank account in test (cherry picked from commit c42444ab3bf1e35ad5bb87f5f94ea78b9052c12e) --- .../doctype/plaid_settings/test_plaid_settings.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py index 6d34a204cd2..302bdc436c6 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py @@ -7,7 +7,6 @@ import unittest import frappe from frappe.utils.response import json_handler -from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings import ( add_account_subtype, add_account_type, @@ -106,14 +105,6 @@ class TestPlaidSettings(unittest.TestCase): bank = json.dumps(frappe.get_doc("Bank", "Citi").as_dict(), default=json_handler) company = frappe.db.get_single_value("Global Defaults", "default_company") - if frappe.db.get_value("Company", company, "default_bank_account") is None: - frappe.db.set_value( - "Company", - company, - "default_bank_account", - get_default_bank_cash_account(company, "Cash").get("account"), - ) - add_bank_accounts(bank_accounts, bank, company) transactions = { From 019d8f92fed10d479870ecea94b6abc2f7c508dd Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sun, 25 Feb 2024 11:59:44 +0530 Subject: [PATCH 40/50] feat: update billed amount in PO and PR (cherry picked from commit 9f6535472d50e651fa99ef7b9001ce913acb6902) # Conflicts: # erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json # erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py --- .../purchase_invoice/purchase_invoice.json | 20 ++ .../purchase_invoice/purchase_invoice.py | 186 ++++++++++++++++++ 2 files changed, 206 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 643047e982d..de81742ac00 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -22,6 +22,8 @@ "is_paid", "is_return", "return_against", + "update_billed_amount_in_purchase_order", + "update_billed_amount_in_purchase_receipt", "apply_tds", "tax_withholding_category", "amended_from", @@ -410,6 +412,20 @@ "read_only": 1, "search_index": 1 }, + { + "default": "0", + "depends_on": "eval: doc.is_return", + "fieldname": "update_billed_amount_in_purchase_order", + "fieldtype": "Check", + "label": "Update Billed Amount in Purchase Order" + }, + { + "default": "1", + "depends_on": "eval: doc.is_return", + "fieldname": "update_billed_amount_in_purchase_receipt", + "fieldtype": "Check", + "label": "Update Billed Amount in Purchase Receipt" + }, { "fieldname": "section_addresses", "fieldtype": "Section Break", @@ -1594,7 +1610,11 @@ "idx": 204, "is_submittable": 1, "links": [], +<<<<<<< HEAD "modified": "2023-11-03 15:47:30.319200", +======= + "modified": "2024-02-25 11:20:28.366808", +>>>>>>> 9f6535472d (feat: update billed amount in PO and PR) "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 802ac8080a9..c04f8d40364 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -54,6 +54,180 @@ form_grid_templates = {"items": "templates/form_grid/item_grid.html"} class PurchaseInvoice(BuyingController): +<<<<<<< HEAD +======= + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + from erpnext.accounts.doctype.advance_tax.advance_tax import AdvanceTax + 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.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_taxes_and_charges.purchase_taxes_and_charges import ( + PurchaseTaxesandCharges, + ) + 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, + ) + + additional_discount_percentage: DF.Float + address_display: DF.SmallText | None + advance_tax: DF.Table[AdvanceTax] + advances: DF.Table[PurchaseInvoiceAdvance] + against_expense_account: DF.SmallText | None + allocate_advances_automatically: DF.Check + amended_from: DF.Link | None + apply_discount_on: DF.Literal["", "Grand Total", "Net Total"] + apply_tds: DF.Check + auto_repeat: DF.Link | None + base_discount_amount: DF.Currency + base_grand_total: DF.Currency + base_in_words: DF.Data | None + base_net_total: DF.Currency + base_paid_amount: DF.Currency + base_rounded_total: DF.Currency + base_rounding_adjustment: DF.Currency + base_tax_withholding_net_total: DF.Currency + base_taxes_and_charges_added: DF.Currency + base_taxes_and_charges_deducted: DF.Currency + base_total: DF.Currency + base_total_taxes_and_charges: DF.Currency + base_write_off_amount: DF.Currency + bill_date: DF.Date | None + bill_no: DF.Data | None + billing_address: DF.Link | None + billing_address_display: DF.SmallText | None + buying_price_list: DF.Link | None + cash_bank_account: DF.Link | None + clearance_date: DF.Date | None + company: DF.Link | None + contact_display: DF.SmallText | None + contact_email: DF.SmallText | None + contact_mobile: DF.SmallText | None + contact_person: DF.Link | None + conversion_rate: DF.Float + cost_center: DF.Link | None + credit_to: DF.Link + currency: DF.Link | None + disable_rounded_total: DF.Check + discount_amount: DF.Currency + due_date: DF.Date | None + from_date: DF.Date | None + grand_total: DF.Currency + group_same_items: DF.Check + hold_comment: DF.SmallText | None + ignore_default_payment_terms_template: DF.Check + ignore_pricing_rule: DF.Check + in_words: DF.Data | None + incoterm: DF.Link | None + inter_company_invoice_reference: DF.Link | None + is_internal_supplier: DF.Check + is_old_subcontracting_flow: DF.Check + is_opening: DF.Literal["No", "Yes"] + is_paid: DF.Check + is_return: DF.Check + is_subcontracted: DF.Check + items: DF.Table[PurchaseInvoiceItem] + language: DF.Data | None + letter_head: DF.Link | None + mode_of_payment: DF.Link | None + named_place: DF.Data | None + naming_series: DF.Literal["ACC-PINV-.YYYY.-", "ACC-PINV-RET-.YYYY.-"] + net_total: DF.Currency + on_hold: DF.Check + only_include_allocated_payments: DF.Check + other_charges_calculation: DF.LongText | None + outstanding_amount: DF.Currency + paid_amount: DF.Currency + party_account_currency: DF.Link | None + payment_schedule: DF.Table[PaymentSchedule] + payment_terms_template: DF.Link | None + per_received: DF.Percent + plc_conversion_rate: DF.Float + posting_date: DF.Date + posting_time: DF.Time | None + price_list_currency: DF.Link | None + pricing_rules: DF.Table[PricingRuleDetail] + project: DF.Link | None + rejected_warehouse: DF.Link | None + release_date: DF.Date | None + remarks: DF.SmallText | None + repost_required: DF.Check + represents_company: DF.Link | None + return_against: DF.Link | None + rounded_total: DF.Currency + rounding_adjustment: DF.Currency + scan_barcode: DF.Data | None + select_print_heading: DF.Link | None + set_from_warehouse: DF.Link | None + set_posting_time: DF.Check + set_warehouse: DF.Link | None + shipping_address: DF.Link | None + shipping_address_display: DF.SmallText | None + shipping_rule: DF.Link | None + status: DF.Literal[ + "", + "Draft", + "Return", + "Debit Note Issued", + "Submitted", + "Paid", + "Partly Paid", + "Unpaid", + "Overdue", + "Cancelled", + "Internal Transfer", + ] + subscription: DF.Link | None + supplied_items: DF.Table[PurchaseReceiptItemSupplied] + supplier: DF.Link + supplier_address: DF.Link | None + supplier_group: DF.Link | None + supplier_name: DF.Data | None + supplier_warehouse: DF.Link | None + tax_category: DF.Link | None + tax_id: DF.ReadOnly | None + tax_withheld_vouchers: DF.Table[TaxWithheldVouchers] + tax_withholding_category: DF.Link | None + tax_withholding_net_total: DF.Currency + taxes: DF.Table[PurchaseTaxesandCharges] + taxes_and_charges: DF.Link | None + taxes_and_charges_added: DF.Currency + taxes_and_charges_deducted: DF.Currency + tc_name: DF.Link | None + terms: DF.TextEditor | None + title: DF.Data | None + to_date: DF.Date | None + total: DF.Currency + total_advance: DF.Currency + total_net_weight: DF.Float + total_qty: DF.Float + total_taxes_and_charges: DF.Currency + unrealized_profit_loss_account: DF.Link | None + update_billed_amount_in_purchase_order: DF.Check + update_billed_amount_in_purchase_receipt: DF.Check + update_stock: DF.Check + use_company_roundoff_cost_center: DF.Check + use_transaction_date_exchange_rate: DF.Check + write_off_account: DF.Link | None + write_off_amount: DF.Currency + write_off_cost_center: DF.Link | None + # end: auto-generated types + +>>>>>>> 9f6535472d (feat: update billed amount in PO and PR) def __init__(self, *args, **kwargs): super(PurchaseInvoice, self).__init__(*args, **kwargs) self.status_updater = [ @@ -514,6 +688,11 @@ class PurchaseInvoice(BuyingController): super(PurchaseInvoice, self).on_submit() self.check_prev_docstatus() + + if self.is_return and not self.update_billed_amount_in_purchase_order: + # NOTE status updating bypassed for is_return + self.status_updater = [] + self.update_status_updater_args() self.update_prevdoc_status() @@ -1264,6 +1443,10 @@ class PurchaseInvoice(BuyingController): self.check_on_hold_or_closed_status() + if self.is_return and not self.update_billed_amount_in_purchase_order: + # NOTE status updating bypassed for is_return + self.status_updater = [] + self.update_status_updater_args() self.update_prevdoc_status() @@ -1357,6 +1540,9 @@ class PurchaseInvoice(BuyingController): frappe.throw(_("Supplier Invoice No exists in Purchase Invoice {0}").format(pi)) def update_billing_status_in_pr(self, update_modified=True): + if self.is_return and not self.update_billed_amount_in_purchase_receipt: + return + updated_pr = [] po_details = [] From 4f87e73f559e36a30bbfc342510b9079e843d712 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sun, 25 Feb 2024 19:40:28 +0530 Subject: [PATCH 41/50] test: po billed amount against debit note (cherry picked from commit 81dbfe189e66f48cee4a37c54544aeaa4f0b3c2e) # Conflicts: # erpnext/buying/doctype/purchase_order/test_purchase_order.py --- .../purchase_order/test_purchase_order.py | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 739a989c79e..f2c12ae6733 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -940,6 +940,165 @@ class TestPurchaseOrder(FrappeTestCase): self.assertRaises(frappe.ValidationError, po.save) +<<<<<<< HEAD +======= + def test_update_items_for_subcontracting_purchase_order(self): + from erpnext.controllers.tests.test_subcontracting_controller import ( + get_subcontracting_order, + make_bom_for_subcontracted_items, + make_raw_materials, + make_service_items, + make_subcontracted_items, + ) + + def update_items(po, qty): + trans_items = [po.items[0].as_dict()] + trans_items[0]["qty"] = qty + trans_items[0]["fg_item_qty"] = qty + trans_items = json.dumps(trans_items, default=str) + + return update_child_qty_rate( + po.doctype, + trans_items, + po.name, + ) + + make_subcontracted_items() + make_raw_materials() + make_service_items() + make_bom_for_subcontracted_items() + + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 7", + "qty": 10, + "rate": 100, + "fg_item": "Subcontracted Item SA7", + "fg_item_qty": 10, + }, + ] + po = create_purchase_order( + rm_items=service_items, + is_subcontracted=1, + supplier_warehouse="_Test Warehouse 1 - _TC", + ) + + update_items(po, qty=20) + po.reload() + + # Test - 1: Items should be updated as there is no Subcontracting Order against PO + self.assertEqual(po.items[0].qty, 20) + self.assertEqual(po.items[0].fg_item_qty, 20) + + sco = get_subcontracting_order(po_name=po.name, warehouse="_Test Warehouse - _TC") + + # Test - 2: ValidationError should be raised as there is Subcontracting Order against PO + self.assertRaises(frappe.ValidationError, update_items, po=po, qty=30) + + sco.reload() + sco.cancel() + po.reload() + + update_items(po, qty=30) + po.reload() + + # Test - 3: Items should be updated as the Subcontracting Order is cancelled + self.assertEqual(po.items[0].qty, 30) + self.assertEqual(po.items[0].fg_item_qty, 30) + + @change_settings("Buying Settings", {"auto_create_subcontracting_order": 1}) + def test_auto_create_subcontracting_order(self): + from erpnext.controllers.tests.test_subcontracting_controller import ( + make_bom_for_subcontracted_items, + make_raw_materials, + make_service_items, + make_subcontracted_items, + ) + + make_subcontracted_items() + make_raw_materials() + make_service_items() + make_bom_for_subcontracted_items() + + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 7", + "qty": 10, + "rate": 100, + "fg_item": "Subcontracted Item SA7", + "fg_item_qty": 10, + }, + ] + po = create_purchase_order( + rm_items=service_items, + is_subcontracted=1, + supplier_warehouse="_Test Warehouse 1 - _TC", + ) + + self.assertTrue(frappe.db.get_value("Subcontracting Order", {"purchase_order": po.name})) + + def test_purchase_order_advance_payment_status(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request + + po = create_purchase_order() + self.assertEqual( + frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Not Initiated" + ) + + pr = make_payment_request(dt=po.doctype, dn=po.name, submit_doc=True, return_doc=True) + self.assertEqual(frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Initiated") + + pe = get_payment_entry(po.doctype, po.name).save().submit() + self.assertEqual( + frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Fully Paid" + ) + + pe.reload() + pe.cancel() + self.assertEqual(frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Initiated") + + pr.reload() + pr.cancel() + self.assertEqual( + frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Not Initiated" + ) + + def test_po_billed_amount_against_return_entry(self): + from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_debit_note + + # Create a Purchase Order and Fully Bill it + po = create_purchase_order() + pi = make_pi_from_po(po.name) + pi.insert() + pi.submit() + + # Debit Note - 50% Qty & enable updating PO billed amount + pi_return = make_debit_note(pi.name) + pi_return.items[0].qty = -5 + pi_return.update_billed_amount_in_purchase_order = 1 + pi_return.submit() + + # Check if the billed amount reduced + po.reload() + self.assertEqual(po.per_billed, 50) + + pi_return.reload() + pi_return.cancel() + + # Debit Note - 50% Qty & disable updating PO billed amount + pi_return = make_debit_note(pi.name) + pi_return.items[0].qty = -5 + pi_return.update_billed_amount_in_purchase_order = 0 + pi_return.submit() + + # Check if the billed amount stayed the same + po.reload() + self.assertEqual(po.per_billed, 100) + +>>>>>>> 81dbfe189e (test: po billed amount against debit note) def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From a84fc0fe408523d11266e2937be98fc053c76542 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sun, 25 Feb 2024 20:03:33 +0530 Subject: [PATCH 42/50] test: pr billed amount against debit note (cherry picked from commit 6d408448943c47f0a3632d5772a0786b14c75a59) --- .../purchase_receipt/test_purchase_receipt.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index d8141f93e68..7defbc5bcdf 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2189,6 +2189,41 @@ class TestPurchaseReceipt(FrappeTestCase): pr.cancel() self.assertTrue(frappe.db.exists("Batch", batch_no)) + def test_pr_billed_amount_against_return_entry(self): + from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_debit_note + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( + make_purchase_invoice as make_pi_from_pr, + ) + + # Create a Purchase Receipt and Fully Bill it + pr = make_purchase_receipt(qty=10) + pi = make_pi_from_pr(pr.name) + pi.insert() + pi.submit() + + # Debit Note - 50% Qty & enable updating PR billed amount + pi_return = make_debit_note(pi.name) + pi_return.items[0].qty = -5 + pi_return.update_billed_amount_in_purchase_receipt = 1 + pi_return.submit() + + # Check if the billed amount reduced + pr.reload() + self.assertEqual(pr.per_billed, 50) + + pi_return.reload() + pi_return.cancel() + + # Debit Note - 50% Qty & disable updating PR billed amount + pi_return = make_debit_note(pi.name) + pi_return.items[0].qty = -5 + pi_return.update_billed_amount_in_purchase_receipt = 0 + pi_return.submit() + + # Check if the billed amount stayed the same + pr.reload() + self.assertEqual(pr.per_billed, 100) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From 42b7be60ef3cb68040fa7985eaaf20c330486d25 Mon Sep 17 00:00:00 2001 From: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:57:43 +0530 Subject: [PATCH 43/50] 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 de81742ac00..813c70806f4 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1610,11 +1610,7 @@ "idx": 204, "is_submittable": 1, "links": [], -<<<<<<< HEAD - "modified": "2023-11-03 15:47:30.319200", -======= "modified": "2024-02-25 11:20:28.366808", ->>>>>>> 9f6535472d (feat: update billed amount in PO and PR) "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", From 8f983e61093a077e373b6f8218dfa15b97ba5692 Mon Sep 17 00:00:00 2001 From: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:01:33 +0530 Subject: [PATCH 44/50] chore: resolve conflicts --- .../purchase_order/test_purchase_order.py | 127 ------------------ 1 file changed, 127 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index f2c12ae6733..3a498ee271b 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -940,132 +940,6 @@ class TestPurchaseOrder(FrappeTestCase): self.assertRaises(frappe.ValidationError, po.save) -<<<<<<< HEAD -======= - def test_update_items_for_subcontracting_purchase_order(self): - from erpnext.controllers.tests.test_subcontracting_controller import ( - get_subcontracting_order, - make_bom_for_subcontracted_items, - make_raw_materials, - make_service_items, - make_subcontracted_items, - ) - - def update_items(po, qty): - trans_items = [po.items[0].as_dict()] - trans_items[0]["qty"] = qty - trans_items[0]["fg_item_qty"] = qty - trans_items = json.dumps(trans_items, default=str) - - return update_child_qty_rate( - po.doctype, - trans_items, - po.name, - ) - - make_subcontracted_items() - make_raw_materials() - make_service_items() - make_bom_for_subcontracted_items() - - service_items = [ - { - "warehouse": "_Test Warehouse - _TC", - "item_code": "Subcontracted Service Item 7", - "qty": 10, - "rate": 100, - "fg_item": "Subcontracted Item SA7", - "fg_item_qty": 10, - }, - ] - po = create_purchase_order( - rm_items=service_items, - is_subcontracted=1, - supplier_warehouse="_Test Warehouse 1 - _TC", - ) - - update_items(po, qty=20) - po.reload() - - # Test - 1: Items should be updated as there is no Subcontracting Order against PO - self.assertEqual(po.items[0].qty, 20) - self.assertEqual(po.items[0].fg_item_qty, 20) - - sco = get_subcontracting_order(po_name=po.name, warehouse="_Test Warehouse - _TC") - - # Test - 2: ValidationError should be raised as there is Subcontracting Order against PO - self.assertRaises(frappe.ValidationError, update_items, po=po, qty=30) - - sco.reload() - sco.cancel() - po.reload() - - update_items(po, qty=30) - po.reload() - - # Test - 3: Items should be updated as the Subcontracting Order is cancelled - self.assertEqual(po.items[0].qty, 30) - self.assertEqual(po.items[0].fg_item_qty, 30) - - @change_settings("Buying Settings", {"auto_create_subcontracting_order": 1}) - def test_auto_create_subcontracting_order(self): - from erpnext.controllers.tests.test_subcontracting_controller import ( - make_bom_for_subcontracted_items, - make_raw_materials, - make_service_items, - make_subcontracted_items, - ) - - make_subcontracted_items() - make_raw_materials() - make_service_items() - make_bom_for_subcontracted_items() - - service_items = [ - { - "warehouse": "_Test Warehouse - _TC", - "item_code": "Subcontracted Service Item 7", - "qty": 10, - "rate": 100, - "fg_item": "Subcontracted Item SA7", - "fg_item_qty": 10, - }, - ] - po = create_purchase_order( - rm_items=service_items, - is_subcontracted=1, - supplier_warehouse="_Test Warehouse 1 - _TC", - ) - - self.assertTrue(frappe.db.get_value("Subcontracting Order", {"purchase_order": po.name})) - - def test_purchase_order_advance_payment_status(self): - from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry - from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request - - po = create_purchase_order() - self.assertEqual( - frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Not Initiated" - ) - - pr = make_payment_request(dt=po.doctype, dn=po.name, submit_doc=True, return_doc=True) - self.assertEqual(frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Initiated") - - pe = get_payment_entry(po.doctype, po.name).save().submit() - self.assertEqual( - frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Fully Paid" - ) - - pe.reload() - pe.cancel() - self.assertEqual(frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Initiated") - - pr.reload() - pr.cancel() - self.assertEqual( - frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Not Initiated" - ) - def test_po_billed_amount_against_return_entry(self): from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_debit_note @@ -1098,7 +972,6 @@ class TestPurchaseOrder(FrappeTestCase): po.reload() self.assertEqual(po.per_billed, 100) ->>>>>>> 81dbfe189e (test: po billed amount against debit note) def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From 614119b3e18b5852e09a28b8479e68b637dc882f Mon Sep 17 00:00:00 2001 From: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:04:34 +0530 Subject: [PATCH 45/50] chore: resolve conflicts --- .../purchase_invoice/purchase_invoice.py | 174 ------------------ 1 file changed, 174 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index c04f8d40364..f6fcd7e70e0 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -54,180 +54,6 @@ form_grid_templates = {"items": "templates/form_grid/item_grid.html"} class PurchaseInvoice(BuyingController): -<<<<<<< HEAD -======= - # begin: auto-generated types - # This code is auto-generated. Do not modify anything in this block. - - from typing import TYPE_CHECKING - - if TYPE_CHECKING: - from frappe.types import DF - - from erpnext.accounts.doctype.advance_tax.advance_tax import AdvanceTax - 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.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_taxes_and_charges.purchase_taxes_and_charges import ( - PurchaseTaxesandCharges, - ) - 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, - ) - - additional_discount_percentage: DF.Float - address_display: DF.SmallText | None - advance_tax: DF.Table[AdvanceTax] - advances: DF.Table[PurchaseInvoiceAdvance] - against_expense_account: DF.SmallText | None - allocate_advances_automatically: DF.Check - amended_from: DF.Link | None - apply_discount_on: DF.Literal["", "Grand Total", "Net Total"] - apply_tds: DF.Check - auto_repeat: DF.Link | None - base_discount_amount: DF.Currency - base_grand_total: DF.Currency - base_in_words: DF.Data | None - base_net_total: DF.Currency - base_paid_amount: DF.Currency - base_rounded_total: DF.Currency - base_rounding_adjustment: DF.Currency - base_tax_withholding_net_total: DF.Currency - base_taxes_and_charges_added: DF.Currency - base_taxes_and_charges_deducted: DF.Currency - base_total: DF.Currency - base_total_taxes_and_charges: DF.Currency - base_write_off_amount: DF.Currency - bill_date: DF.Date | None - bill_no: DF.Data | None - billing_address: DF.Link | None - billing_address_display: DF.SmallText | None - buying_price_list: DF.Link | None - cash_bank_account: DF.Link | None - clearance_date: DF.Date | None - company: DF.Link | None - contact_display: DF.SmallText | None - contact_email: DF.SmallText | None - contact_mobile: DF.SmallText | None - contact_person: DF.Link | None - conversion_rate: DF.Float - cost_center: DF.Link | None - credit_to: DF.Link - currency: DF.Link | None - disable_rounded_total: DF.Check - discount_amount: DF.Currency - due_date: DF.Date | None - from_date: DF.Date | None - grand_total: DF.Currency - group_same_items: DF.Check - hold_comment: DF.SmallText | None - ignore_default_payment_terms_template: DF.Check - ignore_pricing_rule: DF.Check - in_words: DF.Data | None - incoterm: DF.Link | None - inter_company_invoice_reference: DF.Link | None - is_internal_supplier: DF.Check - is_old_subcontracting_flow: DF.Check - is_opening: DF.Literal["No", "Yes"] - is_paid: DF.Check - is_return: DF.Check - is_subcontracted: DF.Check - items: DF.Table[PurchaseInvoiceItem] - language: DF.Data | None - letter_head: DF.Link | None - mode_of_payment: DF.Link | None - named_place: DF.Data | None - naming_series: DF.Literal["ACC-PINV-.YYYY.-", "ACC-PINV-RET-.YYYY.-"] - net_total: DF.Currency - on_hold: DF.Check - only_include_allocated_payments: DF.Check - other_charges_calculation: DF.LongText | None - outstanding_amount: DF.Currency - paid_amount: DF.Currency - party_account_currency: DF.Link | None - payment_schedule: DF.Table[PaymentSchedule] - payment_terms_template: DF.Link | None - per_received: DF.Percent - plc_conversion_rate: DF.Float - posting_date: DF.Date - posting_time: DF.Time | None - price_list_currency: DF.Link | None - pricing_rules: DF.Table[PricingRuleDetail] - project: DF.Link | None - rejected_warehouse: DF.Link | None - release_date: DF.Date | None - remarks: DF.SmallText | None - repost_required: DF.Check - represents_company: DF.Link | None - return_against: DF.Link | None - rounded_total: DF.Currency - rounding_adjustment: DF.Currency - scan_barcode: DF.Data | None - select_print_heading: DF.Link | None - set_from_warehouse: DF.Link | None - set_posting_time: DF.Check - set_warehouse: DF.Link | None - shipping_address: DF.Link | None - shipping_address_display: DF.SmallText | None - shipping_rule: DF.Link | None - status: DF.Literal[ - "", - "Draft", - "Return", - "Debit Note Issued", - "Submitted", - "Paid", - "Partly Paid", - "Unpaid", - "Overdue", - "Cancelled", - "Internal Transfer", - ] - subscription: DF.Link | None - supplied_items: DF.Table[PurchaseReceiptItemSupplied] - supplier: DF.Link - supplier_address: DF.Link | None - supplier_group: DF.Link | None - supplier_name: DF.Data | None - supplier_warehouse: DF.Link | None - tax_category: DF.Link | None - tax_id: DF.ReadOnly | None - tax_withheld_vouchers: DF.Table[TaxWithheldVouchers] - tax_withholding_category: DF.Link | None - tax_withholding_net_total: DF.Currency - taxes: DF.Table[PurchaseTaxesandCharges] - taxes_and_charges: DF.Link | None - taxes_and_charges_added: DF.Currency - taxes_and_charges_deducted: DF.Currency - tc_name: DF.Link | None - terms: DF.TextEditor | None - title: DF.Data | None - to_date: DF.Date | None - total: DF.Currency - total_advance: DF.Currency - total_net_weight: DF.Float - total_qty: DF.Float - total_taxes_and_charges: DF.Currency - unrealized_profit_loss_account: DF.Link | None - update_billed_amount_in_purchase_order: DF.Check - update_billed_amount_in_purchase_receipt: DF.Check - update_stock: DF.Check - use_company_roundoff_cost_center: DF.Check - use_transaction_date_exchange_rate: DF.Check - write_off_account: DF.Link | None - write_off_amount: DF.Currency - write_off_cost_center: DF.Link | None - # end: auto-generated types - ->>>>>>> 9f6535472d (feat: update billed amount in PO and PR) def __init__(self, *args, **kwargs): super(PurchaseInvoice, self).__init__(*args, **kwargs) self.status_updater = [ From 22db6f6b72ae80126b31c84eaa48b7e80e6e68a1 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 27 Feb 2024 16:51:48 +0530 Subject: [PATCH 46/50] fix: test for plaid bank account validation --- .../doctype/plaid_settings/test_plaid_settings.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py index 302bdc436c6..7312d8a5ad4 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py @@ -42,7 +42,7 @@ class TestPlaidSettings(unittest.TestCase): add_account_subtype("loan") self.assertEqual(frappe.get_doc("Bank Account Subtype", "loan").name, "loan") - def test_default_bank_account(self): + def test_parent_bank_account_validation(self): if not frappe.db.exists("Bank", "Citi"): frappe.get_doc({"doctype": "Bank", "bank_name": "Citi"}).insert() @@ -70,12 +70,19 @@ class TestPlaidSettings(unittest.TestCase): bank = json.dumps(frappe.get_doc("Bank", "Citi").as_dict(), default=json_handler) company = frappe.db.get_single_value("Global Defaults", "default_company") - frappe.db.set_value("Company", company, "default_bank_account", None) + group_bank_account = frappe.db.get_all( + "Account", {"company": company, "account_type": "Bank", "is_group": 1}, pluck="name" + ) + if group_bank_account: + frappe.db.set_value("Account", group_bank_account[0], "disabled", 1) self.assertRaises( frappe.ValidationError, add_bank_accounts, response=bank_accounts, bank=bank, company=company ) + if group_bank_account: + frappe.db.set_value("Account", group_bank_account[0], "disabled", 0) + def test_new_transaction(self): if not frappe.db.exists("Bank", "Citi"): frappe.get_doc({"doctype": "Bank", "bank_name": "Citi"}).insert() From 9a85ab45e7233aa7a66884f04044ffb404d4e0f1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 17:18:24 +0530 Subject: [PATCH 47/50] fix: parent warehouse checks in the production plan for sub-assemblies (backport #40150) (#40156) fix: parent warehouse checks in the production plan for sub-assemblies (cherry picked from commit 6f5815e44f0532667509e64bc0266769d794c9be) Co-authored-by: rohitwaghchaure --- .../production_plan/production_plan.js | 6 ++ .../production_plan/production_plan.json | 4 +- .../production_plan/production_plan.py | 82 +++++++++++-------- .../production_plan/test_production_plan.py | 44 ++++++++++ .../production_plan_item.json | 8 +- .../production_plan_sub_assembly_item.json | 6 +- 6 files changed, 112 insertions(+), 38 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index c9c474db7f0..667ece2077e 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -518,6 +518,12 @@ frappe.ui.form.on("Production Plan Sales Order", { } }); +frappe.ui.form.on("Production Plan Sub Assembly Item", { + fg_warehouse(frm, cdt, cdn) { + erpnext.utils.copy_value_in_all_rows(frm.doc, cdt, cdn, "sub_assembly_items", "fg_warehouse"); + }, +}) + frappe.tour['Production Plan'] = [ { fieldname: "get_items_from", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 54c3893928b..84bbad58c38 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -421,9 +421,11 @@ "fieldtype": "Column Break" }, { + "description": "When a parent warehouse is chosen, the system conducts stock checks against the associated child warehouses", "fieldname": "sub_assembly_warehouse", "fieldtype": "Link", "label": "Sub Assembly Warehouse", + "mandatory_depends_on": "eval:doc.skip_available_sub_assembly_item === 1", "options": "Warehouse" }, { @@ -437,7 +439,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-02-11 15:42:47.642481", + "modified": "2024-02-27 13:34:20.692211", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 5495c49803d..7efc4f75e5b 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -817,8 +817,8 @@ class ProductionPlan(Document): sub_assembly_items_store = [] # temporary store to process all subassembly items for row in self.po_items: - if self.skip_available_sub_assembly_item and not row.warehouse: - frappe.throw(_("Row #{0}: Please select the FG Warehouse in Assembly Items").format(row.idx)) + if self.skip_available_sub_assembly_item and not self.sub_assembly_warehouse: + frappe.throw(_("Row #{0}: Please select the Sub Assembly Warehouse").format(row.idx)) if not row.item_code: frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx)) @@ -828,15 +828,24 @@ class ProductionPlan(Document): bom_data = [] - warehouse = ( - (self.sub_assembly_warehouse or row.warehouse) - if self.skip_available_sub_assembly_item - else None - ) + warehouse = (self.sub_assembly_warehouse) if self.skip_available_sub_assembly_item else None get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty, self.company, warehouse=warehouse) self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type) sub_assembly_items_store.extend(bom_data) + if not sub_assembly_items_store and self.skip_available_sub_assembly_item: + message = ( + _( + "As there are sufficient Sub Assembly Items, Work Order is not required for Warehouse {0}." + ).format(self.sub_assembly_warehouse) + + "

" + ) + message += _( + "If you still want to proceed, please disable 'Skip Available Sub Assembly Items' checkbox." + ) + + frappe.msgprint(message, title=_("Note")) + if self.combine_sub_items: # Combine subassembly items sub_assembly_items_store = self.combine_subassembly_items(sub_assembly_items_store) @@ -849,15 +858,19 @@ class ProductionPlan(Document): def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None): "Modify bom_data, set additional details." + is_group_warehouse = frappe.db.get_value("Warehouse", self.sub_assembly_warehouse, "is_group") + for data in bom_data: data.qty = data.stock_qty data.production_plan_item = row.name - data.fg_warehouse = self.sub_assembly_warehouse or row.warehouse data.schedule_date = row.planned_start_date data.type_of_manufacturing = manufacturing_type or ( "Subcontract" if data.is_sub_contracted_item else "In House" ) + if not is_group_warehouse: + data.fg_warehouse = self.sub_assembly_warehouse + def set_default_supplier_for_subcontracting_order(self): items = [ d.production_item for d in self.sub_assembly_items if d.type_of_manufacturing == "Subcontract" @@ -1401,7 +1414,7 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d so_item_details = frappe._dict() sub_assembly_items = {} - if doc.get("skip_available_sub_assembly_item"): + if doc.get("skip_available_sub_assembly_item") and doc.get("sub_assembly_items"): for d in doc.get("sub_assembly_items"): sub_assembly_items.setdefault((d.get("production_item"), d.get("bom_no")), d.get("qty")) @@ -1613,34 +1626,37 @@ def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, company, warehouse= stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty) if warehouse: - bin_dict = get_bin_details(d, company, for_warehouse=warehouse) + bin_details = get_bin_details(d, company, for_warehouse=warehouse) - if bin_dict and bin_dict[0].projected_qty > 0: - if bin_dict[0].projected_qty > stock_qty: - continue - else: - stock_qty = stock_qty - bin_dict[0].projected_qty + for _bin_dict in bin_details: + if _bin_dict.projected_qty > 0: + if _bin_dict.projected_qty > stock_qty: + stock_qty = 0 + continue + else: + stock_qty = stock_qty - _bin_dict.projected_qty - bom_data.append( - frappe._dict( - { - "parent_item_code": parent_item_code, - "description": d.description, - "production_item": d.item_code, - "item_name": d.item_name, - "stock_uom": d.stock_uom, - "uom": d.stock_uom, - "bom_no": d.value, - "is_sub_contracted_item": d.is_sub_contracted_item, - "bom_level": indent, - "indent": indent, - "stock_qty": stock_qty, - } + if stock_qty > 0: + bom_data.append( + frappe._dict( + { + "parent_item_code": parent_item_code, + "description": d.description, + "production_item": d.item_code, + "item_name": d.item_name, + "stock_uom": d.stock_uom, + "uom": d.stock_uom, + "bom_no": d.value, + "is_sub_contracted_item": d.is_sub_contracted_item, + "bom_level": indent, + "indent": indent, + "stock_qty": stock_qty, + } + ) ) - ) - if d.value: - get_sub_assembly_items(d.value, bom_data, stock_qty, company, warehouse, indent=indent + 1) + if d.value: + get_sub_assembly_items(d.value, bom_data, stock_qty, company, warehouse, indent=indent + 1) def set_default_warehouses(row, default_warehouses): diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 3d878c140f8..7e33eb80f7d 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1194,6 +1194,7 @@ class TestProductionPlan(FrappeTestCase): ignore_existing_ordered_qty=1, do_not_submit=1, skip_available_sub_assembly_item=1, + sub_assembly_warehouse="_Test Warehouse - _TC", warehouse="_Test Warehouse - _TC", ) @@ -1327,6 +1328,7 @@ class TestProductionPlan(FrappeTestCase): ignore_existing_ordered_qty=1, do_not_submit=1, skip_available_sub_assembly_item=1, + sub_assembly_warehouse="_Test Warehouse - _TC", warehouse="_Test Warehouse - _TC", ) @@ -1579,6 +1581,48 @@ class TestProductionPlan(FrappeTestCase): for row in work_orders: self.assertEqual(row.qty, wo_qty[row.name]) + def test_parent_warehouse_for_sub_assembly_items(self): + from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + parent_warehouse = "_Test Warehouse Group - _TC" + sub_warehouse = create_warehouse("Sub Warehouse", company="_Test Company") + + fg_item = make_item(properties={"is_stock_item": 1}).name + sf_item = make_item(properties={"is_stock_item": 1}).name + rm_item = make_item(properties={"is_stock_item": 1}).name + + bom_tree = {fg_item: {sf_item: {rm_item: {}}}} + create_nested_bom(bom_tree, prefix="") + + pln = create_production_plan( + item_code=fg_item, + planned_qty=10, + warehouse="_Test Warehouse - _TC", + sub_assembly_warehouse=parent_warehouse, + skip_available_sub_assembly_item=1, + do_not_submit=1, + skip_getting_mr_items=1, + ) + + pln.get_sub_assembly_items() + + for row in pln.sub_assembly_items: + self.assertFalse(row.fg_warehouse) + self.assertEqual(row.production_item, sf_item) + self.assertEqual(row.qty, 10.0) + + make_stock_entry(item_code=sf_item, qty=5, target=sub_warehouse, rate=100) + + pln.sub_assembly_items = [] + pln.get_sub_assembly_items() + + self.assertEqual(pln.sub_assembly_warehouse, parent_warehouse) + for row in pln.sub_assembly_items: + self.assertFalse(row.fg_warehouse) + self.assertEqual(row.production_item, sf_item) + self.assertEqual(row.qty, 5.0) + def create_production_plan(**args): """ diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json index 0688278e091..78a389760a7 100644 --- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json +++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json @@ -11,6 +11,7 @@ "bom_no", "column_break_6", "planned_qty", + "stock_uom", "warehouse", "planned_start_date", "section_break_9", @@ -18,7 +19,6 @@ "ordered_qty", "column_break_17", "description", - "stock_uom", "produced_qty", "reference_section", "sales_order", @@ -65,6 +65,7 @@ "width": "100px" }, { + "columns": 1, "fieldname": "planned_qty", "fieldtype": "Float", "in_list_view": 1, @@ -80,6 +81,7 @@ "fieldtype": "Column Break" }, { + "columns": 2, "fieldname": "warehouse", "fieldtype": "Link", "in_list_view": 1, @@ -141,8 +143,10 @@ "width": "200px" }, { + "columns": 1, "fieldname": "stock_uom", "fieldtype": "Link", + "in_list_view": 1, "label": "UOM", "oldfieldname": "stock_uom", "oldfieldtype": "Data", @@ -216,7 +220,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-11-25 14:15:40.061514", + "modified": "2024-02-27 13:24:43.571844", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Item", diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json index aff740b732e..7965965d2b6 100644 --- a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json +++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json @@ -101,7 +101,6 @@ "columns": 1, "fieldname": "bom_level", "fieldtype": "Int", - "in_list_view": 1, "label": "Level (BOM)", "read_only": 1 }, @@ -149,8 +148,10 @@ "label": "Indent" }, { + "columns": 2, "fieldname": "fg_warehouse", "fieldtype": "Link", + "in_list_view": 1, "label": "Target Warehouse", "options": "Warehouse" }, @@ -170,6 +171,7 @@ "options": "Supplier" }, { + "columns": 1, "fieldname": "schedule_date", "fieldtype": "Datetime", "in_list_view": 1, @@ -207,7 +209,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-11-03 13:33:42.959387", + "modified": "2024-02-27 13:45:17.422435", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Sub Assembly Item", From 88af431eeaf2fbe90f762e3d1fea4ef61ff8668e Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 27 Feb 2024 17:20:01 +0530 Subject: [PATCH 48/50] chore: semantic commits bump node version to 20 --- .github/workflows/semantic-commits.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/semantic-commits.yml b/.github/workflows/semantic-commits.yml index 1744bc33a9e..da3d564e66c 100644 --- a/.github/workflows/semantic-commits.yml +++ b/.github/workflows/semantic-commits.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: 14 + node-version: 20 check-latest: true - name: Check commit titles From 8c772cfb9f2c01f2e1dbf514fb819b3d0f3836f7 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 27 Feb 2024 20:33:02 +0530 Subject: [PATCH 49/50] fix: add flags for repost to ensure correct accounting from India Compliance App --- .../repost_accounting_ledger/repost_accounting_ledger.py | 2 ++ .../doctype/repost_item_valuation/repost_item_valuation.py | 1 + 2 files changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index 9211b286c7d..28355964cfa 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -70,6 +70,7 @@ class RepostAccountingLedger(Document): ).append(gle.update({"old": True})) def generate_preview_data(self): + frappe.flags.through_repost_accounting_ledger = True self.gl_entries = [] self.get_existing_ledger_entries() for x in self.vouchers: @@ -123,6 +124,7 @@ class RepostAccountingLedger(Document): @frappe.whitelist() def start_repost(account_repost_doc=str) -> None: + frappe.flags.through_repost_accounting_ledger = True if account_repost_doc: repost_doc = frappe.get_doc("Repost Accounting Ledger", account_repost_doc) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 6ffb34dc135..5d087a46242 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -226,6 +226,7 @@ def on_doctype_update(): def repost(doc): try: + frappe.flags.through_repost_item_valuation = True if not frappe.db.exists("Repost Item Valuation", doc.name): return From 30315aba37bb86eb2ecf2805c77b9ef2316d26ba Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 26 Feb 2024 18:41:33 +0530 Subject: [PATCH 50/50] fix: default taxable value for item not found in item list (cherry picked from commit 5885978fc2f829b064568e8e2b6a28143f69218c) --- erpnext/controllers/taxes_and_totals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index e24618af103..2881c15a14d 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -1005,7 +1005,7 @@ def get_itemised_tax_breakup_data(doc): for item_code, taxes in itemised_tax.items(): itemised_tax_data.append( frappe._dict( - {"item": item_code, "taxable_amount": itemised_taxable_amount.get(item_code), **taxes} + {"item": item_code, "taxable_amount": itemised_taxable_amount.get(item_code, 0), **taxes} ) )