From 2077b2cde413a66347b3b72e201a2702d7f6c0b6 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 28 Aug 2023 13:09:51 +0530 Subject: [PATCH 01/51] fix: show letterhead and terms for AR pdf (cherry picked from commit 0a9187ea422f2312ffd9508f1f3c6a120c228f71) --- ...ement_of_accounts_accounts_receivable.html | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html index 259526f8c43..647600a9fea 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html @@ -8,9 +8,24 @@ } +
+ {% if letter_head.content %} +
{{ letter_head.content }}
+
+ {% endif %} +
+ +

{{ _(report.report_name) }}

- {{ filters.customer }} + {{ filters.customer_name }}

{% if (filters.tax_id) %} @@ -341,4 +356,9 @@ {% endif %} + {% if terms_and_conditions %} +
+ {{ terms_and_conditions }} +
+ {% endif %}

{{ _("Printed On ") }}{{ frappe.utils.now() }}

From 657ca7ff22602ab34acc25c3c2a2d7bcd2ba25e9 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 28 Aug 2023 13:11:28 +0530 Subject: [PATCH 02/51] feat: add field for specifying pdf name (cherry picked from commit 5c2a949593727ec6c9f84f8b801035e4da01558d) # Conflicts: # erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py --- .../process_statement_of_accounts.json | 10 ++++++++-- .../process_statement_of_accounts.py | 19 +++++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json index 45373741f39..e711ae0de2b 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json @@ -49,6 +49,7 @@ "column_break_21", "start_date", "section_break_33", + "pdf_name", "subject", "column_break_28", "cc_to", @@ -273,7 +274,7 @@ "fieldname": "help_text", "fieldtype": "HTML", "label": "Help Text", - "options": "
\n

Note

\n
    \n
  • \nYou can use Jinja tags in Subject and Body fields for dynamic values.\n
  • \n All fields in this doctype are available under the doc object and all fields for the customer to whom the mail will go to is available under the customer object.\n
\n

Examples

\n\n
    \n
  • Subject:

    Statement Of Accounts for {{ customer.name }}

  • \n
  • Body:

    \n
    Hello {{ customer.name }},
    PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}.
  • \n
\n" + "options": "
\n

Note

\n
    \n
  • \nYou can use Jinja tags in Subject and Body fields for dynamic values.\n
  • \n All fields in this doctype are available under the doc object and all fields for the customer to whom the mail will go to is available under the customer object.\n
\n

Examples

\n\n
    \n
  • Subject:

    Statement Of Accounts for {{ customer.customer_name }}

  • \n
  • Body:

    \n
    Hello {{ customer.customer_name }},
    PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}.
  • \n
\n" }, { "fieldname": "subject", @@ -368,10 +369,15 @@ "fieldname": "based_on_payment_terms", "fieldtype": "Check", "label": "Based On Payment Terms" + }, + { + "fieldname": "pdf_name", + "fieldtype": "Data", + "label": "PDF Name" } ], "links": [], - "modified": "2023-06-23 10:13:15.051950", + "modified": "2023-08-28 12:59:53.071334", "modified_by": "Administrator", "module": "Accounts", "name": "Process Statement Of Accounts", diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 6cd601f663d..b9ea2142958 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -26,7 +26,13 @@ class ProcessStatementOfAccounts(Document): if not self.subject: self.subject = "Statement Of Accounts for {{ customer.customer_name }}" if not self.body: - self.body = "Hello {{ customer.name }},
PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}." + if self.report == "General Ledger": + body_str = " from {{ doc.from_date }} to {{ doc.to_date }}." + else: + body_str = " until {{ doc.posting_date }}." + self.body = "Hello {{ customer.customer_name }},
PFA your Statement Of Accounts" + body_str + if not self.pdf_name: + self.pdf_name = "{{ customer.customer_name }}" validate_template(self.subject) validate_template(self.body) @@ -139,6 +145,7 @@ def get_ar_filters(doc, entry): return { "report_date": doc.posting_date if doc.posting_date else None, "customer": entry.customer, + "customer_name": entry.customer_name if entry.customer_name else None, "payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None, "sales_partner": doc.sales_partner if doc.sales_partner else None, "sales_person": doc.sales_person if doc.sales_person else None, @@ -368,10 +375,18 @@ def send_emails(document_name, from_scheduler=False): if report: for customer, report_pdf in report.items(): - attachments = [{"fname": customer + ".pdf", "fcontent": report_pdf}] + context = get_context(customer, doc) + filename = frappe.render_template(doc.pdf_name, context) + attachments = [{"fname": filename + ".pdf", "fcontent": report_pdf}] recipients, cc = get_recipients_and_cc(customer, doc) +<<<<<<< HEAD context = get_context(customer, doc) +======= + if not recipients: + continue + +>>>>>>> 5c2a949593 (feat: add field for specifying pdf name) subject = frappe.render_template(doc.subject, context) message = frappe.render_template(doc.body, context) From 53270dd933d1e1ab22787de4ecbab6894810c5e8 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 28 Aug 2023 17:14:15 +0530 Subject: [PATCH 03/51] fix: generate pdf only when result exists (cherry picked from commit f07f4ce86f5963d03ea41ab699df2c4ce53ef2c5) --- .../process_statement_of_accounts.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index b9ea2142958..6c8afbecb5f 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -63,11 +63,6 @@ def get_report_pdf(doc, consolidated=True): filters = get_common_filters(doc) - if doc.report == "General Ledger": - filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency)) - else: - filters.update(get_ar_filters(doc, entry)) - if doc.report == "General Ledger": col, res = get_soa(filters) for x in [0, -2, -1]: @@ -75,8 +70,11 @@ def get_report_pdf(doc, consolidated=True): if len(res) == 3: continue else: + filters.update(get_ar_filters(doc, entry)) ar_res = get_ar_soa(filters) col, res = ar_res[0], ar_res[1] + if not res: + continue statement_dict[entry.customer] = get_html(doc, filters, entry, col, res, ageing) From f9f1ac3601a8ddb3c6c56bdc97d3b718f7c04d51 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 29 Aug 2023 14:51:35 +0530 Subject: [PATCH 04/51] test: auto email for ar report (cherry picked from commit a006b66e45c1b0c192674ad522b05f5565513e35) --- .../process_statement_of_accounts.py | 9 +++-- .../test_process_statement_of_accounts.py | 37 ++++++++++++++++++- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 6c8afbecb5f..ef6ba3e3ab1 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -367,7 +367,7 @@ def download_statements(document_name): @frappe.whitelist() -def send_emails(document_name, from_scheduler=False): +def send_emails(document_name, from_scheduler=False, posting_date=None): doc = frappe.get_doc("Process Statement Of Accounts", document_name) report = get_report_pdf(doc, consolidated=False) @@ -403,7 +403,7 @@ def send_emails(document_name, from_scheduler=False): ) if doc.enable_auto_email and from_scheduler: - new_to_date = getdate(today()) + new_to_date = getdate(posting_date or today()) if doc.frequency == "Weekly": new_to_date = add_days(new_to_date, 7) else: @@ -414,6 +414,8 @@ def send_emails(document_name, from_scheduler=False): ) doc.db_set("to_date", new_to_date, commit=True) doc.db_set("from_date", new_from_date, commit=True) + doc.db_set("posting_date", new_to_date, commit=True) + doc.db_set("report", doc.report, commit=True) return True else: return False @@ -423,7 +425,8 @@ def send_emails(document_name, from_scheduler=False): def send_auto_email(): selected = frappe.get_list( "Process Statement Of Accounts", - filters={"to_date": format_date(today()), "enable_auto_email": 1}, + filters={"enable_auto_email": 1}, + or_filters={"to_date": format_date(today()), "posting_date": format_date(today())}, ) for entry in selected: send_emails(entry.name, from_scheduler=True) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py index c281040aaf2..fb0d8d152f0 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py @@ -1,9 +1,42 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -# import frappe import unittest +import frappe +from frappe.utils import add_days, getdate, today + +from erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts import ( + send_emails, +) +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice + class TestProcessStatementOfAccounts(unittest.TestCase): - pass + def setUp(self): + self.si = create_sales_invoice() + self.process_soa = create_process_soa() + + def test_auto_email_for_process_soa_ar(self): + send_emails(self.process_soa.name, from_scheduler=True) + self.process_soa.load_from_db() + self.assertEqual(self.process_soa.posting_date, getdate(add_days(today(), 7))) + + def tearDown(self): + frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA") + + +def create_process_soa(): + frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA") + process_soa = frappe.new_doc("Process Statement Of Accounts") + soa_dict = { + "name": "Test Process SOA", + "company": "_Test Company", + } + process_soa.update(soa_dict) + process_soa.set("customers", [{"customer": "_Test Customer"}]) + process_soa.enable_auto_email = 1 + process_soa.frequency = "Weekly" + process_soa.report = "Accounts Receivable" + process_soa.save() + return process_soa From 284181d766e4b321e0fc642b3230c1ce86338e37 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 29 Aug 2023 16:16:50 +0530 Subject: [PATCH 05/51] fix: remove report field db set (cherry picked from commit 060da2c5bc47300d8e7103af63626d0b53ce2807) --- .../process_statement_of_accounts.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index ef6ba3e3ab1..21708ab4ec6 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -412,10 +412,11 @@ def send_emails(document_name, from_scheduler=False, posting_date=None): doc.add_comment( "Comment", "Emails sent on: " + frappe.utils.format_datetime(frappe.utils.now()) ) - doc.db_set("to_date", new_to_date, commit=True) - doc.db_set("from_date", new_from_date, commit=True) - doc.db_set("posting_date", new_to_date, commit=True) - doc.db_set("report", doc.report, commit=True) + if doc.report == "General Ledger": + doc.db_set("to_date", new_to_date, commit=True) + doc.db_set("from_date", new_from_date, commit=True) + else: + doc.db_set("posting_date", new_to_date, commit=True) return True else: return False From 619644af04a44b38c0c4f5160b3c897ea2535181 Mon Sep 17 00:00:00 2001 From: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com> Date: Mon, 11 Sep 2023 11:25:30 +0530 Subject: [PATCH 06/51] chore: resolve conflicts --- .../process_statement_of_accounts.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 21708ab4ec6..d2249c2147f 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -378,13 +378,9 @@ def send_emails(document_name, from_scheduler=False, posting_date=None): attachments = [{"fname": filename + ".pdf", "fcontent": report_pdf}] recipients, cc = get_recipients_and_cc(customer, doc) -<<<<<<< HEAD - context = get_context(customer, doc) -======= if not recipients: continue - ->>>>>>> 5c2a949593 (feat: add field for specifying pdf name) + subject = frappe.render_template(doc.subject, context) message = frappe.render_template(doc.body, context) From a35abf840397f02ababf4ab869464bf82709a98a Mon Sep 17 00:00:00 2001 From: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com> Date: Mon, 11 Sep 2023 12:06:24 +0530 Subject: [PATCH 07/51] chore: linting issues --- .../process_statement_of_accounts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index d2249c2147f..e1f32952205 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -380,7 +380,7 @@ def send_emails(document_name, from_scheduler=False, posting_date=None): recipients, cc = get_recipients_and_cc(customer, doc) if not recipients: continue - + subject = frappe.render_template(doc.subject, context) message = frappe.render_template(doc.body, context) From 21be889a771f815a720d44494ba2e6e8174adb83 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 18:56:20 +0530 Subject: [PATCH 08/51] fix(ux): docstatus filter for `Reference Name` in QI (backport #37024) (#37028) fix(ux): docstatus filter for `Reference Name` in QI (#37024) (cherry picked from commit d739ab6ee3165b0c661086e71ad78aa1c41f58a6) Co-authored-by: s-aga-r --- .../doctype/quality_inspection/quality_inspection.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js index eea28791a9f..05fa2324dd4 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js @@ -6,6 +6,14 @@ cur_frm.cscript.refresh = cur_frm.cscript.inspection_type; frappe.ui.form.on("Quality Inspection", { setup: function(frm) { + frm.set_query("reference_name", function() { + return { + filters: { + "docstatus": ["!=", 2], + } + } + }); + frm.set_query("batch_no", function() { return { filters: { From 66027877d3cdd7ac5de421d9c5b631fe07630f6a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 12 Sep 2023 09:04:37 +0530 Subject: [PATCH 09/51] fix: `Parent Task` link with `Project Task` (backport #37025) (#37033) * feat: new field in `Task` to hold ref of Template Task (cherry picked from commit b4bcd9ba3f1bfcbd72867d24448ed14bda579dd4) # Conflicts: # erpnext/projects/doctype/task/task.json * fix: set `Template Task` ref in `Project Task` (cherry picked from commit d3295c43e3ba86b66d2085572f921f8af94e27d5) * fix: reload task before save (cherry picked from commit 5cae2e79bd1fcc6222ba08988af2c9d1353ede6c) * test: add test case for Task having common subject (cherry picked from commit 0d5c8f03bdcfb8f34bf389eb3c67f57302be6b75) * chore: `conflicts` --------- Co-authored-by: s-aga-r --- erpnext/projects/doctype/project/project.py | 12 +++-- .../projects/doctype/project/test_project.py | 49 +++++++++++++++++-- erpnext/projects/doctype/task/task.json | 13 ++++- 3 files changed, 66 insertions(+), 8 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index d80133c988a..082ba915207 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -84,6 +84,7 @@ class Project(Document): issue=task_details.issue, is_group=task_details.is_group, color=task_details.color, + template_task=task_details.name, ) ).insert() @@ -103,9 +104,13 @@ class Project(Document): return date def dependency_mapping(self, template_tasks, project_tasks): - for template_task in template_tasks: - project_task = list(filter(lambda x: x.subject == template_task.subject, project_tasks))[0] - project_task = frappe.get_doc("Task", project_task.name) + for project_task in project_tasks: + if project_task.get("template_task"): + template_task = frappe.get_doc("Task", project_task.template_task) + else: + template_task = list(filter(lambda x: x.subject == project_task.subject, template_tasks))[0] + template_task = frappe.get_doc("Task", template_task.name) + self.check_depends_on_value(template_task, project_task, project_tasks) self.check_for_parent_tasks(template_task, project_task, project_tasks) @@ -117,6 +122,7 @@ class Project(Document): filter(lambda x: x.subject == child_task_subject, project_tasks) ) if len(corresponding_project_task): + project_task.reload() # reload, as it might have been updated in the previous iteration project_task.append("depends_on", {"task": corresponding_project_task[0].name}) project_task.save() diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index 8a599cef753..e49fecd1f47 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -1,9 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -import unittest - import frappe +from frappe.tests.utils import FrappeTestCase from frappe.utils import add_days, getdate, nowdate from erpnext.projects.doctype.project_template.test_project_template import make_project_template @@ -15,7 +14,7 @@ test_records = frappe.get_test_records("Project") test_ignore = ["Sales Order"] -class TestProject(unittest.TestCase): +class TestProject(FrappeTestCase): def test_project_with_template_having_no_parent_and_depend_tasks(self): project_name = "Test Project with Template - No Parent and Dependend Tasks" frappe.db.sql(""" delete from tabTask where project = %s """, project_name) @@ -155,6 +154,50 @@ class TestProject(unittest.TestCase): so.reload() self.assertFalse(so.project) + def test_project_with_template_tasks_having_common_name(self): + # Step - 1: Create Template Parent Tasks + template_parent_task1 = create_task(subject="Parent Task - 1", is_template=1, is_group=1) + template_parent_task2 = create_task(subject="Parent Task - 2", is_template=1, is_group=1) + template_parent_task3 = create_task(subject="Parent Task - 1", is_template=1, is_group=1) + + # Step - 2: Create Template Child Tasks + template_task1 = create_task( + subject="Task - 1", is_template=1, parent_task=template_parent_task1.name + ) + template_task2 = create_task( + subject="Task - 2", is_template=1, parent_task=template_parent_task2.name + ) + template_task3 = create_task( + subject="Task - 1", is_template=1, parent_task=template_parent_task3.name + ) + + # Step - 3: Create Project Template + template_tasks = [ + template_parent_task1, + template_task1, + template_parent_task2, + template_task2, + template_parent_task3, + template_task3, + ] + project_template = make_project_template( + "Project template with common Task Subject", template_tasks + ) + + # Step - 4: Create Project against the Project Template + project = get_project("Project with common Task Subject", project_template) + project_tasks = frappe.get_all( + "Task", {"project": project.name}, ["subject", "parent_task", "is_group"] + ) + + # Test - 1: No. of Project Tasks should be equal to No. of Template Tasks + self.assertEquals(len(project_tasks), len(template_tasks)) + + # Test - 2: All child Project Tasks should have Parent Task linked + for pt in project_tasks: + if not pt.is_group: + self.assertIsNotNone(pt.parent_task) + def get_project(name, template): diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index 141a99e612b..33a8799f96c 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -52,13 +52,15 @@ "company", "lft", "rgt", - "old_parent" + "old_parent", + "template_task" ], "fields": [ { "fieldname": "subject", "fieldtype": "Data", "in_global_search": 1, + "in_list_view": 1, "in_standard_filter": 1, "label": "Subject", "reqd": 1, @@ -138,6 +140,7 @@ "fieldname": "parent_task", "fieldtype": "Link", "ignore_user_permissions": 1, + "in_list_view": 1, "label": "Parent Task", "options": "Task", "search_index": 1 @@ -382,6 +385,12 @@ "fieldtype": "Date", "label": "Completed On", "mandatory_depends_on": "eval: doc.status == \"Completed\"" + }, + { + "fieldname": "template_task", + "fieldtype": "Data", + "hidden": 1, + "label": "Template Task" } ], "icon": "fa fa-check", @@ -389,7 +398,7 @@ "is_tree": 1, "links": [], "max_attachments": 5, - "modified": "2022-06-23 16:58:47.005241", + "modified": "2023-09-06 13:52:05.861175", "modified_by": "Administrator", "module": "Projects", "name": "Task", From d278b116030df0f272c3c101b75fe3b66a344c04 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 12 Sep 2023 13:32:56 +0530 Subject: [PATCH 10/51] feat: provision to set required by from Production Plan (#37039) * feat: provision to set the Required By date from production plan * test: added test case for validate schedule_date --- .../material_request_plan_item.json | 41 ++++++++++++++----- .../production_plan/production_plan.py | 2 +- .../production_plan/test_production_plan.py | 12 +++++- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json index 09bf1d8a736..d07bf0fa66b 100644 --- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json +++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json @@ -10,22 +10,25 @@ "warehouse", "item_name", "material_request_type", - "actual_qty", - "ordered_qty", + "quantity", "required_bom_qty", "column_break_4", - "quantity", + "schedule_date", "uom", "conversion_factor", - "projected_qty", - "reserved_qty_for_production", - "safety_stock", "item_details", "description", "min_order_qty", "section_break_8", "sales_order", - "requested_qty" + "bin_qty_section", + "actual_qty", + "requested_qty", + "reserved_qty_for_production", + "column_break_yhelv", + "ordered_qty", + "projected_qty", + "safety_stock" ], "fields": [ { @@ -65,7 +68,7 @@ "fieldtype": "Column Break" }, { - "columns": 1, + "columns": 2, "fieldname": "quantity", "fieldtype": "Float", "in_list_view": 1, @@ -80,12 +83,12 @@ "read_only": 1 }, { - "columns": 2, + "columns": 1, "default": "0", "fieldname": "actual_qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Available Qty", + "label": "Qty In Stock", "no_copy": 1, "read_only": 1 }, @@ -176,11 +179,27 @@ "fieldtype": "Float", "label": "Conversion Factor", "read_only": 1 + }, + { + "columns": 1, + "fieldname": "schedule_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Required By" + }, + { + "fieldname": "bin_qty_section", + "fieldtype": "Section Break", + "label": "BIN Qty" + }, + { + "fieldname": "column_break_yhelv", + "fieldtype": "Column Break" } ], "istable": 1, "links": [], - "modified": "2023-05-03 12:43:29.895754", + "modified": "2023-09-12 12:09:08.358326", "modified_by": "Administrator", "module": "Manufacturing", "name": "Material Request Plan Item", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index a494550423f..795cb97fffa 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -725,7 +725,7 @@ class ProductionPlan(Document): # key for Sales Order:Material Request Type:Customer key = "{}:{}:{}".format(item.sales_order, material_request_type, item_doc.customer or "") - schedule_date = add_days(nowdate(), cint(item_doc.lead_time_days)) + schedule_date = item.schedule_date or add_days(nowdate(), cint(item_doc.lead_time_days)) if not key in material_request_map: # make a new MR for the combination diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 2871a29d768..f5778847b9c 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -2,7 +2,7 @@ # See license.txt import frappe from frappe.tests.utils import FrappeTestCase -from frappe.utils import add_to_date, flt, now_datetime, nowdate +from frappe.utils import add_to_date, flt, getdate, now_datetime, nowdate from erpnext.controllers.item_variant import create_variant from erpnext.manufacturing.doctype.production_plan.production_plan import ( @@ -58,6 +58,9 @@ class TestProductionPlan(FrappeTestCase): pln = create_production_plan(item_code="Test Production Item 1") self.assertTrue(len(pln.mr_items), 2) + for row in pln.mr_items: + row.schedule_date = add_to_date(nowdate(), days=10) + pln.make_material_request() pln.reload() self.assertTrue(pln.status, "Material Requested") @@ -71,6 +74,13 @@ class TestProductionPlan(FrappeTestCase): self.assertTrue(len(material_requests), 2) + for row in material_requests: + mr_schedule_date = getdate(frappe.db.get_value("Material Request", row[0], "schedule_date")) + + expected_date = getdate(add_to_date(nowdate(), days=10)) + + self.assertEqual(mr_schedule_date, expected_date) + pln.make_work_order() work_orders = frappe.get_all( "Work Order", fields=["name"], filters={"production_plan": pln.name}, as_list=1 From 413b40f5a770e062732f850714d3ea75de2b4337 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 13 Sep 2023 06:03:12 +0530 Subject: [PATCH 11/51] fix: packed item using expired price (cherry picked from commit 47ffa4983c8752d706af80fefbedd3f80dc53f3b) --- erpnext/stock/doctype/packed_item/packed_item.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index dbd8de4fcb0..a9e9ad1a639 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -207,6 +207,9 @@ def update_packed_item_price_data(pi_row, item_data, doc): "conversion_rate": doc.get("conversion_rate"), } ) + if not row_data.get("transaction_date"): + row_data.update({"transaction_date": doc.get("transaction_date")}) + rate = get_price_list_rate(row_data, item_doc).get("price_list_rate") pi_row.rate = rate or item_data.get("valuation_rate") or 0.0 From aa0a756111bb20887c9d6ec11a9bf56d2261a55e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 13 Sep 2023 06:24:57 +0530 Subject: [PATCH 12/51] test: expired item price should not be picked (cherry picked from commit 055156d28a637a5dfa3b73ba5740826642884aa0) --- .../doctype/sales_order/test_sales_order.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 608e23a8268..799ad555a52 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1998,6 +1998,61 @@ class TestSalesOrder(FrappeTestCase): self.assertEqual(len(dn.packed_items), 1) self.assertEqual(dn.items[0].item_code, "_Test Product Bundle Item Partial 2") + @change_settings("Selling Settings", {"editable_bundle_item_rates": 1}) + def test_expired_rate_for_packed_item(self): + bundle = "_Test Product Bundle 1" + packed_item = "_Packed Item 1" + + # test Update Items with product bundle + for product_bundle in [bundle]: + if not frappe.db.exists("Item", product_bundle): + bundle_item = make_item(product_bundle, {"is_stock_item": 0}) + bundle_item.append( + "item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"} + ) + bundle_item.save(ignore_permissions=True) + + for product_bundle in [packed_item]: + if not frappe.db.exists("Item", product_bundle): + make_item(product_bundle, {"is_stock_item": 0, "stock_uom": "Nos"}) + + make_product_bundle(bundle, [packed_item], 1) + + for scenario in [ + {"valid_upto": add_days(nowdate(), -1), "expected_rate": 0.0}, + {"valid_upto": add_days(nowdate(), 1), "expected_rate": 111.0}, + ]: + with self.subTest(scenario=scenario): + frappe.get_doc( + { + "doctype": "Item Price", + "item_code": packed_item, + "selling": 1, + "price_list": "_Test Price List", + "valid_from": add_days(nowdate(), -1), + "valid_upto": scenario.get("valid_upto"), + "price_list_rate": 111, + } + ).save() + + so = frappe.new_doc("Sales Order") + so.transaction_date = nowdate() + so.delivery_date = nowdate() + so.set_warehouse = "" + so.company = "_Test Company" + so.customer = "_Test Customer" + so.currency = "INR" + so.selling_price_list = "_Test Price List" + so.append("items", {"item_code": bundle, "qty": 1}) + so.save() + + self.assertEqual(len(so.items), 1) + self.assertEqual(len(so.packed_items), 1) + self.assertEqual(so.items[0].item_code, bundle) + self.assertEqual(so.packed_items[0].item_code, packed_item) + self.assertEqual(so.items[0].rate, scenario.get("expected_rate")) + self.assertEqual(so.packed_items[0].rate, scenario.get("expected_rate")) + def automatically_fetch_payment_terms(enable=1): accounts_settings = frappe.get_doc("Accounts Settings") From 9bc44a3b40c661ceb4821d90f70fc160d406bf8e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 12 Sep 2023 21:14:08 +0530 Subject: [PATCH 13/51] fix: Apply dimension filter, irrespective of dimesion columns (cherry picked from commit 769db0b3bc76319a746e24ef4e3ced1de597e0b1) --- .../report/general_ledger/general_ledger.py | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 23403a4b15b..d670a356975 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -272,20 +272,19 @@ def get_conditions(filters): if match_conditions: conditions.append(match_conditions) - if filters.get("include_dimensions"): - accounting_dimensions = get_accounting_dimensions(as_list=False) + accounting_dimensions = get_accounting_dimensions(as_list=False) - if accounting_dimensions: - for dimension in accounting_dimensions: - if not dimension.disabled: - if filters.get(dimension.fieldname): - if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"): - filters[dimension.fieldname] = get_dimension_with_children( - dimension.document_type, filters.get(dimension.fieldname) - ) - conditions.append("{0} in %({0})s".format(dimension.fieldname)) - else: - conditions.append("{0} in %({0})s".format(dimension.fieldname)) + if accounting_dimensions: + for dimension in accounting_dimensions: + if not dimension.disabled: + if filters.get(dimension.fieldname): + if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"): + filters[dimension.fieldname] = get_dimension_with_children( + dimension.document_type, filters.get(dimension.fieldname) + ) + conditions.append("{0} in %({0})s".format(dimension.fieldname)) + else: + conditions.append("{0} in %({0})s".format(dimension.fieldname)) return "and {}".format(" and ".join(conditions)) if conditions else "" From c2a0c1e989aab8ec136a6e8c15687abcab3cb55b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 13 Sep 2023 16:06:08 +0530 Subject: [PATCH 14/51] fix: + btn not appearing for delivery note connection (backport #36980) (#37070) fix: move SI and DI connected links to internal_and_external_links (cherry picked from commit e1a94a9ba1c1241012cc0458791686e26ad5483d) Co-authored-by: anandbaburajan --- .../accounts/doctype/sales_invoice/sales_invoice_dashboard.py | 4 +++- .../stock/doctype/delivery_note/delivery_note_dashboard.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py index 6fdcf263a55..fd95c1fe0e5 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py @@ -15,9 +15,11 @@ def get_data(): }, "internal_links": { "Sales Order": ["items", "sales_order"], - "Delivery Note": ["items", "delivery_note"], "Timesheet": ["timesheets", "time_sheet"], }, + "internal_and_external_links": { + "Delivery Note": ["items", "delivery_note"], + }, "transactions": [ { "label": _("Payment"), diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py index e66c23324da..d4a574da73f 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py @@ -11,10 +11,12 @@ def get_data(): }, "internal_links": { "Sales Order": ["items", "against_sales_order"], - "Sales Invoice": ["items", "against_sales_invoice"], "Material Request": ["items", "material_request"], "Purchase Order": ["items", "purchase_order"], }, + "internal_and_external_links": { + "Sales Invoice": ["items", "against_sales_invoice"], + }, "transactions": [ {"label": _("Related"), "items": ["Sales Invoice", "Packing Slip", "Delivery Trip"]}, {"label": _("Reference"), "items": ["Sales Order", "Shipment", "Quality Inspection"]}, From b56c9b91f11dd9e84d02b82ea2b0b2f727c5d4e1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 13 Sep 2023 11:26:13 +0000 Subject: [PATCH 15/51] fix: accepted warehouse and rejected warehouse can't be same (backport #36973) (#37071) * fix: ignore user permissions for `From Warehouse` in PR (cherry picked from commit d2f32861158236964ba18daa01144ea455607d80) # Conflicts: # erpnext/buying/doctype/purchase_order/purchase_order.json # erpnext/buying/doctype/purchase_order_item/purchase_order_item.json * chore: `conflicts` * chore: `conflicts` --------- Co-authored-by: s-aga-r --- erpnext/buying/doctype/purchase_order/purchase_order.json | 3 ++- .../doctype/purchase_order_item/purchase_order_item.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 645abf25a8f..bacd98bea77 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1171,6 +1171,7 @@ "depends_on": "is_internal_supplier", "fieldname": "set_from_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Set From Warehouse", "options": "Warehouse" }, @@ -1271,7 +1272,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2023-05-24 11:16:41.195340", + "modified": "2023-09-13 16:21:07.361700", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index c645b04e129..fe1b9702539 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -878,6 +878,7 @@ "depends_on": "eval:parent.is_internal_supplier", "fieldname": "from_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "From Warehouse", "options": "Warehouse" }, @@ -902,7 +903,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-11-29 16:47:41.364387", + "modified": "2023-09-13 16:22:40.825092", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", From 8772e40bae96db49b1d8653a5e8e5cb975f492ad Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 13 Sep 2023 18:16:33 +0530 Subject: [PATCH 16/51] fix: Purchase Receipt Provisional Accounting GL Entries (backport #37046) (#37068) * fix: Purchase Receipt Provisional Accounting GL Entries (cherry picked from commit 6bab0eeaa1ac53c85a4a7b9668ea4ffadf99be4d) * test: Purchase Receipt Provisional Accounting GL Entries (cherry picked from commit 1c78a5a9aa2dbd4d77e7f411699c4cb7dd265cc9) * fix(test): PR Provisional Accounting --------- Co-authored-by: s-aga-r --- .../buying/doctype/supplier/test_supplier.py | 4 ++ .../purchase_receipt/purchase_receipt.py | 4 +- .../purchase_receipt/test_purchase_receipt.py | 43 +++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py index b9fc344647b..7c7467e6f64 100644 --- a/erpnext/buying/doctype/supplier/test_supplier.py +++ b/erpnext/buying/doctype/supplier/test_supplier.py @@ -195,6 +195,9 @@ class TestSupplier(FrappeTestCase): def create_supplier(**args): args = frappe._dict(args) + if not args.supplier_name: + args.supplier_name = frappe.generate_hash() + if frappe.db.exists("Supplier", args.supplier_name): return frappe.get_doc("Supplier", args.supplier_name) @@ -202,6 +205,7 @@ def create_supplier(**args): { "doctype": "Supplier", "supplier_name": args.supplier_name, + "default_currency": args.default_currency, "supplier_group": args.supplier_group or "Services", "supplier_type": args.supplier_type or "Company", "tax_withholding_category": args.tax_withholding_category, diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 4f6c6364a47..1873efc711a 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -605,7 +605,7 @@ class PurchaseReceipt(BuyingController): account=provisional_account, cost_center=item.cost_center, debit=0.0, - credit=multiplication_factor * item.amount, + credit=multiplication_factor * item.base_amount, remarks=remarks, against_account=expense_account, account_currency=credit_currency, @@ -619,7 +619,7 @@ class PurchaseReceipt(BuyingController): gl_entries=gl_entries, account=expense_account, cost_center=item.cost_center, - debit=multiplication_factor * item.amount, + debit=multiplication_factor * item.base_amount, credit=0.0, remarks=remarks, against_account=provisional_account, diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index c9433cf5106..2f46809f49d 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2024,6 +2024,49 @@ class TestPurchaseReceipt(FrappeTestCase): ste7.reload() self.assertEqual(ste7.items[0].valuation_rate, valuation_rate) + def test_purchase_receipt_provisional_accounting(self): + # Step - 1: Create Supplier with Default Currency as USD + from erpnext.buying.doctype.supplier.test_supplier import create_supplier + + supplier = create_supplier(default_currency="USD") + + # Step - 2: Setup Company for Provisional Accounting + from erpnext.accounts.doctype.account.test_account import create_account + + provisional_account = create_account( + account_name="Provision Account", + parent_account="Current Liabilities - _TC", + company="_Test Company", + ) + company = frappe.get_doc("Company", "_Test Company") + company.enable_provisional_accounting_for_non_stock_items = 1 + company.default_provisional_account = provisional_account + company.save() + + # Step - 3: Create Non-Stock Item + item = make_item(properties={"is_stock_item": 0}) + + # Step - 4: Create Purchase Receipt + pr = make_purchase_receipt( + qty=2, + item_code=item.name, + company=company.name, + supplier=supplier.name, + currency=supplier.default_currency, + ) + + # Test - 1: Total and Base Total should not be the same as the currency is different + self.assertNotEqual(flt(pr.total, 2), flt(pr.base_total, 2)) + self.assertEqual(flt(pr.total * pr.conversion_rate, 2), flt(pr.base_total, 2)) + + # Test - 2: Sum of Debit or Credit should be equal to Purchase Receipt Base Total + amount = frappe.db.get_value("GL Entry", {"docstatus": 1, "voucher_no": pr.name}, ["sum(debit)"]) + expected_amount = pr.base_total + self.assertEqual(amount, expected_amount) + + company.enable_provisional_accounting_for_non_stock_items = 0 + company.save() + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From 3ecdf028f28229d230faef07babd126ef13ac4fc Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 13 Sep 2023 21:03:45 +0530 Subject: [PATCH 17/51] fix: Remove redundant code (#37001) fix: Remove redundant code (#37001) fix: Remove redundant code (cherry picked from commit 96363dbb07ebb7f22e6450a2e4600d4b80deb4c3) Co-authored-by: ViralKansodiya <141210323+viralkansodiya@users.noreply.github.com> --- erpnext/e_commerce/shopping_cart/cart.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/e_commerce/shopping_cart/cart.py b/erpnext/e_commerce/shopping_cart/cart.py index 4f9088e8c08..57746a234bc 100644 --- a/erpnext/e_commerce/shopping_cart/cart.py +++ b/erpnext/e_commerce/shopping_cart/cart.py @@ -634,7 +634,6 @@ def get_applicable_shipping_rules(party=None, quotation=None): shipping_rules = get_shipping_rules(quotation) if shipping_rules: - rule_label_map = frappe.db.get_values("Shipping Rule", shipping_rules, "label") # we need this in sorted order as per the position of the rule in the settings page return [[rule, rule] for rule in shipping_rules] From f2395a92971f48904e88afb929285def7ba221e4 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 14 Sep 2023 14:28:05 +0530 Subject: [PATCH 18/51] fix: precision issue and column name (#37073) --- .../requested_items_to_order_and_receive.py | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py index 21241e08603..07187352eb7 100644 --- a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py +++ b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py @@ -7,7 +7,7 @@ import copy import frappe from frappe import _ from frappe.query_builder.functions import Coalesce, Sum -from frappe.utils import date_diff, flt, getdate +from frappe.utils import cint, date_diff, flt, getdate def execute(filters=None): @@ -47,8 +47,10 @@ def get_data(filters): mr.transaction_date.as_("date"), mr_item.schedule_date.as_("required_date"), mr_item.item_code.as_("item_code"), - Sum(Coalesce(mr_item.stock_qty, 0)).as_("qty"), - Coalesce(mr_item.stock_uom, "").as_("uom"), + Sum(Coalesce(mr_item.qty, 0)).as_("qty"), + Sum(Coalesce(mr_item.stock_qty, 0)).as_("stock_qty"), + Coalesce(mr_item.uom, "").as_("uom"), + Coalesce(mr_item.stock_uom, "").as_("stock_uom"), Sum(Coalesce(mr_item.ordered_qty, 0)).as_("ordered_qty"), Sum(Coalesce(mr_item.received_qty, 0)).as_("received_qty"), (Sum(Coalesce(mr_item.stock_qty, 0)) - Sum(Coalesce(mr_item.received_qty, 0))).as_( @@ -96,7 +98,7 @@ def get_conditions(filters, query, mr, mr_item): def update_qty_columns(row_to_update, data_row): - fields = ["qty", "ordered_qty", "received_qty", "qty_to_receive", "qty_to_order"] + fields = ["qty", "stock_qty", "ordered_qty", "received_qty", "qty_to_receive", "qty_to_order"] for field in fields: row_to_update[field] += flt(data_row[field]) @@ -104,16 +106,20 @@ def update_qty_columns(row_to_update, data_row): def prepare_data(data, filters): """Prepare consolidated Report data and Chart data""" material_request_map, item_qty_map = {}, {} + precision = cint(frappe.db.get_default("float_precision")) or 2 for row in data: # item wise map for charts if not row["item_code"] in item_qty_map: item_qty_map[row["item_code"]] = { - "qty": row["qty"], - "ordered_qty": row["ordered_qty"], - "received_qty": row["received_qty"], - "qty_to_receive": row["qty_to_receive"], - "qty_to_order": row["qty_to_order"], + "qty": flt(row["stock_qty"], precision), + "stock_qty": flt(row["stock_qty"], precision), + "stock_uom": row["stock_uom"], + "uom": row["uom"], + "ordered_qty": flt(row["ordered_qty"], precision), + "received_qty": flt(row["received_qty"], precision), + "qty_to_receive": flt(row["qty_to_receive"], precision), + "qty_to_order": flt(row["qty_to_order"], precision), } else: item_entry = item_qty_map[row["item_code"]] @@ -200,21 +206,34 @@ def get_columns(filters): {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 100}, {"label": _("Description"), "fieldname": "description", "fieldtype": "Data", "width": 200}, { - "label": _("Stock UOM"), + "label": _("UOM"), "fieldname": "uom", "fieldtype": "Data", "width": 100, }, + { + "label": _("Stock UOM"), + "fieldname": "stock_uom", + "fieldtype": "Data", + "width": 100, + }, ] ) columns.extend( [ { - "label": _("Stock Qty"), + "label": _("Qty"), "fieldname": "qty", "fieldtype": "Float", - "width": 120, + "width": 140, + "convertible": "qty", + }, + { + "label": _("Qty in Stock UOM"), + "fieldname": "stock_qty", + "fieldtype": "Float", + "width": 140, "convertible": "qty", }, { From fffa13f22b39cd768b081a52d454a03b386596ba Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 15 Sep 2023 16:31:53 +0530 Subject: [PATCH 19/51] fix: validate duplicate serial no in DN --- .../stock/doctype/delivery_note/delivery_note.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 3a056500b54..ba6e247d2e4 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -138,6 +138,7 @@ class DeliveryNote(SellingController): self.validate_uom_is_integer("stock_uom", "stock_qty") self.validate_uom_is_integer("uom", "qty") self.validate_with_previous_doc() + self.validate_duplicate_serial_nos() from erpnext.stock.doctype.packed_item.packed_item import make_packing_list @@ -412,6 +413,21 @@ class DeliveryNote(SellingController): pluck="name", ) + def validate_duplicate_serial_nos(self): + serial_nos = [] + for item in self.items: + if not item.serial_no: + continue + + for serial_no in item.serial_no.split("\n"): + if serial_no in serial_nos: + frappe.throw( + _("Row #{0}: Serial No {1} is already selected.").format(item.idx, serial_no), + title=_("Duplicate Serial No"), + ) + else: + serial_nos.append(serial_no) + def update_billed_amount_based_on_so(so_detail, update_modified=True): from frappe.query_builder.functions import Sum From e5177a6e4692d8a5ae7cf0209a402ee273ef3304 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 15 Sep 2023 17:04:32 +0530 Subject: [PATCH 20/51] test: add test case for DN duplicate serial nos --- .../delivery_note/test_delivery_note.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 2565d1b76d1..2acfd84d944 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1211,6 +1211,38 @@ class TestDeliveryNote(FrappeTestCase): self.assertTrue(return_dn.docstatus == 1) + def test_duplicate_serial_no_in_delivery_note(self): + # Step - 1: Create Serial Item + serial_item = make_item( + properties={ + "is_stock_item": 1, + "has_serial_no": 1, + "serial_no_series": frappe.generate_hash("", 10) + ".###", + } + ).name + + # Step - 2: Inward Stock + se = make_stock_entry(item_code=serial_item, target="_Test Warehouse - _TC", qty=4) + + # Step - 3: Create Delivery Note with Duplicare Serial Nos + serial_nos = se.items[0].serial_no.split("\n") + dn = create_delivery_note( + item_code=serial_item, + warehouse="_Test Warehouse - _TC", + qty=2, + do_not_save=True, + ) + dn.items[0].serial_no = "\n".join(serial_nos[:2]) + dn.append("items", dn.items[0].as_dict()) + + # Test - 1: ValidationError should be raised + self.assertRaises(frappe.ValidationError, dn.save) + + # Step - 4: Submit Delivery Note with unique Serial Nos + dn.items[1].serial_no = "\n".join(serial_nos[2:]) + dn.save() + dn.submit() + def tearDown(self): frappe.db.rollback() frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0) From b33db6c79a6b7f37c93f7e49d6721ee8d7593527 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 15 Sep 2023 17:54:20 +0530 Subject: [PATCH 21/51] fix: asset validation misfire on debit notes (cherry picked from commit 097b9892dca4d8c41dfbe17ffd812287692d424e) --- erpnext/controllers/buying_controller.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 1de39967730..01990a3a268 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -162,10 +162,13 @@ class BuyingController(SubcontractingController): purchase_doc_field = ( "purchase_receipt" if self.doctype == "Purchase Receipt" else "purchase_invoice" ) - not_cancelled_asset = [ - d.name - for d in frappe.db.get_all("Asset", {purchase_doc_field: self.return_against, "docstatus": 1}) - ] + not_cancelled_asset = [] + if self.return_against: + not_cancelled_asset = [ + d.name + for d in frappe.db.get_all("Asset", {purchase_doc_field: self.return_against, "docstatus": 1}) + ] + if self.is_return and len(not_cancelled_asset): frappe.throw( _( From 727dcc5034749f2505052aa05c9ad8d8b94fa324 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 15 Sep 2023 21:47:15 +0530 Subject: [PATCH 22/51] fix: ignore user permissions for `Source Warehouse` in MR (backport #37102) (#37110) fix: ignore user permissions for `Source Warehouse` in MR (#37102) fix: ignore user permissions for Source Warehouse in MR (cherry picked from commit fc016680c9015e3d5089bac84868e7556f96d77a) Co-authored-by: s-aga-r --- erpnext/stock/doctype/material_request/material_request.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json index ffec57ca1df..25c765bbced 100644 --- a/erpnext/stock/doctype/material_request/material_request.json +++ b/erpnext/stock/doctype/material_request/material_request.json @@ -296,6 +296,7 @@ "depends_on": "eval:doc.material_request_type == 'Material Transfer'", "fieldname": "set_from_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Set Source Warehouse", "options": "Warehouse" }, @@ -356,7 +357,7 @@ "idx": 70, "is_submittable": 1, "links": [], - "modified": "2023-07-25 17:19:31.662662", + "modified": "2023-09-15 12:07:24.789471", "modified_by": "Administrator", "module": "Stock", "name": "Material Request", From a563fed6dcaf99fa799cbf034dc6d38473e0fd57 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Sat, 16 Sep 2023 11:47:26 +0200 Subject: [PATCH 23/51] fix(ux): move `get_route_options_for_new_doc` to `refresh` (#37092) fix: move `get_route_options_for_new_doc` to `refresh` --- erpnext/public/js/controllers/transaction.js | 32 ++++++++----------- .../stock/doctype/stock_entry/stock_entry.js | 18 +++++------ .../subcontracting_receipt.js | 20 ++++++------ 3 files changed, 32 insertions(+), 38 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index b6b6e2e5ad6..fe24b18098a 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -119,19 +119,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } }); - if(this.frm.fields_dict["items"].grid.get_field('batch_no')) { - this.frm.set_query("batch_no", "items", function(doc, cdt, cdn) { + if(this.frm.fields_dict['items'].grid.get_field('batch_no')) { + this.frm.set_query('batch_no', 'items', function(doc, cdt, cdn) { return me.set_query_for_batch(doc, cdt, cdn); }); - - let batch_field = this.frm.get_docfield('items', 'batch_no'); - if (batch_field) { - batch_field.get_route_options_for_new_doc = (row) => { - return { - 'item': row.doc.item_code - } - }; - } } if( @@ -196,14 +187,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe }); } - let batch_no_field = this.frm.get_docfield("items", "batch_no"); - if (batch_no_field) { - batch_no_field.get_route_options_for_new_doc = function(row) { - return { - "item": row.doc.item_code - } - }; - } if (this.frm.fields_dict["items"].grid.get_field('blanket_order')) { this.frm.set_query("blanket_order", "items", function(doc, cdt, cdn) { @@ -257,6 +240,17 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } ]); } + + if(this.frm.fields_dict['items'].grid.get_field('batch_no')) { + let batch_field = this.frm.get_docfield('items', 'batch_no'); + if (batch_field) { + batch_field.get_route_options_for_new_doc = (row) => { + return { + 'item': row.doc.item_code + } + }; + } + } } is_return() { diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index dd08ef4d1e3..6dd0a58645c 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -101,15 +101,6 @@ frappe.ui.form.on('Stock Entry', { } }); - let batch_field = frm.get_docfield('items', 'batch_no'); - if (batch_field) { - batch_field.get_route_options_for_new_doc = (row) => { - return { - 'item': row.doc.item_code - } - }; - } - frm.add_fetch("bom_no", "inspection_required", "inspection_required"); erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); @@ -345,6 +336,15 @@ frappe.ui.form.on('Stock Entry', { if(!check_should_not_attach_bom_items(frm.doc.bom_no)) { erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); } + + let batch_field = frm.get_docfield('items', 'batch_no'); + if (batch_field) { + batch_field.get_route_options_for_new_doc = (row) => { + return { + 'item': row.doc.item_code + } + }; + } }, get_items_from_transit_entry: function(frm) { diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 4bf008ac406..e335c6ba7a0 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -75,15 +75,6 @@ frappe.ui.form.on('Subcontracting Receipt', { } } }); - - let batch_no_field = frm.get_docfield('items', 'batch_no'); - if (batch_no_field) { - batch_no_field.get_route_options_for_new_doc = function(row) { - return { - 'item': row.doc.item_code - } - }; - } }, refresh: (frm) => { @@ -148,6 +139,15 @@ frappe.ui.form.on('Subcontracting Receipt', { frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty', 'read_only', frm.doc.__onload && frm.doc.__onload.backflush_based_on === 'BOM'); } + + let batch_no_field = frm.get_docfield('items', 'batch_no'); + if (batch_no_field) { + batch_no_field.get_route_options_for_new_doc = function(row) { + return { + 'item': row.doc.item_code + } + }; + } }, set_warehouse: (frm) => { @@ -202,4 +202,4 @@ let set_missing_values = (frm) => { if (!r.exc) frm.refresh(); }, }); -}; \ No newline at end of file +}; From 13aaff30a5d6ae27d131eecee6f9f54575f2688c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 09:57:11 +0530 Subject: [PATCH 24/51] fix: company wise deferred accounting fields in item (#37023) * fix: company wise deferred accounting fields in item (#37023) * fix: move deferred accounts in accounting section * fix: move deferred check boxes in item accounting * fix: show company wise acc in filters * fix: fetch item deferred account from child table * fix: tests using deferred acc * refactor: use cached value * fix: cached value call * feat: patch to migrate deferred acc * fix: hardcode education module doctypes in patch * chore: resolve conflicts --------- Co-authored-by: Deepesh Garg (cherry picked from commit 099468e3cf1f96a32e2257729ee2a48024ab91d3) # Conflicts: # erpnext/patches.txt # erpnext/patches/v14_0/delete_education_doctypes.py # erpnext/stock/doctype/item/item.json * chore: resolve conflicts * chore: resolve conflicts --------- Co-authored-by: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com> Co-authored-by: Deepesh Garg --- .../purchase_invoice/test_purchase_invoice.py | 2 +- .../sales_invoice/test_sales_invoice.py | 4 +- .../test_deferred_revenue_and_expense.py | 4 +- erpnext/patches.txt | 1 + .../v14_0/delete_education_doctypes.py | 11 ++++ .../v14_0/delete_healthcare_doctypes.py | 2 +- ...rate_deferred_accounts_to_item_defaults.py | 39 ++++++++++++ erpnext/stock/doctype/item/item.js | 6 +- erpnext/stock/doctype/item/item.json | 61 +++++++------------ .../doctype/item_default/item_default.json | 31 +++++++++- erpnext/stock/get_item_details.py | 6 +- 11 files changed, 118 insertions(+), 49 deletions(-) create mode 100644 erpnext/patches/v14_0/migrate_deferred_accounts_to_item_defaults.py diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 0f8e77952cf..30265aeb50e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1153,7 +1153,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): item = create_item("_Test Item for Deferred Accounting", is_purchase_item=True) item.enable_deferred_expense = 1 - item.deferred_expense_account = deferred_account + item.item_defaults[0].deferred_expense_account = deferred_account item.save() pi = make_purchase_invoice(item=item.name, qty=1, rate=100, do_not_save=True) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index eee99dcfde0..378be113e7c 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2322,7 +2322,7 @@ class TestSalesInvoice(unittest.TestCase): item = create_item("_Test Item for Deferred Accounting") item.enable_deferred_revenue = 1 - item.deferred_revenue_account = deferred_account + item.item_defaults[0].deferred_revenue_account = deferred_account item.no_of_months = 12 item.save() @@ -3102,7 +3102,7 @@ class TestSalesInvoice(unittest.TestCase): item = create_item("_Test Item for Deferred Accounting") item.enable_deferred_expense = 1 - item.deferred_revenue_account = deferred_account + item.item_defaults[0].deferred_revenue_account = deferred_account item.save() si = create_sales_invoice( diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py index 28d0c20a918..7b1a9027780 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py +++ b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py @@ -81,7 +81,7 @@ class TestDeferredRevenueAndExpense(FrappeTestCase, AccountsTestMixin): self.create_item("_Test Internet Subscription", 0, self.warehouse, self.company) item = frappe.get_doc("Item", self.item) item.enable_deferred_revenue = 1 - item.deferred_revenue_account = self.deferred_revenue_account + item.item_defaults[0].deferred_revenue_account = self.deferred_revenue_account item.no_of_months = 3 item.save() @@ -150,7 +150,7 @@ class TestDeferredRevenueAndExpense(FrappeTestCase, AccountsTestMixin): self.create_item("_Test Office Desk", 0, self.warehouse, self.company) item = frappe.get_doc("Item", self.item) item.enable_deferred_expense = 1 - item.deferred_expense_account = self.deferred_expense_account + item.item_defaults[0].deferred_expense_account = self.deferred_expense_account item.no_of_months_exp = 3 item.save() diff --git a/erpnext/patches.txt b/erpnext/patches.txt index cde484eb0fa..19f8dab9a17 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -339,5 +339,6 @@ erpnext.patches.v14_0.update_closing_balances #15-07-2023 execute:frappe.defaults.clear_default("fiscal_year") execute:frappe.db.set_single_value('Selling Settings', 'allow_negative_rates_for_items', 0) erpnext.patches.v14_0.correct_asset_value_if_je_with_workflow +erpnext.patches.v14_0.migrate_deferred_accounts_to_item_defaults # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger diff --git a/erpnext/patches/v14_0/delete_education_doctypes.py b/erpnext/patches/v14_0/delete_education_doctypes.py index 76b2300fd2a..55b64eaabd8 100644 --- a/erpnext/patches/v14_0/delete_education_doctypes.py +++ b/erpnext/patches/v14_0/delete_education_doctypes.py @@ -46,6 +46,17 @@ def execute(): for doctype in doctypes: frappe.delete_doc("DocType", doctype, ignore_missing=True) + titles = [ + "Fees", + "Student Admission", + "Grant Application", + "Chapter", + "Certification Application", + ] + items = frappe.get_all("Portal Menu Item", filters=[["title", "in", titles]], pluck="name") + for item in items: + frappe.delete_doc("Portal Menu Item", item, ignore_missing=True, force=True) + frappe.delete_doc("Module Def", "Education", ignore_missing=True, force=True) click.secho( diff --git a/erpnext/patches/v14_0/delete_healthcare_doctypes.py b/erpnext/patches/v14_0/delete_healthcare_doctypes.py index 2c699e4a9f2..896a4409507 100644 --- a/erpnext/patches/v14_0/delete_healthcare_doctypes.py +++ b/erpnext/patches/v14_0/delete_healthcare_doctypes.py @@ -41,7 +41,7 @@ def execute(): for card in cards: frappe.delete_doc("Number Card", card, ignore_missing=True, force=True) - titles = ["Lab Test", "Prescription", "Patient Appointment"] + titles = ["Lab Test", "Prescription", "Patient Appointment", "Patient"] items = frappe.get_all("Portal Menu Item", filters=[["title", "in", titles]], pluck="name") for item in items: frappe.delete_doc("Portal Menu Item", item, ignore_missing=True, force=True) diff --git a/erpnext/patches/v14_0/migrate_deferred_accounts_to_item_defaults.py b/erpnext/patches/v14_0/migrate_deferred_accounts_to_item_defaults.py new file mode 100644 index 00000000000..44b830babb2 --- /dev/null +++ b/erpnext/patches/v14_0/migrate_deferred_accounts_to_item_defaults.py @@ -0,0 +1,39 @@ +import frappe + + +def execute(): + try: + item_dict = get_deferred_accounts() + add_to_item_defaults(item_dict) + except Exception: + frappe.db.rollback() + frappe.log_error("Failed to migrate deferred accounts in Item Defaults.") + + +def get_deferred_accounts(): + item = frappe.qb.DocType("Item") + return ( + frappe.qb.from_(item) + .select(item.name, item.deferred_expense_account, item.deferred_revenue_account) + .where((item.enable_deferred_expense == 1) | (item.enable_deferred_revenue == 1)) + .run(as_dict=True) + ) + + +def add_to_item_defaults(item_dict): + for item in item_dict: + add_company_wise_item_default(item, "deferred_expense_account") + add_company_wise_item_default(item, "deferred_revenue_account") + + +def add_company_wise_item_default(item, account_type): + company = frappe.get_cached_value("Account", item[account_type], "company") + if company and item[account_type]: + item_defaults = frappe.get_cached_value("Item", item["name"], "item_defaults") + for item_row in item_defaults: + if item_row.company == company: + frappe.set_value("Item Default", item_row.name, account_type, item[account_type]) + break + else: + item_defaults.append({"company": company, account_type: item[account_type]}) + frappe.set_value("Item", item["name"], "item_defaults", item_defaults) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 86f9af25e7a..b306a41bb83 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -350,18 +350,20 @@ $.extend(erpnext.item, { } } - frm.fields_dict['deferred_revenue_account'].get_query = function() { + frm.fields_dict["item_defaults"].grid.get_field("deferred_revenue_account").get_query = function(doc, cdt, cdn) { return { filters: { + "company": locals[cdt][cdn].company, 'root_type': 'Liability', "is_group": 0 } } } - frm.fields_dict['deferred_expense_account'].get_query = function() { + frm.fields_dict["item_defaults"].grid.get_field("deferred_expense_account").get_query = function(doc, cdt, cdn) { return { filters: { + "company": locals[cdt][cdn].company, 'root_type': 'Asset', "is_group": 0 } diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 7f4ba032e86..7d0a387f43e 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -70,6 +70,13 @@ "variant_based_on", "attributes", "accounting", + "deferred_accounting_section", + "enable_deferred_expense", + "no_of_months_exp", + "column_break_9s9o", + "enable_deferred_revenue", + "no_of_months", + "section_break_avcp", "item_defaults", "purchasing_tab", "purchase_uom", @@ -85,10 +92,6 @@ "delivered_by_supplier", "column_break2", "supplier_items", - "deferred_expense_section", - "enable_deferred_expense", - "deferred_expense_account", - "no_of_months_exp", "foreign_trade_details", "country_of_origin", "column_break_59", @@ -99,10 +102,6 @@ "is_sales_item", "column_break3", "max_discount", - "deferred_revenue", - "enable_deferred_revenue", - "deferred_revenue_account", - "no_of_months", "customer_details", "customer_items", "item_tax_section_break", @@ -657,20 +656,6 @@ "oldfieldname": "max_discount", "oldfieldtype": "Currency" }, - { - "collapsible": 1, - "fieldname": "deferred_revenue", - "fieldtype": "Section Break", - "label": "Deferred Revenue" - }, - { - "depends_on": "enable_deferred_revenue", - "fieldname": "deferred_revenue_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Deferred Revenue Account", - "options": "Account" - }, { "default": "0", "fieldname": "enable_deferred_revenue", @@ -681,21 +666,7 @@ "depends_on": "enable_deferred_revenue", "fieldname": "no_of_months", "fieldtype": "Int", - "label": "No of Months" - }, - { - "collapsible": 1, - "fieldname": "deferred_expense_section", - "fieldtype": "Section Break", - "label": "Deferred Expense" - }, - { - "depends_on": "enable_deferred_expense", - "fieldname": "deferred_expense_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Deferred Expense Account", - "options": "Account" + "label": "No of Months (Revenue)" }, { "default": "0", @@ -904,6 +875,20 @@ "fieldname": "accounting", "fieldtype": "Tab Break", "label": "Accounting" + }, + { + "fieldname": "column_break_9s9o", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_avcp", + "fieldtype": "Section Break" + }, + { + "collapsible": 1, + "fieldname": "deferred_accounting_section", + "fieldtype": "Section Break", + "label": "Deferred Accounting" } ], "icon": "fa fa-tag", @@ -912,7 +897,7 @@ "index_web_pages_for_search": 1, "links": [], "make_attachments_public": 1, - "modified": "2023-07-14 17:18:18.658942", + "modified": "2023-09-11 13:46:32.688051", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/doctype/item_default/item_default.json b/erpnext/stock/doctype/item_default/item_default.json index 042d398256a..28956612762 100644 --- a/erpnext/stock/doctype/item_default/item_default.json +++ b/erpnext/stock/doctype/item_default/item_default.json @@ -19,7 +19,11 @@ "selling_defaults", "selling_cost_center", "column_break_12", - "income_account" + "income_account", + "deferred_accounting_defaults_section", + "deferred_expense_account", + "column_break_kwad", + "deferred_revenue_account" ], "fields": [ { @@ -108,11 +112,34 @@ "fieldtype": "Link", "label": "Default Provisional Account", "options": "Account" + }, + { + "fieldname": "deferred_accounting_defaults_section", + "fieldtype": "Section Break", + "label": "Deferred Accounting Defaults" + }, + { + "depends_on": "eval: parent.enable_deferred_expense", + "fieldname": "deferred_expense_account", + "fieldtype": "Link", + "label": "Deferred Expense Account", + "options": "Account" + }, + { + "depends_on": "eval: parent.enable_deferred_revenue", + "fieldname": "deferred_revenue_account", + "fieldtype": "Link", + "label": "Deferred Revenue Account", + "options": "Account" + }, + { + "fieldname": "column_break_kwad", + "fieldtype": "Column Break" } ], "istable": 1, "links": [], - "modified": "2022-04-10 20:18:54.148195", + "modified": "2023-09-04 12:33:14.607267", "modified_by": "Administrator", "module": "Stock", "name": "Item Default", diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index f3adefb3e74..f7eb859f830 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -729,7 +729,11 @@ def get_default_discount_account(args, item): def get_default_deferred_account(args, item, fieldname=None): if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): return ( - item.get(fieldname) + frappe.get_cached_value( + "Item Default", + {"parent": args.item_code, "company": args.get("company")}, + fieldname, + ) or args.get(fieldname) or frappe.get_cached_value("Company", args.company, "default_" + fieldname) ) From 8b2328c6d31b2985f40a5516a2a6377a1bc53218 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 2 Jan 2023 12:21:45 +0530 Subject: [PATCH 25/51] refactor: show balance checkbox in Accounts Settings (cherry picked from commit 1b78fae6fc5e61a02781abe298a6436364a6695d) # Conflicts: # erpnext/accounts/doctype/accounts_settings/accounts_settings.json --- .../accounts_settings/accounts_settings.json | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 467c68f1021..bfd1eb37f47 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -64,9 +64,14 @@ "column_break_25", "frozen_accounts_modifier", "report_settings_sb", +<<<<<<< HEAD "banking_tab", "enable_party_matching", "enable_fuzzy_matching" +======= + "tab_break_dpet", + "show_balance_in_coa" +>>>>>>> 1b78fae6fc (refactor: show balance checkbox in Accounts Settings) ], "fields": [ { @@ -355,6 +360,7 @@ "label": "Allow multi-currency invoices against single party account " }, { +<<<<<<< HEAD "default": "0", "description": "Split Early Payment Discount Loss into Income and Tax Loss", "fieldname": "book_tax_discount_loss", @@ -416,6 +422,17 @@ "fieldname": "ignore_account_closing_balance", "fieldtype": "Check", "label": "Ignore Account Closing Balance" +======= + "fieldname": "tab_break_dpet", + "fieldtype": "Tab Break", + "label": "Chart Of Accounts" + }, + { + "default": "1", + "fieldname": "show_balance_in_coa", + "fieldtype": "Check", + "label": "Show Balances in Chart Of Accounts" +>>>>>>> 1b78fae6fc (refactor: show balance checkbox in Accounts Settings) } ], "icon": "icon-cog", @@ -423,7 +440,11 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], +<<<<<<< HEAD "modified": "2023-07-27 15:05:34.000264", +======= + "modified": "2023-01-02 12:07:42.434214", +>>>>>>> 1b78fae6fc (refactor: show balance checkbox in Accounts Settings) "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", From 18702841af6fc13ae89e1a2bc56ea703143e076b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 2 Jan 2023 12:22:33 +0530 Subject: [PATCH 26/51] refactor: Show Balance in COA based on Accounts Settings (cherry picked from commit 23fbe86d51193c7242119bec81d4d63c8191d49d) --- .../accounts/doctype/account/account_tree.js | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index 8ae90ceb383..d537adfcbfd 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -56,36 +56,41 @@ frappe.treeview_settings["Account"] = { accounts = nodes; } - const get_balances = frappe.call({ - method: 'erpnext.accounts.utils.get_account_balances', - args: { - accounts: accounts, - company: cur_tree.args.company - }, - }); + frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => { + if(value) { - get_balances.then(r => { - if (!r.message || r.message.length == 0) return; + const get_balances = frappe.call({ + method: 'erpnext.accounts.utils.get_account_balances', + args: { + accounts: accounts, + company: cur_tree.args.company + }, + }); - for (let account of r.message) { + get_balances.then(r => { + if (!r.message || r.message.length == 0) return; - const node = cur_tree.nodes && cur_tree.nodes[account.value]; - if (!node || node.is_root) continue; + for (let account of r.message) { - // show Dr if positive since balance is calculated as debit - credit else show Cr - const balance = account.balance_in_account_currency || account.balance; - const dr_or_cr = balance > 0 ? "Dr": "Cr"; - const format = (value, currency) => format_currency(Math.abs(value), currency); + const node = cur_tree.nodes && cur_tree.nodes[account.value]; + if (!node || node.is_root) continue; - if (account.balance!==undefined) { - node.parent && node.parent.find('.balance-area').remove(); - $('' - + (account.balance_in_account_currency ? - (format(account.balance_in_account_currency, account.account_currency) + " / ") : "") - + format(account.balance, account.company_currency) - + " " + dr_or_cr - + '').insertBefore(node.$ul); - } + // show Dr if positive since balance is calculated as debit - credit else show Cr + const balance = account.balance_in_account_currency || account.balance; + const dr_or_cr = balance > 0 ? "Dr": "Cr"; + const format = (value, currency) => format_currency(Math.abs(value), currency); + + if (account.balance!==undefined) { + node.parent && node.parent.find('.balance-area').remove(); + $('' + + (account.balance_in_account_currency ? + (format(account.balance_in_account_currency, account.account_currency) + " / ") : "") + + format(account.balance, account.company_currency) + + " " + dr_or_cr + + '').insertBefore(node.$ul); + } + } + }); } }); }, From 79321f56caf12af2d2d5abfd3260104cc1279936 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 18 Sep 2023 10:29:29 +0530 Subject: [PATCH 27/51] chore: resolve conflicts --- .../accounts_settings/accounts_settings.json | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index bfd1eb37f47..3ab9d2b60d5 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -64,14 +64,11 @@ "column_break_25", "frozen_accounts_modifier", "report_settings_sb", -<<<<<<< HEAD "banking_tab", "enable_party_matching", - "enable_fuzzy_matching" -======= + "enable_fuzzy_matching", "tab_break_dpet", "show_balance_in_coa" ->>>>>>> 1b78fae6fc (refactor: show balance checkbox in Accounts Settings) ], "fields": [ { @@ -360,7 +357,6 @@ "label": "Allow multi-currency invoices against single party account " }, { -<<<<<<< HEAD "default": "0", "description": "Split Early Payment Discount Loss into Income and Tax Loss", "fieldname": "book_tax_discount_loss", @@ -422,7 +418,8 @@ "fieldname": "ignore_account_closing_balance", "fieldtype": "Check", "label": "Ignore Account Closing Balance" -======= + }, + { "fieldname": "tab_break_dpet", "fieldtype": "Tab Break", "label": "Chart Of Accounts" @@ -432,7 +429,6 @@ "fieldname": "show_balance_in_coa", "fieldtype": "Check", "label": "Show Balances in Chart Of Accounts" ->>>>>>> 1b78fae6fc (refactor: show balance checkbox in Accounts Settings) } ], "icon": "icon-cog", @@ -440,11 +436,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], -<<<<<<< HEAD "modified": "2023-07-27 15:05:34.000264", -======= - "modified": "2023-01-02 12:07:42.434214", ->>>>>>> 1b78fae6fc (refactor: show balance checkbox in Accounts Settings) "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", From c41cb3930c76efb620cf8bc27672dab8c5302e6c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 13:03:08 +0530 Subject: [PATCH 28/51] fix: Don't allow merging accounts with different currency (#37074) * fix: Don't allow merging accounts with different currency (#37074) * fix: Don't allow merging accounts with different currency * test: Update conflicting values * test: Update conflicting values (cherry picked from commit 5e21e7cd1da12cad1d3491d92aafa2664c8518a8) # Conflicts: # erpnext/accounts/doctype/account/account.js # erpnext/accounts/doctype/account/account.py * chore: resolve conflicts --------- Co-authored-by: Deepesh Garg --- erpnext/accounts/doctype/account/account.js | 3 - erpnext/accounts/doctype/account/account.py | 39 ++++-- .../accounts/doctype/account/test_account.py | 119 ++++++++++-------- .../doctype/ledger_merge/ledger_merge.py | 3 - 4 files changed, 94 insertions(+), 70 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.js b/erpnext/accounts/doctype/account/account.js index 320e1cab7c3..7d63b257faf 100644 --- a/erpnext/accounts/doctype/account/account.js +++ b/erpnext/accounts/doctype/account/account.js @@ -117,9 +117,6 @@ frappe.ui.form.on('Account', { args: { old: frm.doc.name, new: data.name, - is_group: frm.doc.is_group, - root_type: frm.doc.root_type, - company: frm.doc.company }, callback: function(r) { if(!r.exc) { diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index bbe4c54a71a..22ddc2ffae3 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -18,6 +18,10 @@ class BalanceMismatchError(frappe.ValidationError): pass +class InvalidAccountMergeError(frappe.ValidationError): + pass + + class Account(NestedSet): nsm_parent_field = "parent_account" @@ -444,24 +448,35 @@ def update_account_number(name, account_name, account_number=None, from_descenda @frappe.whitelist() -def merge_account(old, new, is_group, root_type, company): +def merge_account(old, new): # Validate properties before merging - if not frappe.db.exists("Account", new): + new_account = frappe.get_cached_doc("Account", new) + old_account = frappe.get_cached_doc("Account", old) + + if not new_account: throw(_("Account {0} does not exist").format(new)) - val = list(frappe.db.get_value("Account", new, ["is_group", "root_type", "company"])) - - if val != [cint(is_group), root_type, company]: + if ( + cint(new_account.is_group), + new_account.root_type, + new_account.company, + cstr(new_account.account_currency), + ) != ( + cint(old_account.is_group), + old_account.root_type, + old_account.company, + cstr(old_account.account_currency), + ): throw( - _( - """Merging is only possible if following properties are same in both records. Is Group, Root Type, Company""" - ) + msg=_( + """Merging is only possible if following properties are same in both records. Is Group, Root Type, Company and Account Currency""" + ), + title=("Invalid Accounts"), + exc=InvalidAccountMergeError, ) - if is_group and frappe.db.get_value("Account", new, "parent_account") == old: - frappe.db.set_value( - "Account", new, "parent_account", frappe.db.get_value("Account", old, "parent_account") - ) + if old_account.is_group and new_account.parent_account == old: + new_account.db_set("parent_account", frappe.get_cached_value("Account", old, "parent_account")) frappe.rename_doc("Account", old, new, merge=1, force=1) diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py index 62303bd723f..30eebef7fba 100644 --- a/erpnext/accounts/doctype/account/test_account.py +++ b/erpnext/accounts/doctype/account/test_account.py @@ -7,7 +7,11 @@ import unittest import frappe from frappe.test_runner import make_test_records -from erpnext.accounts.doctype.account.account import merge_account, update_account_number +from erpnext.accounts.doctype.account.account import ( + InvalidAccountMergeError, + merge_account, + update_account_number, +) from erpnext.stock import get_company_default_inventory_account, get_warehouse_account test_dependencies = ["Company"] @@ -47,49 +51,53 @@ class TestAccount(unittest.TestCase): frappe.delete_doc("Account", "1211-11-4 - 6 - Debtors 1 - Test - - _TC") def test_merge_account(self): - if not frappe.db.exists("Account", "Current Assets - _TC"): - acc = frappe.new_doc("Account") - acc.account_name = "Current Assets" - acc.is_group = 1 - acc.parent_account = "Application of Funds (Assets) - _TC" - acc.company = "_Test Company" - acc.insert() - if not frappe.db.exists("Account", "Securities and Deposits - _TC"): - acc = frappe.new_doc("Account") - acc.account_name = "Securities and Deposits" - acc.parent_account = "Current Assets - _TC" - acc.is_group = 1 - acc.company = "_Test Company" - acc.insert() - if not frappe.db.exists("Account", "Earnest Money - _TC"): - acc = frappe.new_doc("Account") - acc.account_name = "Earnest Money" - acc.parent_account = "Securities and Deposits - _TC" - acc.company = "_Test Company" - acc.insert() - if not frappe.db.exists("Account", "Cash In Hand - _TC"): - acc = frappe.new_doc("Account") - acc.account_name = "Cash In Hand" - acc.is_group = 1 - acc.parent_account = "Current Assets - _TC" - acc.company = "_Test Company" - acc.insert() - if not frappe.db.exists("Account", "Accumulated Depreciation - _TC"): - acc = frappe.new_doc("Account") - acc.account_name = "Accumulated Depreciation" - acc.parent_account = "Fixed Assets - _TC" - acc.company = "_Test Company" - acc.account_type = "Accumulated Depreciation" - acc.insert() + create_account( + account_name="Current Assets", + is_group=1, + parent_account="Application of Funds (Assets) - _TC", + company="_Test Company", + ) + + create_account( + account_name="Securities and Deposits", + is_group=1, + parent_account="Current Assets - _TC", + company="_Test Company", + ) + + create_account( + account_name="Earnest Money", + parent_account="Securities and Deposits - _TC", + company="_Test Company", + ) + + create_account( + account_name="Cash In Hand", + is_group=1, + parent_account="Current Assets - _TC", + company="_Test Company", + ) + + create_account( + account_name="Receivable INR", + parent_account="Current Assets - _TC", + company="_Test Company", + account_currency="INR", + ) + + create_account( + account_name="Receivable USD", + parent_account="Current Assets - _TC", + company="_Test Company", + account_currency="USD", + ) - doc = frappe.get_doc("Account", "Securities and Deposits - _TC") parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account") self.assertEqual(parent, "Securities and Deposits - _TC") - merge_account( - "Securities and Deposits - _TC", "Cash In Hand - _TC", doc.is_group, doc.root_type, doc.company - ) + merge_account("Securities and Deposits - _TC", "Cash In Hand - _TC") + parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account") # Parent account of the child account changes after merging @@ -98,30 +106,28 @@ class TestAccount(unittest.TestCase): # Old account doesn't exist after merging self.assertFalse(frappe.db.exists("Account", "Securities and Deposits - _TC")) - doc = frappe.get_doc("Account", "Current Assets - _TC") - # Raise error as is_group property doesn't match self.assertRaises( - frappe.ValidationError, + InvalidAccountMergeError, merge_account, "Current Assets - _TC", "Accumulated Depreciation - _TC", - doc.is_group, - doc.root_type, - doc.company, ) - doc = frappe.get_doc("Account", "Capital Stock - _TC") - # Raise error as root_type property doesn't match self.assertRaises( - frappe.ValidationError, + InvalidAccountMergeError, merge_account, "Capital Stock - _TC", "Softwares - _TC", - doc.is_group, - doc.root_type, - doc.company, + ) + + # Raise error as currency doesn't match + self.assertRaises( + InvalidAccountMergeError, + merge_account, + "Receivable INR - _TC", + "Receivable USD - _TC", ) def test_account_sync(self): @@ -400,11 +406,20 @@ def create_account(**kwargs): "Account", filters={"account_name": kwargs.get("account_name"), "company": kwargs.get("company")} ) if account: - return account + account = frappe.get_doc("Account", account) + account.update( + dict( + is_group=kwargs.get("is_group", 0), + parent_account=kwargs.get("parent_account"), + ) + ) + account.save() + return account.name else: account = frappe.get_doc( dict( doctype="Account", + is_group=kwargs.get("is_group", 0), account_name=kwargs.get("account_name"), account_type=kwargs.get("account_type"), parent_account=kwargs.get("parent_account"), diff --git a/erpnext/accounts/doctype/ledger_merge/ledger_merge.py b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py index 18e5a1ac85b..6e89d039932 100644 --- a/erpnext/accounts/doctype/ledger_merge/ledger_merge.py +++ b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py @@ -49,9 +49,6 @@ def start_merge(docname): merge_account( row.account, ledger_merge.account, - ledger_merge.is_group, - ledger_merge.root_type, - ledger_merge.company, ) row.db_set("merged", 1) frappe.db.commit() From 4b700b726fe4576b215623d21c1ab0d425852205 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 18 Sep 2023 12:39:23 +0530 Subject: [PATCH 29/51] refactor: better date filters in `Get Outstanding Invoices` dialog (cherry picked from commit 9004721859bb940b29bbcf3a16816a5dc72a1b37) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 0e2de4ac395..c5501a58306 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1455,6 +1455,14 @@ def get_outstanding_reference_documents(args): fieldname, args.get(date_fields[0]), args.get(date_fields[1]) ) posting_and_due_date.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])]) + elif args.get(date_fields[0]): + # if only from date is supplied + condition += " and {0} >= '{1}'".format(fieldname, args.get(date_fields[0])) + posting_and_due_date.append(ple[fieldname].gte(args.get(date_fields[0]))) + elif args.get(date_fields[1]): + # if only to date is supplied + condition += " and {0} <= '{1}'".format(fieldname, args.get(date_fields[1])) + posting_and_due_date.append(ple[fieldname].lte(args.get(date_fields[1]))) if args.get("company"): condition += " and company = {0}".format(frappe.db.escape(args.get("company"))) From e62b783f340844783352030de422c6be14b3ddfc Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 18 Sep 2023 16:10:21 +0530 Subject: [PATCH 30/51] fix: don't set from warehouse for purchase material request (#37132) --- .../production_plan/production_plan.js | 9 +++++ .../production_plan/production_plan.py | 10 +++++- .../production_plan/test_production_plan.py | 35 +++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 46c554c1e80..72438ddceea 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -476,6 +476,15 @@ frappe.ui.form.on("Material Request Plan Item", { } }) } + }, + + material_request_type(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + + if (row.from_warehouse && + row.material_request_type !== "Material Transfer") { + frappe.model.set_value(cdt, cdn, 'from_warehouse', ''); + } } }); diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 795cb97fffa..fcaae79d6c5 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -40,6 +40,12 @@ class ProductionPlan(Document): self._rename_temporary_references() validate_uom_is_integer(self, "stock_uom", "planned_qty") self.validate_sales_orders() + self.validate_material_request_type() + + def validate_material_request_type(self): + for row in self.get("mr_items"): + if row.from_warehouse and row.material_request_type != "Material Transfer": + row.from_warehouse = "" @frappe.whitelist() def validate_sales_orders(self, sales_order=None): @@ -749,7 +755,9 @@ class ProductionPlan(Document): "items", { "item_code": item.item_code, - "from_warehouse": item.from_warehouse, + "from_warehouse": item.from_warehouse + if material_request_type == "Material Transfer" + else None, "qty": item.quantity, "schedule_date": schedule_date, "warehouse": item.warehouse, diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index f5778847b9c..78080645050 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1087,6 +1087,41 @@ class TestProductionPlan(FrappeTestCase): ) self.assertEqual(reserved_qty_after_mr, before_qty) + def test_from_warehouse_for_purchase_material_request(self): + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + from erpnext.stock.utils import get_or_make_bin + + create_item("RM-TEST-123 For Purchase", valuation_rate=100) + bin_name = get_or_make_bin("RM-TEST-123 For Purchase", "_Test Warehouse - _TC") + t_warehouse = create_warehouse("_Test Store - _TC") + make_stock_entry( + item_code="Raw Material Item 1", + qty=5, + rate=100, + target=t_warehouse, + ) + + plan = create_production_plan(item_code="Test Production Item 1", do_not_save=1) + mr_items = get_items_for_material_requests( + plan.as_dict(), warehouses=[{"warehouse": t_warehouse}] + ) + + for d in mr_items: + plan.append("mr_items", d) + + plan.save() + + for row in plan.mr_items: + if row.material_request_type == "Material Transfer": + self.assertEqual(row.from_warehouse, t_warehouse) + + row.material_request_type = "Purchase" + + plan.save() + + for row in plan.mr_items: + self.assertFalse(row.from_warehouse) + def test_skip_available_qty_for_sub_assembly_items(self): from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom From 366325ca3ca0a99bb2384755c6f1cc7021649380 Mon Sep 17 00:00:00 2001 From: RitvikSardana <65544983+RitvikSardana@users.noreply.github.com> Date: Mon, 18 Sep 2023 16:32:45 +0530 Subject: [PATCH 31/51] fix: Duplicate Serial Nos validation in POS (#36927) * fix: added validation for duplicate serial nos in pos * chore: code cleanup * chore: code cleanup * fix: removed duplicate batch number validation * chore: code cleanup --- .../doctype/pos_invoice/pos_invoice.py | 15 +++------ .../doctype/pos_invoice/test_pos_invoice.py | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index e7a7ae24ceb..ad3a70a862f 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -1,5 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors # For license information, please see license.txt + + import collections import frappe @@ -43,7 +45,6 @@ class POSInvoice(SalesInvoice): self.validate_debit_to_acc() self.validate_write_off_account() self.validate_change_amount() - self.validate_duplicate_serial_and_batch_no() self.validate_change_account() self.validate_item_cost_centers() self.validate_warehouse() @@ -56,6 +57,7 @@ class POSInvoice(SalesInvoice): self.validate_payment_amount() self.validate_loyalty_transaction() self.validate_company_with_pos_company() + self.validate_duplicate_serial_no() if self.coupon_code: from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code @@ -156,27 +158,18 @@ class POSInvoice(SalesInvoice): title=_("Item Unavailable"), ) - def validate_duplicate_serial_and_batch_no(self): + def validate_duplicate_serial_no(self): serial_nos = [] - batch_nos = [] for row in self.get("items"): if row.serial_no: serial_nos = row.serial_no.split("\n") - if row.batch_no and not row.serial_no: - batch_nos.append(row.batch_no) - if serial_nos: for key, value in collections.Counter(serial_nos).items(): if value > 1: frappe.throw(_("Duplicate Serial No {0} found").format("key")) - if batch_nos: - for key, value in collections.Counter(batch_nos).items(): - if value > 1: - frappe.throw(_("Duplicate Batch No {0} found").format("key")) - def validate_pos_reserved_batch_qty(self, item): filters = {"item_code": item.item_code, "warehouse": item.warehouse, "batch_no": item.batch_no} diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 3132fdd259a..bd2fee3b0ad 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -464,6 +464,37 @@ class TestPOSInvoice(unittest.TestCase): pos2.insert() self.assertRaises(frappe.ValidationError, pos2.submit) + def test_pos_invoice_with_duplicate_serial_no(self): + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item + + se = make_serialized_item( + company="_Test Company", + target_warehouse="Stores - _TC", + cost_center="Main - _TC", + expense_account="Cost of Goods Sold - _TC", + ) + + serial_nos = get_serial_nos(se.get("items")[0].serial_no) + + pos = create_pos_invoice( + company="_Test Company", + debit_to="Debtors - _TC", + account_for_change_amount="Cash - _TC", + warehouse="Stores - _TC", + income_account="Sales - _TC", + expense_account="Cost of Goods Sold - _TC", + cost_center="Main - _TC", + item=se.get("items")[0].item_code, + rate=1000, + qty=2, + do_not_save=1, + ) + + pos.get("items")[0].has_serial_no = 1 + pos.get("items")[0].serial_no = serial_nos[0] + "\n" + serial_nos[0] + self.assertRaises(frappe.ValidationError, pos.submit) + def test_invalid_serial_no_validation(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item From dfe5f63f592a01eee786239ac89fa360b3c96534 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 13:07:30 +0530 Subject: [PATCH 32/51] refactor: more generic filters in Accounts Receivable report (backport #37131) (#37137) * refactor: replace 'customer' filter with 'party_type' and 'party' (cherry picked from commit ac650d2e7a088f468551ed21874c653055ef1422) * chore: remove stale code (cherry picked from commit 083c82c20607bee5b817ab792f8615dc72aaa97b) * refactor(test): AR output filtered on USD customers (cherry picked from commit 08d91ab831928c88e25d88682abc7b97fa91f516) --------- Co-authored-by: ruthra kumar --- .../accounts_payable/test_accounts_payable.py | 2 +- .../accounts_receivable.js | 59 +++++++------------ .../accounts_receivable.py | 28 +++++---- .../test_accounts_receivable.py | 37 ++++++++++++ 4 files changed, 76 insertions(+), 50 deletions(-) diff --git a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py index cb84cf4fc0a..3cf93cc8659 100644 --- a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py +++ b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py @@ -24,7 +24,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): def tearDown(self): frappe.db.rollback() - def test_accounts_receivable_with_supplier(self): + def test_accounts_payable_for_foreign_currency_supplier(self): pi = self.create_purchase_invoice(do_not_submit=True) pi.currency = "USD" pi.conversion_rate = 80 diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index cb8ec876e9e..bb00d616dbc 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -38,32 +38,31 @@ frappe.query_reports["Accounts Receivable"] = { } }, { - "fieldname": "customer", - "label": __("Customer"), + "fieldname": "party_type", + "label": __("Party Type"), "fieldtype": "Link", - "options": "Customer", + "options": "Party Type", + "Default": "Customer", + get_query: () => { + return { + filters: { + 'account_type': 'Receivable' + } + }; + }, on_change: () => { - var customer = frappe.query_report.get_filter_value('customer'); - var company = frappe.query_report.get_filter_value('company'); - if (customer) { - frappe.db.get_value('Customer', customer, ["customer_name", "payment_terms"], function(value) { - frappe.query_report.set_filter_value('customer_name', value["customer_name"]); - frappe.query_report.set_filter_value('payment_terms', value["payment_terms"]); - }); + frappe.query_report.set_filter_value('party', ""); + let party_type = frappe.query_report.get_filter_value('party_type'); + frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer"); - frappe.db.get_value('Customer Credit Limit', {'parent': customer, 'company': company}, - ["credit_limit"], function(value) { - if (value) { - frappe.query_report.set_filter_value('credit_limit', value["credit_limit"]); - } - }, "Customer"); - } else { - frappe.query_report.set_filter_value('customer_name', ""); - frappe.query_report.set_filter_value('credit_limit', ""); - frappe.query_report.set_filter_value('payment_terms', ""); - } } }, + { + "fieldname":"party", + "label": __("Party"), + "fieldtype": "Dynamic Link", + "options": "party_type", + }, { "fieldname": "party_account", "label": __("Receivable Account"), @@ -174,24 +173,6 @@ frappe.query_reports["Accounts Receivable"] = { "fieldname": "show_remarks", "label": __("Show Remarks"), "fieldtype": "Check", - }, - { - "fieldname": "customer_name", - "label": __("Customer Name"), - "fieldtype": "Data", - "hidden": 1 - }, - { - "fieldname": "payment_terms", - "label": __("Payment Tems"), - "fieldtype": "Data", - "hidden": 1 - }, - { - "fieldname": "credit_limit", - "label": __("Credit Limit"), - "fieldtype": "Currency", - "hidden": 1 } ], diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 14f8993727a..79424023652 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -769,15 +769,12 @@ class ReceivablePayableReport(object): self.or_filters = [] for party_type in self.party_type: - party_type_field = scrub(party_type) - self.or_filters.append(self.ple.party_type == party_type) + self.add_common_filters() - self.add_common_filters(party_type_field=party_type_field) - - if party_type_field == "customer": + if self.account_type == "Receivable": self.add_customer_filters() - elif party_type_field == "supplier": + elif self.account_type == "Payable": self.add_supplier_filters() if self.filters.cost_center: @@ -793,16 +790,13 @@ class ReceivablePayableReport(object): ] self.qb_selection_filter.append(self.ple.cost_center.isin(cost_center_list)) - def add_common_filters(self, party_type_field): + def add_common_filters(self): if self.filters.company: self.qb_selection_filter.append(self.ple.company == self.filters.company) if self.filters.finance_book: self.qb_selection_filter.append(self.ple.finance_book == self.filters.finance_book) - if self.filters.get(party_type_field): - self.qb_selection_filter.append(self.ple.party == self.filters.get(party_type_field)) - if self.filters.get("party_type"): self.qb_selection_filter.append(self.filters.party_type == self.ple.party_type) @@ -969,6 +963,20 @@ class ReceivablePayableReport(object): fieldtype="Link", options="Contact", ) + if self.filters.party_type == "Customer": + self.add_column( + _("Customer Name"), + fieldname="customer_name", + fieldtype="Link", + options="Customer", + ) + elif self.filters.party_type == "Supplier": + self.add_column( + _("Supplier Name"), + fieldname="supplier_name", + fieldtype="Link", + options="Supplier", + ) self.add_column(label=_("Cost Center"), fieldname="cost_center", fieldtype="Data") self.add_column(label=_("Voucher Type"), fieldname="voucher_type", fieldtype="Data") diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index 0c7d931d2d5..b98916ee443 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -568,3 +568,40 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): row.account_currency, ], ) + + def test_usd_customer_filter(self): + filters = { + "company": self.company, + "party_type": "Customer", + "party": self.customer, + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + } + + si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True) + si.currency = "USD" + si.conversion_rate = 80 + si.debit_to = self.debtors_usd + si.save().submit() + name = si.name + + # check invoice grand total and invoiced column's value for 3 payment terms + report = execute(filters) + + expected = { + "voucher_type": si.doctype, + "voucher_no": si.name, + "party_account": self.debtors_usd, + "customer_name": self.customer, + "invoiced": 100.0, + "outstanding": 100.0, + "account_currency": "USD", + } + self.assertEqual(len(report[1]), 1) + report_output = report[1][0] + for field in expected: + with self.subTest(field=field): + self.assertEqual(report_output.get(field), expected.get(field)) From af05864e6db336b2897b1138ec25e59caba0a568 Mon Sep 17 00:00:00 2001 From: RitvikSardana <65544983+RitvikSardana@users.noreply.github.com> Date: Tue, 19 Sep 2023 13:09:35 +0530 Subject: [PATCH 33/51] fix: POS opening Issue if Product Bundle is available (#37138) fix: POS opening issue because of Product Bundle --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index ad3a70a862f..7c013b6abef 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -676,7 +676,7 @@ def get_bundle_availability(bundle_item_code, warehouse): item_pos_reserved_qty = get_pos_reserved_qty(item.item_code, warehouse) available_qty = item_bin_qty - item_pos_reserved_qty - max_available_bundles = available_qty / item.stock_qty + max_available_bundles = available_qty / item.qty if bundle_bin_qty > max_available_bundles and frappe.get_value( "Item", item.item_code, "is_stock_item" ): From 29ff0ce286255307ac78652776a8e158b89222ae Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 14:01:47 +0530 Subject: [PATCH 34/51] feat: provision to create RIV from `Stock Ledger Invariant Check` report (backport #37115) (#37147) feat: provision to create RIV from `Stock Ledger Invariant Check` report (#37115) * feat: provision to create RIV from `Stock Ledger Invariant Check` report * fix: `linter` (cherry picked from commit 9c9d0ecb73c581d965f8fafe3347be01147f1a4b) Co-authored-by: s-aga-r --- .../stock_ledger_invariant_check.js | 83 ++++++++++++++----- .../stock_ledger_invariant_check.py | 33 ++++++++ 2 files changed, 96 insertions(+), 20 deletions(-) diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js index 31f389f236e..74849058c3c 100644 --- a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js @@ -3,23 +3,23 @@ /* eslint-disable */ const DIFFERNCE_FIELD_NAMES = [ - "difference_in_qty", - "fifo_qty_diff", - "fifo_value_diff", - "fifo_valuation_diff", - "valuation_diff", - "fifo_difference_diff", - "diff_value_diff" + 'difference_in_qty', + 'fifo_qty_diff', + 'fifo_value_diff', + 'fifo_valuation_diff', + 'valuation_diff', + 'fifo_difference_diff', + 'diff_value_diff' ]; -frappe.query_reports["Stock Ledger Invariant Check"] = { - "filters": [ +frappe.query_reports['Stock Ledger Invariant Check'] = { + 'filters': [ { - "fieldname": "item_code", - "fieldtype": "Link", - "label": "Item", - "mandatory": 1, - "options": "Item", + 'fieldname': 'item_code', + 'fieldtype': 'Link', + 'label': 'Item', + 'mandatory': 1, + 'options': 'Item', get_query: function() { return { filters: {is_stock_item: 1, has_serial_no: 0} @@ -27,18 +27,61 @@ frappe.query_reports["Stock Ledger Invariant Check"] = { } }, { - "fieldname": "warehouse", - "fieldtype": "Link", - "label": "Warehouse", - "mandatory": 1, - "options": "Warehouse", + 'fieldname': 'warehouse', + 'fieldtype': 'Link', + 'label': 'Warehouse', + 'mandatory': 1, + 'options': 'Warehouse', } ], + formatter (value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); if (DIFFERNCE_FIELD_NAMES.includes(column.fieldname) && Math.abs(data[column.fieldname]) > 0.001) { - value = "" + value + ""; + value = '' + value + ''; } return value; }, + + get_datatable_options(options) { + return Object.assign(options, { + checkboxColumn: true, + }); + }, + + onload(report) { + report.page.add_inner_button(__('Create Reposting Entry'), () => { + let message = ` +
+

+ Reposting Entry will change the value of + accounts Stock In Hand, and Stock Expenses + in the Trial Balance report and will also change + the Balance Value in the Stock Balance report. +

+

Are you sure you want to create a Reposting Entry?

+
`; + let indexes = frappe.query_report.datatable.rowmanager.getCheckedRows(); + let selected_rows = indexes.map(i => frappe.query_report.data[i]); + + if (!selected_rows.length) { + frappe.throw(__('Please select a row to create a Reposting Entry')); + } + else if (selected_rows.length > 1) { + frappe.throw(__('Please select only one row to create a Reposting Entry')); + } + else { + frappe.confirm(__(message), () => { + frappe.call({ + method: 'erpnext.stock.report.stock_ledger_invariant_check.stock_ledger_invariant_check.create_reposting_entries', + args: { + rows: selected_rows, + item_code: frappe.query_report.get_filter_values().item_code, + warehouse: frappe.query_report.get_filter_values().warehouse, + } + }); + }); + } + }); + }, }; diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py index ed0e2fc31bd..e11c9bb7891 100644 --- a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py @@ -5,6 +5,7 @@ import json import frappe from frappe import _ +from frappe.utils import get_link_to_form, parse_json SLE_FIELDS = ( "name", @@ -258,3 +259,35 @@ def get_columns(): "label": _("H - J"), }, ] + + +@frappe.whitelist() +def create_reposting_entries(rows, item_code=None, warehouse=None): + if isinstance(rows, str): + rows = parse_json(rows) + + entries = [] + for row in rows: + row = frappe._dict(row) + + try: + doc = frappe.get_doc( + { + "doctype": "Repost Item Valuation", + "based_on": "Item and Warehouse", + "status": "Queued", + "item_code": item_code or row.item_code, + "warehouse": warehouse or row.warehouse, + "posting_date": row.posting_date, + "posting_time": row.posting_time, + "allow_nagative_stock": 1, + } + ).submit() + + entries.append(get_link_to_form("Repost Item Valuation", doc.name)) + except frappe.DuplicateEntryError: + continue + + if entries: + entries = ", ".join(entries) + frappe.msgprint(_("Reposting entries created: {0}").format(entries)) From 480a0ca7a8d97bbaa4de103a722e823ff5aed47f Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Tue, 19 Sep 2023 15:18:35 +0530 Subject: [PATCH 35/51] fix: fetch logic for repay_from_salary in loan_repayment [v14] (#37135) * fix: fetch logic for repay_from_salary in loan_repayment * chore: only fetch repay_from_salary if field exists --- .../doctype/loan_repayment/loan_repayment.js | 9 ++++++++- .../doctype/loan_repayment/loan_repayment.py | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.js b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.js index 82a2d802b80..53581339fae 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.js +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.js @@ -6,7 +6,14 @@ frappe.ui.form.on('Loan Repayment', { // refresh: function(frm) { - // } + // }, + + setup: function(frm) { + if (frappe.meta.has_field("Loan Repayment", "repay_from_salary")) { + frm.add_fetch("against_loan", "repay_from_salary", "repay_from_salary"); + } + }, + onload: function(frm) { frm.set_query('against_loan', function() { return { diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index f0e113b0e7b..0d16c7f0c50 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -80,6 +80,12 @@ class LoanRepayment(AccountsController): if amounts.get("due_date"): self.due_date = amounts.get("due_date") + if hasattr(self, "repay_from_salary") and hasattr(self, "payroll_payable_account"): + if self.repay_from_salary and not self.payroll_payable_account: + frappe.throw(_("Please set Payroll Payable Account in Loan Repayment")) + elif not self.repay_from_salary and self.payroll_payable_account: + self.repay_from_salary = 1 + def check_future_entries(self): future_repayment_date = frappe.db.get_value( "Loan Repayment", From 699ad8080236e9b54c93e71587a027080ce422eb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 19:30:47 +0530 Subject: [PATCH 36/51] fix: incorrect stock ledger entries in DN (backport #36944) (#37066) fix: incorrect stock ledger entries in DN (#36944) (cherry picked from commit 0e83190c190ea122fd6d221704c29556b0394d48) Co-authored-by: rohitwaghchaure --- erpnext/stock/doctype/delivery_note/delivery_note.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index ff0e1b66bf8..11f2cafc35d 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -1252,6 +1252,7 @@ "depends_on": "eval: doc.is_internal_customer", "fieldname": "set_target_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "in_standard_filter": 1, "label": "Set Target Warehouse", "no_copy": 1, @@ -1399,7 +1400,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2023-06-16 14:58:55.066602", + "modified": "2023-09-04 14:15:28.363184", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", From 1cb8c64c94858134c4765f2c21ebbce7394ec795 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 19:44:29 +0530 Subject: [PATCH 37/51] fix: after applying coupon code, field in_words not updated (#37133) fix: after applying coupon code, field in_words not updated (#37133) * fix: after applying coupon code, field in_words not updated * fix: changed the order of the function set_total_in_words (cherry picked from commit 03f0abf6ded44021583041b53355737bf385660b) Co-authored-by: RitvikSardana <65544983+RitvikSardana@users.noreply.github.com> --- 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 df77e5260f5..bd2354e7452 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -168,7 +168,6 @@ class AccountsController(TransactionBase): self.validate_value("base_grand_total", ">=", 0) validate_return(self) - self.set_total_in_words() self.validate_all_documents_schedule() @@ -207,6 +206,8 @@ class AccountsController(TransactionBase): if self.doctype != "Material Request" and not self.ignore_pricing_rule: apply_pricing_rule_on_transaction(self) + self.set_total_in_words() + def before_cancel(self): validate_einvoice_fields(self) From 8236814270f2a74bb5ae46ea7d3818c2ddc7619b Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Tue, 19 Sep 2023 16:27:43 +0200 Subject: [PATCH 38/51] fix: german translation of Sales and Purchase Invoice (#37122) * fix: german translation of Purchase Invoice * fix: german translation of Sales Invoice (cherry picked from commit 84a9000db2f92e81dec825be1df42672305aec5d) # Conflicts: # erpnext/translations/de.csv --- erpnext/translations/de.csv | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 42d313f4db1..6dae398e334 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -1312,7 +1312,7 @@ Invalid GSTIN! A GSTIN must have 15 characters.,Ungültige GSTIN! Eine GSTIN mus Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.,Ungültige GSTIN! Die ersten beiden Ziffern von GSTIN sollten mit der Statusnummer {0} übereinstimmen., Invalid GSTIN! The input you've entered doesn't match the format of GSTIN.,Ungültige GSTIN! Die von Ihnen eingegebene Eingabe stimmt nicht mit dem Format von GSTIN überein., Invalid Posting Time,Ungültige Buchungszeit, -Invalid Purchase Invoice,Ungültige Einkaufsrechnung, +Invalid Purchase Invoice,Ungültige Eingangsrechnung, Invalid attribute {0} {1},Ungültiges Attribut {0} {1}, Invalid quantity specified for item {0}. Quantity should be greater than 0.,Ungültzige Anzahl für Artikel {0} angegeben. Anzahl sollte größer als 0 sein., Invalid reference {0} {1},Ungültige Referenz {0} {1}, @@ -1969,8 +1969,12 @@ Please check Multi Currency option to allow accounts with other currency,"Bitte Please click on 'Generate Schedule',"Bitte auf ""Zeitplan generieren"" klicken", Please click on 'Generate Schedule' to fetch Serial No added for Item {0},"Bitte auf ""Zeitplan generieren"" klicken, um die Seriennummer für Artikel {0} abzurufen", Please click on 'Generate Schedule' to get schedule,"Bitte auf ""Zeitplan generieren"" klicken, um den Zeitplan zu erhalten", +<<<<<<< HEAD Please confirm once you have completed your training,"Bitte bestätigen Sie, sobald Sie Ihre Ausbildung abgeschlossen haben", Please create purchase receipt or purchase invoice for the item {0},Bitte erstellen Sie eine Kaufquittung oder eine Kaufrechnung für den Artikel {0}, +======= +Please create purchase receipt or purchase invoice for the item {0},Bitte erstellen Sie eine Kaufquittung oder eine Eingangsrechnungen für den Artikel {0}, +>>>>>>> 84a9000db2 (fix: german translation of Sales and Purchase Invoice (#37122)) Please define grade for Threshold 0%,Bitte definieren Sie Grade for Threshold 0%, Please enable Applicable on Booking Actual Expenses,Bitte aktivieren Sie Anwendbar bei der Buchung von tatsächlichen Ausgaben, Please enable Applicable on Purchase Order and Applicable on Booking Actual Expenses,Bitte aktivieren Sie Anwendbar bei Bestellung und Anwendbar bei Buchung von tatsächlichen Ausgaben, @@ -4937,7 +4941,7 @@ POS Customer Group,POS Kundengruppe, POS Field,POS-Feld, POS Item Group,POS Artikelgruppe, Company Address,Anschrift des Unternehmens, -Update Stock,Lagerbestand aktualisieren, +Update Stock,Lagerbestand aktualisieren, Ignore Pricing Rule,Preisregel ignorieren, Applicable for Users,Anwendbar für Benutzer, Sales Invoice Payment,Ausgangsrechnung-Zahlungen, @@ -5134,7 +5138,6 @@ ACC-SINV-.YYYY.-,ACC-SINV-.JJJJ.-, Include Payment (POS),(POS) Zahlung einschließen, Offline POS Name,Offline-Verkaufsstellen-Name, Is Return (Credit Note),ist Rücklieferung (Gutschrift), -Return Against Sales Invoice,Zurück zur Kundenrechnung, Update Billed Amount in Sales Order,Aktualisierung des Rechnungsbetrags im Auftrag, Customer PO Details,Auftragsdetails, Customer's Purchase Order,Bestellung des Kunden, @@ -7673,8 +7676,8 @@ Default Company Bank Account,Standard-Bankkonto des Unternehmens, From Lead,Aus Lead, Account Manager,Kundenberater, Accounts Manager,Buchhalter, -Allow Sales Invoice Creation Without Sales Order,Ermöglichen Sie die Erstellung von Kundenrechnungen ohne Auftrag, -Allow Sales Invoice Creation Without Delivery Note,Ermöglichen Sie die Erstellung einer Ausgangsrechnung ohne Lieferschein, +Allow Sales Invoice Creation Without Sales Order,Ermöglichen Sie die Erstellung von Ausgangsrechnungen ohne Auftrag, +Allow Sales Invoice Creation Without Delivery Note,Ermöglichen Sie die Erstellung von Ausgangsrechnungen ohne Lieferschein, Default Price List,Standardpreisliste, Primary Address and Contact,Hauptadresse und -kontakt, "Select, to make the customer searchable with these fields","Wählen Sie, um den Kunden mit diesen Feldern durchsuchbar zu machen", @@ -9598,8 +9601,8 @@ This role is allowed to submit transactions that exceed credit limits,"Diese Rol Show Inclusive Tax in Print,Inklusive Steuern im Druck anzeigen, Only select this if you have set up the Cash Flow Mapper documents,"Wählen Sie diese Option nur, wenn Sie die Cash Flow Mapper-Dokumente eingerichtet haben", Payment Channel,Zahlungskanal, -Is Purchase Order Required for Purchase Invoice & Receipt Creation?,Ist für die Erstellung von Kaufrechnungen und Quittungen eine Bestellung erforderlich?, -Is Purchase Receipt Required for Purchase Invoice Creation?,Ist für die Erstellung der Kaufrechnung ein Kaufbeleg erforderlich?, +Is Purchase Order Required for Purchase Invoice & Receipt Creation?,Ist für die Erstellung von Eingangsrechnungen und Quittungen eine Bestellung erforderlich?, +Is Purchase Receipt Required for Purchase Invoice Creation?,Ist für die Erstellung der Eingangsrechnungen ein Kaufbeleg erforderlich?, Maintain Same Rate Throughout the Purchase Cycle,Behalten Sie den gleichen Preis während des gesamten Kaufzyklus bei, Allow Item To Be Added Multiple Times in a Transaction,"Zulassen, dass ein Element in einer Transaktion mehrmals hinzugefügt wird", Suppliers,Lieferanten, @@ -9657,8 +9660,8 @@ Purchase Order already created for all Sales Order items,Bestellung bereits für Select Items,Gegenstände auswählen, Against Default Supplier,Gegen Standardlieferanten, Auto close Opportunity after the no. of days mentioned above,Gelegenheit zum automatischen Schließen nach der Nr. der oben genannten Tage, -Is Sales Order Required for Sales Invoice & Delivery Note Creation?,Ist ein Auftrag für die Erstellung von Kundenrechnungen und Lieferscheinen erforderlich?, -Is Delivery Note Required for Sales Invoice Creation?,Ist für die Erstellung der Ausgangsrechnung ein Lieferschein erforderlich?, +Is Sales Order Required for Sales Invoice & Delivery Note Creation?,Ist ein Auftrag für die Erstellung von Ausgangsrechnungen und Lieferscheinen erforderlich?, +Is Delivery Note Required for Sales Invoice Creation?,Ist ein Lieferschein für die Erstellung von Ausgangsrechnungen erforderlich?, How often should Project and Company be updated based on Sales Transactions?,Wie oft sollten Projekt und Unternehmen basierend auf Verkaufstransaktionen aktualisiert werden?, Allow User to Edit Price List Rate in Transactions,Benutzer darf Preisliste in Transaktionen bearbeiten, Allow Item to Be Added Multiple Times in a Transaction,"Zulassen, dass ein Element in einer Transaktion mehrmals hinzugefügt wird", @@ -9804,7 +9807,7 @@ or it is not the default inventory account,oder es ist nicht das Standard-Invent Expense Head Changed,Ausgabenkopf geändert, because expense is booked against this account in Purchase Receipt {},weil die Kosten für dieses Konto im Kaufbeleg {} gebucht werden, as no Purchase Receipt is created against Item {}. ,da für Artikel {} kein Kaufbeleg erstellt wird., -This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice,"Dies erfolgt zur Abrechnung von Fällen, in denen der Kaufbeleg nach der Kaufrechnung erstellt wird", +This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice,"Dies erfolgt zur Abrechnung von Fällen, in denen der Kaufbeleg nach der Eingangsrechnung erstellt wird", Purchase Order Required for item {},Bestellung erforderlich für Artikel {}, To submit the invoice without purchase order please set {} ,"Um die Rechnung ohne Bestellung einzureichen, setzen Sie bitte {}", as {} in {},wie in {}, From 8c14dbd7ac4d60c3f0df95252510eaef08007790 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Tue, 19 Sep 2023 16:56:05 +0200 Subject: [PATCH 39/51] chore: translation manual backport of #35697 (#37053) chore: translation manual backport of #35697 --- erpnext/translations/fr.csv | 149 ++++++++++++++++++------------------ 1 file changed, 74 insertions(+), 75 deletions(-) diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index eb9076b4062..0865e2f7a7d 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -115,7 +115,7 @@ Add Customers,Ajouter des clients, Add Employees,Ajouter des employés, Add Item,Ajouter un Article, Add Items,Ajouter des articles, -Add Leads,Créer des Prospects, +Add Leads,Créer des Leads, Add Multiple Tasks,Ajouter plusieurs tâches, Add Row,Ajouter une Ligne, Add Sales Partners,Ajouter des partenaires commerciaux, @@ -658,8 +658,8 @@ Create Invoice,Créer une facture, Create Invoices,Créer des factures, Create Job Card,Créer une carte de travail, Create Journal Entry,Créer une entrée de journal, -Create Lead,Créer un Prospect, -Create Leads,Créer des Prospects, +Create Lead,Créer un Lead, +Create Leads,Créer des Lead, Create Maintenance Visit,Créer une visite de maintenance, Create Material Request,Créer une demande de matériel, Create Multiple,Créer plusieurs, @@ -951,7 +951,7 @@ End time cannot be before start time,L'heure de fin ne peut pas être avant l'he Ends On date cannot be before Next Contact Date.,La date de fin ne peut pas être avant la prochaine date de contact, Energy,Énergie, Engineer,Ingénieur, -Enough Parts to Build,Pièces Suffisantes pour Construire +Enough Parts to Build,Pièces Suffisantes pour Construire, Enroll,Inscrire, Enrolling student,Inscrire un étudiant, Enrolling students,Inscription des étudiants, @@ -1426,13 +1426,12 @@ Last Purchase Price,Dernier prix d'achat, Last Purchase Rate,Dernier Prix d'Achat, Latest,Dernier, Latest price updated in all BOMs,Prix les plus récents mis à jour dans toutes les nomenclatures, -Lead,Prospect, -Lead Count,Nombre de Prospects, +Lead Count,Nombre de Lead, Lead Owner,Responsable du Prospect, -Lead Owner cannot be same as the Lead,Le Responsable du Prospect ne peut pas être identique au Prospect, +Lead Owner cannot be same as the Lead,Le Responsable du Prospect ne peut pas être identique au Lead, Lead Time Days,Jours de Délai, Lead to Quotation,Du Prospect au Devis, -"Leads help you get business, add all your contacts and more as your leads","Les prospects vous aident à obtenir des contrats, ajoutez tous vos contacts et plus dans votre liste de prospects", +"Leads help you get business, add all your contacts and more as your leads","Les lead vous aident à obtenir des contrats, ajoutez tous vos contacts et plus dans votre liste de lead", Learn,Apprendre, Leave Approval Notification,Notification d'approbation de congés, Leave Blocked,Laisser Verrouillé, @@ -1596,7 +1595,7 @@ Middle Name,Deuxième Nom, Middle Name (Optional),Deuxième Prénom (Optionnel), Min Amt can not be greater than Max Amt,Min Amt ne peut pas être supérieur à Max Amt, Min Qty can not be greater than Max Qty,Qté Min ne peut pas être supérieure à Qté Max, -Minimum Lead Age (Days),Âge Minimum du Prospect (Jours), +Minimum Lead Age (Days),Âge Minimum du lead (Jours), Miscellaneous Expenses,Charges Diverses, Missing Currency Exchange Rates for {0},Taux de Change Manquant pour {0}, Missing email template for dispatch. Please set one in Delivery Settings.,Modèle de courrier électronique manquant pour l'envoi. Veuillez en définir un dans les paramètres de livraison., @@ -1676,7 +1675,7 @@ New {0} pricing rules are created,De nouvelles règles de tarification {0} sont Newsletters,Newsletters, Newspaper Publishers,Éditeurs de journaux, Next,Suivant, -Next Contact By cannot be same as the Lead Email Address,Prochain Contact Par ne peut être identique à l’Adresse Email du Prospect, +Next Contact By cannot be same as the Lead Email Address,Prochain Contact Par ne peut être identique à l’Adresse Email du Lead, Next Contact Date cannot be in the past,La Date de Prochain Contact ne peut pas être dans le passé, Next Steps,Prochaines étapes, No Action,Pas d'action, @@ -1808,9 +1807,9 @@ Operation Time must be greater than 0 for Operation {0},Temps de l'Opération do Operations,Opérations, Operations cannot be left blank,Les opérations ne peuvent pas être laissées vides, Opp Count,Compte d'Opportunités, -Opp/Lead %,Opp / Prospect %, +Opp/Lead %,Opp / Lead %, Opportunities,Opportunités, -Opportunities by lead source,Opportunités par source de plomb, +Opportunities by lead source,Opportunités par source de lead, Opportunity,Opportunité, Opportunity Amount,Montant de l'opportunité, Optional Holiday List not set for leave period {0},Une liste de vacances facultative n'est pas définie pour la période de congé {0}, @@ -2007,7 +2006,7 @@ Please mention Basic and HRA component in Company,Veuillez mentionner les compos Please mention Round Off Account in Company,Veuillez indiquer le Compte d’Arrondi de la Société, Please mention Round Off Cost Center in Company,Veuillez indiquer le Centre de Coûts d’Arrondi de la Société, Please mention no of visits required,Veuillez indiquer le nb de visites requises, -Please mention the Lead Name in Lead {0},Veuillez mentionner le nom du Prospect dans le Prospect {0}, +Please mention the Lead Name in Lead {0},Veuillez mentionner le nom du Lead dans le Lead {0}, Please pull items from Delivery Note,Veuillez récupérer les articles des Bons de Livraison, Please register the SIREN number in the company information file,Veuillez enregistrer le numéro SIREN dans la fiche d'information de la société, Please remove this Invoice {0} from C-Form {1},Veuillez retirez cette Facture {0} du C-Form {1}, @@ -2277,7 +2276,7 @@ Queued for replacing the BOM. It may take a few minutes.,En file d'attente pour Queued for updating latest price in all Bill of Materials. It may take a few minutes.,Mise à jour des prix les plus récents dans toutes les nomenclatures en file d'attente. Cela peut prendre quelques minutes., Quick Journal Entry,Écriture Rapide dans le Journal, Quot Count,Compte de Devis, -Quot/Lead %,Devis / Prospects %, +Quot/Lead %,Devis / Lead %, Quotation,Devis, Quotation {0} is cancelled,Devis {0} est annulée, Quotation {0} not of type {1},Le devis {0} n'est pas du type {1}, @@ -2285,7 +2284,7 @@ Quotations,Devis, "Quotations are proposals, bids you have sent to your customers","Les devis sont des propositions, offres que vous avez envoyées à vos clients", Quotations received from Suppliers.,Devis reçus des Fournisseurs., Quotations: ,Devis :, -Quotes to Leads or Customers.,Devis de Prospects ou Clients., +Quotes to Leads or Customers.,Devis de Lead ou Clients., RFQs are not allowed for {0} due to a scorecard standing of {1},Les Appels d'Offres ne sont pas autorisés pour {0} en raison d'une note de {1} sur la fiche d'évaluation, Range,Plage, Rate,Prix, @@ -2888,7 +2887,7 @@ Supplies made to UIN holders,Fournitures faites aux titulaires de l'UIN, Supplies made to Unregistered Persons,Fournitures faites à des personnes non inscrites, Suppliies made to Composition Taxable Persons,Suppleies à des personnes assujetties à la composition, Supply Type,Type d'approvisionnement, -Support,"Assistance/Support", +Support,Assistance/Support, Support Analytics,Analyse de l'assistance, Support Settings,Paramètres du module Assistance, Support Tickets,Ticket d'assistance, @@ -3037,7 +3036,7 @@ To Date must be greater than From Date,La date de fin doit être supérieure à To Date should be within the Fiscal Year. Assuming To Date = {0},La Date Finale doit être dans l'exercice. En supposant Date Finale = {0}, To Datetime,À la Date, To Deliver,À Livrer, -{} To Deliver,{} à livrer +{} To Deliver,{} à livrer, To Deliver and Bill,À Livrer et Facturer, To Fiscal Year,À l'année fiscale, To GSTIN,GSTIN (Destination), @@ -3122,7 +3121,7 @@ Total(Amt),Total (Mnt), Total(Qty),Total (Qté), Traceability,Traçabilité, Traceback,Retraçage, -Track Leads by Lead Source.,Suivre les prospects par sources, +Track Leads by Lead Source.,Suivre les leads par sources, Training,Formation, Training Event,Événement de formation, Training Events,Événements de formation, @@ -3243,8 +3242,8 @@ View Chart of Accounts,Voir le plan comptable, View Fees Records,Voir les honoraires, View Form,Voir le formulaire, View Lab Tests,Afficher les tests de laboratoire, -View Leads,Voir Prospects, -View Ledger,Voir le Livre, +View Leads,Voir Lead, +View Ledger,Voir le Journal, View Now,Voir maintenant, View a list of all the help videos,Afficher la liste de toutes les vidéos d'aide, View in Cart,Voir Panier, @@ -3677,7 +3676,7 @@ Couldn't Set Service Level Agreement {0}.,Impossible de définir le contrat de s Country,Pays, Country Code in File does not match with country code set up in the system,Le code de pays dans le fichier ne correspond pas au code de pays configuré dans le système, Create New Contact,Créer un nouveau contact, -Create New Lead,Créer une nouvelle piste, +Create New Lead,Créer une nouvelle lead, Create Pick List,Créer une liste de choix, Create Quality Inspection for Item {0},Créer un contrôle qualité pour l'article {0}, Creating Accounts...,Création de comptes ..., @@ -3784,7 +3783,7 @@ Group Warehouses cannot be used in transactions. Please change the value of {0}, Help,Aidez-moi, Help Article,Article d’Aide, "Helps you keep tracks of Contracts based on Supplier, Customer and Employee","Vous aide à garder une trace des contrats en fonction du fournisseur, client et employé", -Helps you manage appointments with your leads,Vous aide à gérer les rendez-vous avec vos prospects, +Helps you manage appointments with your leads,Vous aide à gérer les rendez-vous avec vos leads, Home,Accueil, IBAN is not valid,IBAN n'est pas valide, Import Data from CSV / Excel files.,Importer des données à partir de fichiers CSV / Excel, @@ -3880,7 +3879,7 @@ Only expired allocation can be cancelled,Seule l'allocation expirée peut être Only users with the {0} role can create backdated leave applications,Seuls les utilisateurs avec le rôle {0} peuvent créer des demandes de congé antidatées, Open,Ouvert, Open Contact,Contact ouvert, -Open Lead,Ouvrir le Prospect, +Open Lead,Ouvrir le Lead, Opening and Closing,Ouverture et fermeture, Operating Cost as per Work Order / BOM,Coût d'exploitation selon l'ordre de fabrication / nomenclature, Order Amount,Montant de la commande, @@ -3926,7 +3925,7 @@ Please select another payment method. Stripe does not support transactions in cu Please select the customer.,S'il vous plaît sélectionner le client., Please set a Supplier against the Items to be considered in the Purchase Order.,Veuillez définir un fournisseur par rapport aux articles à prendre en compte dans la Commande d'Achat., Please set account heads in GST Settings for Compnay {0},Définissez les en-têtes de compte dans les paramètres de la TPS pour le service {0}., -Please set an email id for the Lead {0},Veuillez définir un identifiant de messagerie pour le prospect {0}., +Please set an email id for the Lead {0},Veuillez définir un identifiant de messagerie pour le lead {0}., Please set default UOM in Stock Settings,Veuillez définir l'UdM par défaut dans les paramètres de stock, Please set filter based on Item or Warehouse due to a large amount of entries.,Veuillez définir le filtre en fonction de l'article ou de l'entrepôt en raison d'une grande quantité d'entrées., Please set up the Campaign Schedule in the Campaign {0},Configurez le calendrier de la campagne dans la campagne {0}., @@ -4943,8 +4942,8 @@ Min Qty,Qté Min, Max Qty,Qté Max, Min Amt,Montant Min, Max Amt,Montant Max, -"If rate is zero them item will be treated as ""Free Item""",Si le prix est à 0 alors l'article sera traité comme article gratuit -Is Recursive,Est récursif +"If rate is zero them item will be treated as ""Free Item""",Si le prix est à 0 alors l'article sera traité comme article gratuit, +Is Recursive,Est récursif, "Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on","La remise sera appliquée séquentiellement telque : acheter 1 => recupérer 1, acheter 2 => recupérer 2, acheter 3 => recupérer 3, etc..." Period Settings,Paramètres de période, Margin,Marge, @@ -5600,7 +5599,7 @@ Call Log,Journal d'appel, Received By,Reçu par, Caller Information,Informations sur l'appelant, Contact Name,Nom du Contact, -Lead Name,Nom du Prospect, +Lead Name,Nom du Lead, Ringing,Sonnerie, Missed,Manqué, Call Duration in seconds,Durée d'appel en secondes, @@ -5668,7 +5667,7 @@ Fulfilment Terms and Conditions,Termes et conditions d'exécution, Contract Template Fulfilment Terms,Conditions d'exécution du modèle de contrat, Email Campaign,Campagne Email, Email Campaign For ,Campagne d'email pour, -Lead is an Organization,Le prospect est une organisation, +Lead is an Organization,Le Lead est une organisation, CRM-LEAD-.YYYY.-,CRM-LEAD-.YYYY.-, Person Name,Nom de la Personne, Lost Quotation,Devis Perdu, @@ -5683,7 +5682,7 @@ Next Contact Date,Date du Prochain Contact, Ends On,Se termine le, Address & Contact,Adresse & Contact, Mobile No.,N° Mobile., -Lead Type,Type de Prospect, +Lead Type,Type de Lead, Channel Partner,Partenaire de Canal, Consultant,Consultant, Market Segment,Part de Marché, @@ -5706,7 +5705,7 @@ Opportunity Lost Reason,Raison perdue, Potential Sales Deal,Ventes Potentielles, CRM-OPP-.YYYY.-,CRM-OPP-YYYY.-, Opportunity From,Opportunité De, -Customer / Lead Name,Nom du Client / Prospect, +Customer / Lead Name,Nom du Client / Lead, Opportunity Type,Type d'Opportunité, Converted By,Converti par, Sales Stage,Stade de vente, @@ -5716,7 +5715,7 @@ To Discuss,À Discuter, With Items,Avec Articles, Probability (%),Probabilité (%), Contact Info,Information du Contact, -Customer / Lead Address,Adresse du Client / Prospect, +Customer / Lead Address,Adresse du Lead / Prospect, Contact Mobile No,N° de Portable du Contact, Enter name of campaign if source of enquiry is campaign,Entrez le nom de la campagne si la source de l'enquête est une campagne, Opportunity Date,Date d'Opportunité, @@ -7240,7 +7239,7 @@ Replace,Remplacer, Update latest price in all BOMs,Mettre à jour le prix le plus récent dans toutes les nomenclatures, BOM Website Item,Article de nomenclature du Site Internet, BOM Website Operation,Opération de nomenclature du Site Internet, -Operation Time,Durée de l'Opération +Operation Time,Durée de l'Opération, PO-JOB.#####,PO-JOB. #####, Timing Detail,Détail du timing, Time Logs,Time Logs, @@ -7645,7 +7644,7 @@ Campaign Schedules,Horaires de campagne, Buyer of Goods and Services.,Acheteur des Biens et Services., CUST-.YYYY.-,CUST-.YYYY.-, Default Company Bank Account,Compte bancaire d'entreprise par défaut, -From Lead,Du Prospect, +From Lead,Du Lead, Account Manager,Gestionnaire de compte, Allow Sales Invoice Creation Without Sales Order,Autoriser la création de factures de vente sans commande client, Allow Sales Invoice Creation Without Delivery Note,Autoriser la création de factures de vente sans bon de livraison, @@ -7672,7 +7671,7 @@ Installation Date,Date d'Installation, Installation Time,Temps d'Installation, Installation Note Item,Article Remarque d'Installation, Installed Qty,Qté Installée, -Lead Source,Source du Prospect, +Lead Source,Source du Lead, Period Start Date,Date de début de la période, Period End Date,Date de fin de la période, Cashier,Caissier, @@ -8517,8 +8516,8 @@ Item-wise Sales Register,Registre des Ventes par Article, Items To Be Requested,Articles À Demander, Reserved,Réservé, Itemwise Recommended Reorder Level,Renouvellement Recommandé par Article, -Lead Details,Détails du Prospect, -Lead Owner Efficiency,Efficacité des Responsables des Prospects, +Lead Details,Détails du Lead, +Lead Owner Efficiency,Efficacité des Responsables des Leads, Loan Repayment and Closure,Remboursement et clôture de prêts, Loan Security Status,État de la sécurité du prêt, Lost Opportunity,Occasion perdue, @@ -9207,7 +9206,7 @@ Time Required (In Mins),Temps requis (en minutes), From Posting Date,À partir de la date de publication, To Posting Date,À la date de publication, No records found,Aucun enregistrement trouvé, -Customer/Lead Name,Nom du client / prospect, +Customer/Lead Name,Nom du client / lead, Unmarked Days,Jours non marqués, Jan,Jan, Feb,fév, @@ -9471,7 +9470,7 @@ Row {0}: Loan Security {1} added multiple times,Ligne {0}: Garantie de prêt {1} Row #{0}: Child Item should not be a Product Bundle. Please remove Item {1} and Save,Ligne n ° {0}: l'élément enfant ne doit pas être un ensemble de produits. Veuillez supprimer l'élément {1} et enregistrer, Credit limit reached for customer {0},Limite de crédit atteinte pour le client {0}, Could not auto create Customer due to the following missing mandatory field(s):,Impossible de créer automatiquement le client en raison du ou des champs obligatoires manquants suivants:, -Please create Customer from Lead {0}.,Veuillez créer un client à partir du prospect {0}., +Please create Customer from Lead {0}.,Veuillez créer un client à partir du lead {0}., Mandatory Missing,Obligatoire manquant, Please set Payroll based on in Payroll settings,Veuillez définir la paie en fonction des paramètres de paie, Additional Salary: {0} already exist for Salary Component: {1} for period {2} and {3},Salaire supplémentaire: {0} existe déjà pour le composant de salaire: {1} pour la période {2} et {3}, @@ -9834,29 +9833,29 @@ Enable European Access,Activer l'accès européen, Creating Purchase Order ...,Création d'une commande d'achat ..., "Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Sélectionnez un fournisseur parmi les fournisseurs par défaut des articles ci-dessous. Lors de la sélection, une commande d'achat sera effectué contre des articles appartenant uniquement au fournisseur sélectionné.", Row #{}: You must select {} serial numbers for item {}.,Ligne n ° {}: vous devez sélectionner {} numéros de série pour l'article {}., -Update Rate as per Last Purchase,Mettre à jour avec les derniers prix d'achats -Company Shipping Address,Adresse d'expédition -Shipping Address Details,Détail d'adresse d'expédition -Company Billing Address,Adresse de la société de facturation +Update Rate as per Last Purchase,Mettre à jour avec les derniers prix d'achats, +Company Shipping Address,Adresse d'expédition, +Shipping Address Details,Détail d'adresse d'expédition, +Company Billing Address,Adresse de la société de facturation, Supplier Address Details, -Bank Reconciliation Tool,Outil de réconcialiation d'écritures bancaires -Supplier Contact,Contact fournisseur -Subcontracting,Sous traitance -Order Status,Statut de la commande -Build,Personnalisations avancées -Dispatch Address Name,Adresse de livraison intermédiaire -Amount Eligible for Commission,Montant éligible à comission -Grant Commission,Eligible aux commissions -Stock Transactions Settings, Paramétre des transactions -Role Allowed to Over Deliver/Receive, Rôle autorisé à dépasser cette limite -Users with this role are allowed to over deliver/receive against orders above the allowance percentage,Rôle Utilisateur qui sont autorisé à livrée/commandé au-delà de la limite -Over Transfer Allowance,Autorisation de limite de transfert -Quality Inspection Settings,Paramétre de l'inspection qualité -Action If Quality Inspection Is Rejected,Action si l'inspection qualité est rejetée -Disable Serial No And Batch Selector,Désactiver le sélecteur de numéro de lot/série -Is Rate Adjustment Entry (Debit Note),Est un justement du prix de la note de débit -Issue a debit note with 0 qty against an existing Sales Invoice,Creer une note de débit avec une quatité à O pour la facture -Control Historical Stock Transactions,Controle de l'historique des stransaction de stock +Bank Reconciliation Tool,Outil de réconcialiation d'écritures bancaires, +Supplier Contact,Contact fournisseur, +Subcontracting,Sous traitance, +Order Status,Statut de la commande, +Build,Personnalisations avancées, +Dispatch Address Name,Adresse de livraison intermédiaire, +Amount Eligible for Commission,Montant éligible à comission, +Grant Commission,Eligible aux commissions, +Stock Transactions Settings, Paramétre des transactions, +Role Allowed to Over Deliver/Receive, Rôle autorisé à dépasser cette limite, +Users with this role are allowed to over deliver/receive against orders above the allowance percentage,Rôle Utilisateur qui sont autorisé à livrée/commandé au-delà de la limite, +Over Transfer Allowance,Autorisation de limite de transfert, +Quality Inspection Settings,Paramétre de l'inspection qualité, +Action If Quality Inspection Is Rejected,Action si l'inspection qualité est rejetée, +Disable Serial No And Batch Selector,Désactiver le sélecteur de numéro de lot/série, +Is Rate Adjustment Entry (Debit Note),Est un justement du prix de la note de débit, +Issue a debit note with 0 qty against an existing Sales Invoice,Creer une note de débit avec une quatité à O pour la facture, +Control Historical Stock Transactions,Controle de l'historique des stransaction de stock, No stock transactions can be created or modified before this date.,Aucune transaction ne peux être créée ou modifié avant cette date. Stock transactions that are older than the mentioned days cannot be modified.,Les transactions de stock plus ancienne que le nombre de jours ci-dessus ne peuvent être modifiées, Role Allowed to Create/Edit Back-dated Transactions,Rôle autorisé à créer et modifier des transactions anti-datée, @@ -9867,29 +9866,29 @@ Show Barcode Field in Stock Transactions,Afficher le champ Code Barre dans les t Convert Item Description to Clean HTML in Transactions,Convertir les descriptions d'articles en HTML valide lors des transactions, Have Default Naming Series for Batch ID?,Masque de numérotation par défaut pour les Lots ou Séries, "The percentage you are allowed to transfer more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed transfer 110 units","Le pourcentage de quantité que vous pourrez réceptionner en plus de la quantité commandée. Par exemple, vous avez commandé 100 unités, votre pourcentage de dépassement est de 10%, vous pourrez réceptionner 110 unités" -Allowed Items,Articles autorisés -Party Specific Item,Restriction d'article disponible -Restrict Items Based On,Type de critére de restriction -Based On Value,critére de restriction +Allowed Items,Articles autorisés, +Party Specific Item,Restriction d'article disponible, +Restrict Items Based On,Type de critére de restriction, +Based On Value,critére de restriction, Unit of Measure (UOM),Unité de mesure (UdM), Unit Of Measure (UOM),Unité de mesure (UdM), -CRM Settings,Paramètres CRM -Do Not Explode,Ne pas décomposer -Quick Access, Accés rapides -{} Available,{} Disponible.s -{} Pending,{} En attente.s -{} To Bill,{} à facturer -{} To Receive,{} A recevoir +CRM Settings,Paramètres CRM, +Do Not Explode,Ne pas décomposer, +Quick Access, Accés rapides, +{} Available,{} Disponible.s, +{} Pending,{} En attente.s, +{} To Bill,{} à facturer, +{} To Receive,{} A recevoir, {} Active,{} Actif.ve(s) {} Open,{} Ouvert.e(s) -Incorrect Data Report,Rapport de données incohérentes -Incorrect Serial No Valuation,Valorisation inccorecte par Num. Série / Lots -Incorrect Balance Qty After Transaction,Equilibre des quantités aprés une transaction +Incorrect Data Report,Rapport de données incohérentes, +Incorrect Serial No Valuation,Valorisation inccorecte par Num. Série / Lots, +Incorrect Balance Qty After Transaction,Equilibre des quantités aprés une transaction, Interview Type,Type d'entretien Interview Round,Cycle d'entretien Interview,Entretien Interview Feedback,Retour d'entretien -Journal Energy Point,Historique des points d'énergies +Journal Energy Point,Historique des points d'énergies, Billing Address Details,Adresse de facturation (détails) Supplier Address Details,Adresse Fournisseur (détails) Retail,Commerce, From c5df164e1cf3a3f86b3c69cf9c5db3211b2af935 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 20:41:53 +0530 Subject: [PATCH 40/51] chore: translations dutch (#37042) chore: translations dutch (#37042) update: translations dutch (cherry picked from commit c35dea7177649adcf068ea8f4d594012bbe35a0f) Co-authored-by: RJPvT <48353029+RJPvT@users.noreply.github.com> --- erpnext/translations/nl.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/translations/nl.csv b/erpnext/translations/nl.csv index 1efc5000b77..92671115bfc 100644 --- a/erpnext/translations/nl.csv +++ b/erpnext/translations/nl.csv @@ -2364,7 +2364,7 @@ Report Type is mandatory,Rapport type is verplicht, Reports,rapporten, Reqd By Date,Benodigd op datum, Reqd Qty,Gewenste hoeveelheid, -Request for Quotation,Offerte, +Request for Quotation,Offerte-verzoek, Request for Quotations,Verzoek om offertes, Request for Raw Materials,Verzoek om grondstoffen, Request for purchase.,Inkoopaanvraag, From 56657b6122c056a8ba091b41fd1b6e94447f34da Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 18:06:30 +0530 Subject: [PATCH 41/51] fix: allow to select parent warehouse in the website item (backport #37047) (#37173) fix: allow to select parent warehouse in the website item (#37047) (cherry picked from commit e6199dc80227023d70c7b7dabb7b0239345f9be5) Co-authored-by: rohitwaghchaure --- .../doctype/website_item/test_website_item.py | 6 +-- .../doctype/website_item/website_item.js | 6 --- .../doctype/website_item/website_item.json | 4 +- .../e_commerce/product_data_engine/query.py | 10 ++--- erpnext/e_commerce/shopping_cart/cart.py | 14 ++++++- erpnext/e_commerce/variant_selector/utils.py | 19 +++++---- erpnext/stock/get_item_details.py | 3 ++ .../generators/item/item_add_to_cart.html | 2 +- erpnext/templates/pages/wishlist.py | 16 ++++++-- erpnext/utilities/product.py | 39 ++++++++++++------- 10 files changed, 75 insertions(+), 44 deletions(-) diff --git a/erpnext/e_commerce/doctype/website_item/test_website_item.py b/erpnext/e_commerce/doctype/website_item/test_website_item.py index 019a5f9ee4f..8eebfdb83af 100644 --- a/erpnext/e_commerce/doctype/website_item/test_website_item.py +++ b/erpnext/e_commerce/doctype/website_item/test_website_item.py @@ -312,7 +312,7 @@ class TestWebsiteItem(unittest.TestCase): # check if stock details are fetched and item not in stock with warehouse set data = get_product_info_for_website(item_code, skip_quotation_creation=True) self.assertFalse(bool(data.product_info["in_stock"])) - self.assertEqual(data.product_info["stock_qty"][0][0], 0) + self.assertEqual(data.product_info["stock_qty"], 0) # disable show stock availability setup_e_commerce_settings({"show_stock_availability": 0}) @@ -355,7 +355,7 @@ class TestWebsiteItem(unittest.TestCase): # check if stock details are fetched and item is in stock with warehouse set data = get_product_info_for_website(item_code, skip_quotation_creation=True) self.assertTrue(bool(data.product_info["in_stock"])) - self.assertEqual(data.product_info["stock_qty"][0][0], 2) + self.assertEqual(data.product_info["stock_qty"], 2) # unset warehouse frappe.db.set_value("Website Item", {"item_code": item_code}, "website_warehouse", "") @@ -364,7 +364,7 @@ class TestWebsiteItem(unittest.TestCase): # (even though it has stock in some warehouse) data = get_product_info_for_website(item_code, skip_quotation_creation=True) self.assertFalse(bool(data.product_info["in_stock"])) - self.assertFalse(bool(data.product_info["stock_qty"])) + self.assertFalse(data.product_info["stock_qty"]) # disable show stock availability setup_e_commerce_settings({"show_stock_availability": 0}) diff --git a/erpnext/e_commerce/doctype/website_item/website_item.js b/erpnext/e_commerce/doctype/website_item/website_item.js index 7b7193e833a..b6595cce8a9 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.js +++ b/erpnext/e_commerce/doctype/website_item/website_item.js @@ -5,12 +5,6 @@ frappe.ui.form.on('Website Item', { onload: (frm) => { // should never check Private frm.fields_dict["website_image"].df.is_private = 0; - - frm.set_query("website_warehouse", () => { - return { - filters: {"is_group": 0} - }; - }); }, refresh: (frm) => { diff --git a/erpnext/e_commerce/doctype/website_item/website_item.json b/erpnext/e_commerce/doctype/website_item/website_item.json index 6556eabf4ab..6f551a0b42d 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.json +++ b/erpnext/e_commerce/doctype/website_item/website_item.json @@ -135,7 +135,7 @@ "fieldtype": "Column Break" }, { - "description": "Show Stock availability based on this warehouse.", + "description": "Show Stock availability based on this warehouse. If the parent warehouse is selected, then the system will display the consolidated available quantity of all child warehouses.", "fieldname": "website_warehouse", "fieldtype": "Link", "ignore_user_permissions": 1, @@ -348,7 +348,7 @@ "index_web_pages_for_search": 1, "links": [], "make_attachments_public": 1, - "modified": "2022-09-30 04:01:52.090732", + "modified": "2023-09-12 14:19:22.822689", "modified_by": "Administrator", "module": "E-commerce", "name": "Website Item", diff --git a/erpnext/e_commerce/product_data_engine/query.py b/erpnext/e_commerce/product_data_engine/query.py index e6a595a0344..975f87608a6 100644 --- a/erpnext/e_commerce/product_data_engine/query.py +++ b/erpnext/e_commerce/product_data_engine/query.py @@ -259,6 +259,10 @@ class ProductQuery: ) def get_stock_availability(self, item): + from erpnext.templates.pages.wishlist import ( + get_stock_availability as get_stock_availability_from_template, + ) + """Modify item object and add stock details.""" item.in_stock = False warehouse = item.get("website_warehouse") @@ -274,11 +278,7 @@ class ProductQuery: else: item.in_stock = True elif warehouse: - # stock item and has warehouse - actual_qty = frappe.db.get_value( - "Bin", {"item_code": item.item_code, "warehouse": item.get("website_warehouse")}, "actual_qty" - ) - item.in_stock = bool(flt(actual_qty)) + item.in_stock = get_stock_availability_from_template(item.item_code, warehouse) def get_cart_items(self): customer = get_customer(silent=True) diff --git a/erpnext/e_commerce/shopping_cart/cart.py b/erpnext/e_commerce/shopping_cart/cart.py index 57746a234bc..030b439ae4b 100644 --- a/erpnext/e_commerce/shopping_cart/cart.py +++ b/erpnext/e_commerce/shopping_cart/cart.py @@ -111,8 +111,8 @@ def place_order(): item_stock = get_web_item_qty_in_stock(item.item_code, "website_warehouse") if not cint(item_stock.in_stock): throw(_("{0} Not in Stock").format(item.item_code)) - if item.qty > item_stock.stock_qty[0][0]: - throw(_("Only {0} in Stock for item {1}").format(item_stock.stock_qty[0][0], item.item_code)) + if item.qty > item_stock.stock_qty: + throw(_("Only {0} in Stock for item {1}").format(item_stock.stock_qty, item.item_code)) sales_order.flags.ignore_permissions = True sales_order.insert() @@ -150,6 +150,10 @@ def update_cart(item_code, qty, additional_notes=None, with_items=False): empty_card = True else: + warehouse = frappe.get_cached_value( + "Website Item", {"item_code": item_code}, "website_warehouse" + ) + quotation_items = quotation.get("items", {"item_code": item_code}) if not quotation_items: quotation.append( @@ -159,11 +163,13 @@ def update_cart(item_code, qty, additional_notes=None, with_items=False): "item_code": item_code, "qty": qty, "additional_notes": additional_notes, + "warehouse": warehouse, }, ) else: quotation_items[0].qty = qty quotation_items[0].additional_notes = additional_notes + quotation_items[0].warehouse = warehouse apply_cart_settings(quotation=quotation) @@ -322,6 +328,10 @@ def decorate_quotation_doc(doc): fields = fields[2:] d.update(frappe.db.get_value("Website Item", {"item_code": item_code}, fields, as_dict=True)) + website_warehouse = frappe.get_cached_value( + "Website Item", {"item_code": item_code}, "website_warehouse" + ) + d.warehouse = website_warehouse return doc diff --git a/erpnext/e_commerce/variant_selector/utils.py b/erpnext/e_commerce/variant_selector/utils.py index 4466c457436..88356f5e909 100644 --- a/erpnext/e_commerce/variant_selector/utils.py +++ b/erpnext/e_commerce/variant_selector/utils.py @@ -104,6 +104,8 @@ def get_attributes_and_values(item_code): @frappe.whitelist(allow_guest=True) def get_next_attribute_and_values(item_code, selected_attributes): + from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses + """Find the count of Items that match the selected attributes. Also, find the attribute values that are not applicable for further searching. If less than equal to 10 items are found, return item_codes of those items. @@ -168,7 +170,7 @@ def get_next_attribute_and_values(item_code, selected_attributes): product_info = None product_id = "" - website_warehouse = "" + warehouse = "" if exact_match or filtered_items: if exact_match and len(exact_match) == 1: product_id = exact_match[0] @@ -176,16 +178,19 @@ def get_next_attribute_and_values(item_code, selected_attributes): product_id = list(filtered_items)[0] if product_id: - website_warehouse = frappe.get_cached_value( + warehouse = frappe.get_cached_value( "Website Item", {"item_code": product_id}, "website_warehouse" ) available_qty = 0.0 - if website_warehouse: - available_qty = flt( - frappe.db.get_value( - "Bin", {"item_code": product_id, "warehouse": website_warehouse}, "actual_qty" - ) + if warehouse and frappe.get_cached_value("Warehouse", warehouse, "is_group") == 1: + warehouses = get_child_warehouses(warehouse) + else: + warehouses = [warehouse] if warehouse else [] + + for warehouse in warehouses: + available_qty += flt( + frappe.db.get_value("Bin", {"item_code": product_id, "warehouse": warehouse}, "actual_qty") ) return { diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index f7eb859f830..13f484b1c85 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1392,6 +1392,9 @@ def get_default_bom(item_code=None): @frappe.whitelist() def get_valuation_rate(item_code, company, warehouse=None): + if frappe.get_cached_value("Warehouse", warehouse, "is_group"): + return {"valuation_rate": 0.0} + item = get_item_defaults(item_code, company) item_group = get_item_group_defaults(item_code, company) brand = get_brand_defaults(item_code, company) diff --git a/erpnext/templates/generators/item/item_add_to_cart.html b/erpnext/templates/generators/item/item_add_to_cart.html index 1381dfe3b74..9bd3f7514c9 100644 --- a/erpnext/templates/generators/item/item_add_to_cart.html +++ b/erpnext/templates/generators/item/item_add_to_cart.html @@ -49,7 +49,7 @@ {{ _('In stock') }} {% if product_info.show_stock_qty and product_info.stock_qty %} - ({{ product_info.stock_qty[0][0] }}) + ({{ product_info.stock_qty }}) {% endif %} {% endif %} diff --git a/erpnext/templates/pages/wishlist.py b/erpnext/templates/pages/wishlist.py index d70f27c9d9d..17607e45f91 100644 --- a/erpnext/templates/pages/wishlist.py +++ b/erpnext/templates/pages/wishlist.py @@ -25,9 +25,19 @@ def get_context(context): def get_stock_availability(item_code, warehouse): - stock_qty = frappe.utils.flt( - frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "actual_qty") - ) + from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses + + if warehouse and frappe.get_cached_value("Warehouse", warehouse, "is_group") == 1: + warehouses = get_child_warehouses(warehouse) + else: + warehouses = [warehouse] if warehouse else [] + + stock_qty = 0.0 + for warehouse in warehouses: + stock_qty += frappe.utils.flt( + frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "actual_qty") + ) + return bool(stock_qty) diff --git a/erpnext/utilities/product.py b/erpnext/utilities/product.py index afe9654e8ea..e967f7061bb 100644 --- a/erpnext/utilities/product.py +++ b/erpnext/utilities/product.py @@ -6,6 +6,7 @@ from frappe.utils import cint, flt, fmt_money, getdate, nowdate from erpnext.accounts.doctype.pricing_rule.pricing_rule import get_pricing_rule_for_item from erpnext.stock.doctype.batch.batch import get_batch_qty +from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses def get_web_item_qty_in_stock(item_code, item_warehouse_field, warehouse=None): @@ -22,23 +23,31 @@ def get_web_item_qty_in_stock(item_code, item_warehouse_field, warehouse=None): "Website Item", {"item_code": template_item_code}, item_warehouse_field ) - if warehouse: - stock_qty = frappe.db.sql( - """ - select GREATEST(S.actual_qty - S.reserved_qty - S.reserved_qty_for_production - S.reserved_qty_for_sub_contract, 0) / IFNULL(C.conversion_factor, 1) - from tabBin S - inner join `tabItem` I on S.item_code = I.Item_code - left join `tabUOM Conversion Detail` C on I.sales_uom = C.uom and C.parent = I.Item_code - where S.item_code=%s and S.warehouse=%s""", - (item_code, warehouse), - ) + if warehouse and frappe.get_cached_value("Warehouse", warehouse, "is_group") == 1: + warehouses = get_child_warehouses(warehouse) + else: + warehouses = [warehouse] if warehouse else [] - if stock_qty: - stock_qty = adjust_qty_for_expired_items(item_code, stock_qty, warehouse) - in_stock = stock_qty[0][0] > 0 and 1 or 0 + total_stock = 0.0 + if warehouses: + for warehouse in warehouses: + stock_qty = frappe.db.sql( + """ + select GREATEST(S.actual_qty - S.reserved_qty - S.reserved_qty_for_production - S.reserved_qty_for_sub_contract, 0) / IFNULL(C.conversion_factor, 1) + from tabBin S + inner join `tabItem` I on S.item_code = I.Item_code + left join `tabUOM Conversion Detail` C on I.sales_uom = C.uom and C.parent = I.Item_code + where S.item_code=%s and S.warehouse=%s""", + (item_code, warehouse), + ) + + if stock_qty: + total_stock += adjust_qty_for_expired_items(item_code, stock_qty, warehouse) + + in_stock = total_stock > 0 and 1 or 0 return frappe._dict( - {"in_stock": in_stock, "stock_qty": stock_qty, "is_stock_item": is_stock_item} + {"in_stock": in_stock, "stock_qty": total_stock, "is_stock_item": is_stock_item} ) @@ -56,7 +65,7 @@ def adjust_qty_for_expired_items(item_code, stock_qty, warehouse): if not stock_qty[0][0]: break - return stock_qty + return stock_qty[0][0] if stock_qty else 0 def get_expired_batches(batches): From 03e52d385969504564b5079c2551b34dd373292f Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Wed, 20 Sep 2023 14:38:42 +0200 Subject: [PATCH 42/51] chore: resolve conflicts --- erpnext/translations/de.csv | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 6dae398e334..dcba85b4d20 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -1969,12 +1969,8 @@ Please check Multi Currency option to allow accounts with other currency,"Bitte Please click on 'Generate Schedule',"Bitte auf ""Zeitplan generieren"" klicken", Please click on 'Generate Schedule' to fetch Serial No added for Item {0},"Bitte auf ""Zeitplan generieren"" klicken, um die Seriennummer für Artikel {0} abzurufen", Please click on 'Generate Schedule' to get schedule,"Bitte auf ""Zeitplan generieren"" klicken, um den Zeitplan zu erhalten", -<<<<<<< HEAD Please confirm once you have completed your training,"Bitte bestätigen Sie, sobald Sie Ihre Ausbildung abgeschlossen haben", -Please create purchase receipt or purchase invoice for the item {0},Bitte erstellen Sie eine Kaufquittung oder eine Kaufrechnung für den Artikel {0}, -======= Please create purchase receipt or purchase invoice for the item {0},Bitte erstellen Sie eine Kaufquittung oder eine Eingangsrechnungen für den Artikel {0}, ->>>>>>> 84a9000db2 (fix: german translation of Sales and Purchase Invoice (#37122)) Please define grade for Threshold 0%,Bitte definieren Sie Grade for Threshold 0%, Please enable Applicable on Booking Actual Expenses,Bitte aktivieren Sie Anwendbar bei der Buchung von tatsächlichen Ausgaben, Please enable Applicable on Purchase Order and Applicable on Booking Actual Expenses,Bitte aktivieren Sie Anwendbar bei Bestellung und Anwendbar bei Buchung von tatsächlichen Ausgaben, From f2bcfb5f977927cad9f4288b667bbce4d231c6c8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 19:21:54 +0530 Subject: [PATCH 43/51] fix: labels for `Stock Ledger Invariant Check` report (backport #37150) (#37176) fix: labels for `Stock Ledger Invariant Check` report (#37150) refactor: `Stock Ledger Invariant Check` report (cherry picked from commit f0859ecc60a8a08f547bf124f222ef5cd3282502) Co-authored-by: s-aga-r --- .../stock_ledger_invariant_check.js | 4 ++-- .../stock_ledger_invariant_check.py | 22 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js index 74849058c3c..94e0b2dce3b 100644 --- a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.js @@ -2,7 +2,7 @@ // For license information, please see license.txt /* eslint-disable */ -const DIFFERNCE_FIELD_NAMES = [ +const DIFFERENCE_FIELD_NAMES = [ 'difference_in_qty', 'fifo_qty_diff', 'fifo_value_diff', @@ -37,7 +37,7 @@ frappe.query_reports['Stock Ledger Invariant Check'] = { formatter (value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); - if (DIFFERNCE_FIELD_NAMES.includes(column.fieldname) && Math.abs(data[column.fieldname]) > 0.001) { + if (DIFFERENCE_FIELD_NAMES.includes(column.fieldname) && Math.abs(data[column.fieldname]) > 0.001) { value = '' + value + ''; } return value; diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py index e11c9bb7891..ca15afe444d 100644 --- a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py @@ -186,7 +186,7 @@ def get_columns(): { "fieldname": "fifo_queue_qty", "fieldtype": "Float", - "label": _("(C) Total qty in queue"), + "label": _("(C) Total Qty in Queue"), }, { "fieldname": "fifo_qty_diff", @@ -211,52 +211,52 @@ def get_columns(): { "fieldname": "stock_value_difference", "fieldtype": "Float", - "label": _("(F) Stock Value Difference"), + "label": _("(F) Change in Stock Value"), }, { "fieldname": "stock_value_from_diff", "fieldtype": "Float", - "label": _("Balance Stock Value using (F)"), + "label": _("(G) Sum of Change in Stock Value"), }, { "fieldname": "diff_value_diff", "fieldtype": "Float", - "label": _("K - D"), + "label": _("G - D"), }, { "fieldname": "fifo_stock_diff", "fieldtype": "Float", - "label": _("(G) Stock Value difference (FIFO queue)"), + "label": _("(H) Change in Stock Value (FIFO Queue)"), }, { "fieldname": "fifo_difference_diff", "fieldtype": "Float", - "label": _("F - G"), + "label": _("H - F"), }, { "fieldname": "valuation_rate", "fieldtype": "Float", - "label": _("(H) Valuation Rate"), + "label": _("(I) Valuation Rate"), }, { "fieldname": "fifo_valuation_rate", "fieldtype": "Float", - "label": _("(I) Valuation Rate as per FIFO"), + "label": _("(J) Valuation Rate as per FIFO"), }, { "fieldname": "fifo_valuation_diff", "fieldtype": "Float", - "label": _("H - I"), + "label": _("I - J"), }, { "fieldname": "balance_value_by_qty", "fieldtype": "Float", - "label": _("(J) Valuation = Value (D) ÷ Qty (A)"), + "label": _("(K) Valuation = Value (D) ÷ Qty (A)"), }, { "fieldname": "valuation_diff", "fieldtype": "Float", - "label": _("H - J"), + "label": _("I - K"), }, ] From 02fc67c83c94b6277e4e6dafd22381ed04da0ad6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 21 Sep 2023 07:49:02 +0000 Subject: [PATCH 44/51] feat: `Stock Ledger Variance` report (backport #37165) (#37183) feat: `Stock Ledger Variance` report (#37165) * feat: `Stock Ledger Variance` report * refactor: `get_data()` (cherry picked from commit acda72d6165aa395fedebf9522aa0adf45c25fa2) Co-authored-by: s-aga-r --- .../report/stock_ledger_variance/__init__.py | 0 .../stock_ledger_variance.js | 101 +++++++ .../stock_ledger_variance.json | 22 ++ .../stock_ledger_variance.py | 279 ++++++++++++++++++ 4 files changed, 402 insertions(+) create mode 100644 erpnext/stock/report/stock_ledger_variance/__init__.py create mode 100644 erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js create mode 100644 erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.json create mode 100644 erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py diff --git a/erpnext/stock/report/stock_ledger_variance/__init__.py b/erpnext/stock/report/stock_ledger_variance/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js new file mode 100644 index 00000000000..b1e4a74571e --- /dev/null +++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js @@ -0,0 +1,101 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +const DIFFERENCE_FIELD_NAMES = [ + "difference_in_qty", + "fifo_qty_diff", + "fifo_value_diff", + "fifo_valuation_diff", + "valuation_diff", + "fifo_difference_diff", + "diff_value_diff" +]; + +frappe.query_reports["Stock Ledger Variance"] = { + "filters": [ + { + "fieldname": "item_code", + "fieldtype": "Link", + "label": "Item", + "options": "Item", + get_query: function() { + return { + filters: {is_stock_item: 1, has_serial_no: 0} + } + } + }, + { + "fieldname": "warehouse", + "fieldtype": "Link", + "label": "Warehouse", + "options": "Warehouse", + get_query: function() { + return { + filters: {is_group: 0, disabled: 0} + } + } + }, + { + "fieldname": "difference_in", + "fieldtype": "Select", + "label": "Difference In", + "options": [ + "", + "Qty", + "Value", + "Valuation", + ], + }, + { + "fieldname": "include_disabled", + "fieldtype": "Check", + "label": "Include Disabled", + } + ], + + formatter (value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + + if (DIFFERENCE_FIELD_NAMES.includes(column.fieldname) && Math.abs(data[column.fieldname]) > 0.001) { + value = "" + value + ""; + } + + return value; + }, + + get_datatable_options(options) { + return Object.assign(options, { + checkboxColumn: true, + }); + }, + + onload(report) { + report.page.add_inner_button(__('Create Reposting Entries'), () => { + let message = ` +
+

+ Reposting Entries will change the value of + accounts Stock In Hand, and Stock Expenses + in the Trial Balance report and will also change + the Balance Value in the Stock Balance report. +

+

Are you sure you want to create Reposting Entries?

+
`; + let indexes = frappe.query_report.datatable.rowmanager.getCheckedRows(); + let selected_rows = indexes.map(i => frappe.query_report.data[i]); + + if (!selected_rows.length) { + frappe.throw(__("Please select rows to create Reposting Entries")); + } + + frappe.confirm(__(message), () => { + frappe.call({ + method: 'erpnext.stock.report.stock_ledger_invariant_check.stock_ledger_invariant_check.create_reposting_entries', + args: { + rows: selected_rows, + } + }); + }); + }); + }, +}; diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.json b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.json new file mode 100644 index 00000000000..f36ed1b9ca6 --- /dev/null +++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.json @@ -0,0 +1,22 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2023-09-20 10:44:19.414449", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "letterhead": null, + "modified": "2023-09-20 10:44:19.414449", + "modified_by": "Administrator", + "module": "Stock", + "name": "Stock Ledger Variance", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Stock Ledger Entry", + "report_name": "Stock Ledger Variance", + "report_type": "Script Report", + "roles": [] +} \ No newline at end of file diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py new file mode 100644 index 00000000000..732f108ac41 --- /dev/null +++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py @@ -0,0 +1,279 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.utils import cint, flt + +from erpnext.stock.report.stock_ledger_invariant_check.stock_ledger_invariant_check import ( + get_data as stock_ledger_invariant_check, +) + + +def execute(filters=None): + columns, data = [], [] + + filters = frappe._dict(filters or {}) + columns = get_columns() + data = get_data(filters) + + return columns, data + + +def get_columns(): + return [ + { + "fieldname": "name", + "fieldtype": "Link", + "label": _("Stock Ledger Entry"), + "options": "Stock Ledger Entry", + }, + { + "fieldname": "posting_date", + "fieldtype": "Data", + "label": _("Posting Date"), + }, + { + "fieldname": "posting_time", + "fieldtype": "Data", + "label": _("Posting Time"), + }, + { + "fieldname": "creation", + "fieldtype": "Data", + "label": _("Creation"), + }, + { + "fieldname": "item_code", + "fieldtype": "Link", + "label": _("Item"), + "options": "Item", + }, + { + "fieldname": "warehouse", + "fieldtype": "Link", + "label": _("Warehouse"), + "options": "Warehouse", + }, + { + "fieldname": "voucher_type", + "fieldtype": "Link", + "label": _("Voucher Type"), + "options": "DocType", + }, + { + "fieldname": "voucher_no", + "fieldtype": "Dynamic Link", + "label": _("Voucher No"), + "options": "voucher_type", + }, + { + "fieldname": "batch_no", + "fieldtype": "Link", + "label": _("Batch"), + "options": "Batch", + }, + { + "fieldname": "use_batchwise_valuation", + "fieldtype": "Check", + "label": _("Batchwise Valuation"), + }, + { + "fieldname": "actual_qty", + "fieldtype": "Float", + "label": _("Qty Change"), + }, + { + "fieldname": "incoming_rate", + "fieldtype": "Float", + "label": _("Incoming Rate"), + }, + { + "fieldname": "consumption_rate", + "fieldtype": "Float", + "label": _("Consumption Rate"), + }, + { + "fieldname": "qty_after_transaction", + "fieldtype": "Float", + "label": _("(A) Qty After Transaction"), + }, + { + "fieldname": "expected_qty_after_transaction", + "fieldtype": "Float", + "label": _("(B) Expected Qty After Transaction"), + }, + { + "fieldname": "difference_in_qty", + "fieldtype": "Float", + "label": _("A - B"), + }, + { + "fieldname": "stock_queue", + "fieldtype": "Data", + "label": _("FIFO/LIFO Queue"), + }, + { + "fieldname": "fifo_queue_qty", + "fieldtype": "Float", + "label": _("(C) Total Qty in Queue"), + }, + { + "fieldname": "fifo_qty_diff", + "fieldtype": "Float", + "label": _("A - C"), + }, + { + "fieldname": "stock_value", + "fieldtype": "Float", + "label": _("(D) Balance Stock Value"), + }, + { + "fieldname": "fifo_stock_value", + "fieldtype": "Float", + "label": _("(E) Balance Stock Value in Queue"), + }, + { + "fieldname": "fifo_value_diff", + "fieldtype": "Float", + "label": _("D - E"), + }, + { + "fieldname": "stock_value_difference", + "fieldtype": "Float", + "label": _("(F) Change in Stock Value"), + }, + { + "fieldname": "stock_value_from_diff", + "fieldtype": "Float", + "label": _("(G) Sum of Change in Stock Value"), + }, + { + "fieldname": "diff_value_diff", + "fieldtype": "Float", + "label": _("G - D"), + }, + { + "fieldname": "fifo_stock_diff", + "fieldtype": "Float", + "label": _("(H) Change in Stock Value (FIFO Queue)"), + }, + { + "fieldname": "fifo_difference_diff", + "fieldtype": "Float", + "label": _("H - F"), + }, + { + "fieldname": "valuation_rate", + "fieldtype": "Float", + "label": _("(I) Valuation Rate"), + }, + { + "fieldname": "fifo_valuation_rate", + "fieldtype": "Float", + "label": _("(J) Valuation Rate as per FIFO"), + }, + { + "fieldname": "fifo_valuation_diff", + "fieldtype": "Float", + "label": _("I - J"), + }, + { + "fieldname": "balance_value_by_qty", + "fieldtype": "Float", + "label": _("(K) Valuation = Value (D) ÷ Qty (A)"), + }, + { + "fieldname": "valuation_diff", + "fieldtype": "Float", + "label": _("I - K"), + }, + ] + + +def get_data(filters=None): + filters = frappe._dict(filters or {}) + item_warehouse_map = get_item_warehouse_combinations(filters) + + data = [] + if item_warehouse_map: + precision = cint(frappe.db.get_single_value("System Settings", "float_precision")) + + for item_warehouse in item_warehouse_map: + report_data = stock_ledger_invariant_check(item_warehouse) + + if not report_data: + continue + + for row in report_data: + if has_difference(row, precision, filters.difference_in): + data.append(add_item_warehouse_details(row, item_warehouse)) + break + + return data + + +def get_item_warehouse_combinations(filters: dict = None) -> dict: + filters = frappe._dict(filters or {}) + + bin = frappe.qb.DocType("Bin") + item = frappe.qb.DocType("Item") + warehouse = frappe.qb.DocType("Warehouse") + + query = ( + frappe.qb.from_(bin) + .inner_join(item) + .on(bin.item_code == item.name) + .inner_join(warehouse) + .on(bin.warehouse == warehouse.name) + .select( + bin.item_code, + bin.warehouse, + ) + .where((item.is_stock_item == 1) & (item.has_serial_no == 0) & (warehouse.is_group == 0)) + ) + + if filters.item_code: + query = query.where(item.name == filters.item_code) + if filters.warehouse: + query = query.where(warehouse.name == filters.warehouse) + if not filters.include_disabled: + query = query.where((item.disabled == 0) & (warehouse.disabled == 0)) + + return query.run(as_dict=1) + + +def has_difference(row, precision, difference_in): + has_qty_difference = flt(row.difference_in_qty, precision) or flt(row.fifo_qty_diff, precision) + has_value_difference = ( + flt(row.diff_value_diff, precision) + or flt(row.fifo_value_diff, precision) + or flt(row.fifo_difference_diff, precision) + ) + has_valuation_difference = flt(row.valuation_diff, precision) or flt( + row.fifo_valuation_diff, precision + ) + + if difference_in == "Qty" and has_qty_difference: + return True + elif difference_in == "Value" and has_value_difference: + return True + elif difference_in == "Valuation" and has_valuation_difference: + return True + elif difference_in not in ["Qty", "Value", "Valuation"] and ( + has_qty_difference or has_value_difference or has_valuation_difference + ): + return True + + return False + + +def add_item_warehouse_details(row, item_warehouse): + row.update( + { + "item_code": item_warehouse.item_code, + "warehouse": item_warehouse.warehouse, + } + ) + + return row From 1181dcf521b6d5bc6f96a217f6991e0d5cb138d3 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 13 Sep 2023 14:24:56 +0530 Subject: [PATCH 45/51] fix: Update `advance_paid` in SO/PO after unlinking from advance entry (cherry picked from commit 426350eee6efdfeddf300bbeccd7674f0a6d7b9b) --- erpnext/accounts/utils.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 8f0ef869ad3..971932e415a 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -560,6 +560,10 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): """ jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0] + # Update Advance Paid in SO/PO since they might be getting unlinked + if jv_detail.get("reference_type") in ("Sales Order", "Purchase Order"): + frappe.get_doc(jv_detail.reference_type, jv_detail.reference_name).set_total_advance_paid() + if flt(d["unadjusted_amount"]) - flt(d["allocated_amount"]) != 0: # adjust the unreconciled balance amount_in_account_currency = flt(d["unadjusted_amount"]) - flt(d["allocated_amount"]) @@ -625,6 +629,13 @@ def update_reference_in_payment_entry( if d.voucher_detail_no: existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0] + + # Update Advance Paid in SO/PO since they are getting unlinked + if existing_row.get("reference_doctype") in ("Sales Order", "Purchase Order"): + frappe.get_doc( + existing_row.reference_doctype, existing_row.reference_name + ).set_total_advance_paid() + original_row = existing_row.as_dict().copy() existing_row.update(reference_details) From 591c720e5165019672faacb9369c9da8087dfbbe Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 13 Sep 2023 19:09:03 +0530 Subject: [PATCH 46/51] test: Impact on SO of advance PE submit and unlinking/replacement by SI (cherry picked from commit 8a4954d713a31a60581414be38ca90c1fad3c794) --- .../sales_invoice/test_sales_invoice.py | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 378be113e7c..e0a7ff002bb 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1801,6 +1801,10 @@ class TestSalesInvoice(unittest.TestCase): ) def test_outstanding_amount_after_advance_payment_entry_cancellation(self): + """Test impact of advance PE submission/cancellation on SI and SO.""" + from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order + + sales_order = make_sales_order(item_code="138-CMS Shoe", qty=1, price_list_rate=500) pe = frappe.get_doc( { "doctype": "Payment Entry", @@ -1820,10 +1824,25 @@ class TestSalesInvoice(unittest.TestCase): "paid_to": "_Test Cash - _TC", } ) + pe.append( + "references", + { + "reference_doctype": "Sales Order", + "reference_name": sales_order.name, + "total_amount": sales_order.grand_total, + "outstanding_amount": sales_order.grand_total, + "allocated_amount": 300, + }, + ) pe.insert() pe.submit() + sales_order.reload() + self.assertEqual(sales_order.advance_paid, 300) + si = frappe.copy_doc(test_records[0]) + si.items[0].sales_order = sales_order.name + si.items[0].so_detail = sales_order.get("items")[0].name si.is_pos = 0 si.append( "advances", @@ -1831,6 +1850,7 @@ class TestSalesInvoice(unittest.TestCase): "doctype": "Sales Invoice Advance", "reference_type": "Payment Entry", "reference_name": pe.name, + "reference_row": pe.references[0].name, "advance_amount": 300, "allocated_amount": 300, "remarks": pe.remarks, @@ -1839,7 +1859,13 @@ class TestSalesInvoice(unittest.TestCase): si.insert() si.submit() - si.load_from_db() + si.reload() + pe.reload() + sales_order.reload() + + # Check if SO is unlinked/replaced by SI in PE & if SO advance paid is 0 + self.assertEqual(pe.references[0].reference_name, si.name) + self.assertEqual(sales_order.advance_paid, 0.0) # check outstanding after advance allocation self.assertEqual( @@ -1847,11 +1873,9 @@ class TestSalesInvoice(unittest.TestCase): flt(si.rounded_total - si.total_advance, si.precision("outstanding_amount")), ) - # added to avoid Document has been modified exception - pe = frappe.get_doc("Payment Entry", pe.name) pe.cancel() + si.reload() - si.load_from_db() # check outstanding after advance cancellation self.assertEqual( flt(si.outstanding_amount), From 9d5fce9091c2e0bd9f32ebf4735f4fefb69aab3b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 22 Sep 2023 12:21:57 +0530 Subject: [PATCH 47/51] refactor: ignore PLE's on PCV cancellation (cherry picked from commit 301092dad1020634986dfa744a279b2a793ff30f) --- .../doctype/period_closing_voucher/period_closing_voucher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index af1c06643a1..d984d86af25 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -33,7 +33,7 @@ class PeriodClosingVoucher(AccountsController): def on_cancel(self): self.validate_future_closing_vouchers() self.db_set("gle_processing_status", "In Progress") - self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") + self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry") gle_count = frappe.db.count( "GL Entry", {"voucher_type": "Period Closing Voucher", "voucher_no": self.name, "is_cancelled": 0}, From b4bc44db4a8d459de02ee80b0516f20913897b95 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 17:50:45 +0530 Subject: [PATCH 48/51] fix: apply gl report filters (cherry picked from commit 5346c67b020fe2ae1bdf01399aa7bf6e57dedaeb) --- .../process_statement_of_accounts.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index e1f32952205..c622d308995 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -64,6 +64,7 @@ def get_report_pdf(doc, consolidated=True): filters = get_common_filters(doc) if doc.report == "General Ledger": + filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency)) col, res = get_soa(filters) for x in [0, -2, -1]: res[x]["account"] = res[x]["account"].replace("'", "") From 57c82c18008ace3b95ccf984be06bb60ec51fe66 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 23 Sep 2023 14:34:11 +0530 Subject: [PATCH 49/51] feat: Toggle net values in Trial Balance report (cherry picked from commit 06a45897def2595916c93e4b15ded624011adecb) --- .../accounts/report/trial_balance/trial_balance.js | 6 ++++++ .../accounts/report/trial_balance/trial_balance.py | 11 +++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js index e45c3adcb6d..6e233c802f0 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.js +++ b/erpnext/accounts/report/trial_balance/trial_balance.js @@ -99,6 +99,12 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "label": __("Include Default Book Entries"), "fieldtype": "Check", "default": 1 + }, + { + "fieldname": "show_net_values", + "label": __("Show net values in opening and closing columns"), + "fieldtype": "Check", + "default": 1 } ], "formatter": erpnext.financial_statements.formatter, diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 376571f0346..2a8aa0c202f 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -120,7 +120,9 @@ def get_data(filters): ignore_opening_entries=True, ) - calculate_values(accounts, gl_entries_by_account, opening_balances) + calculate_values( + accounts, gl_entries_by_account, opening_balances, filters.get("show_net_values") + ) accumulate_values_into_parents(accounts, accounts_by_name) data = prepare_data(accounts, filters, parent_children_map, company_currency) @@ -310,7 +312,7 @@ def get_opening_balance( return gle -def calculate_values(accounts, gl_entries_by_account, opening_balances): +def calculate_values(accounts, gl_entries_by_account, opening_balances, show_net_values): init = { "opening_debit": 0.0, "opening_credit": 0.0, @@ -335,7 +337,8 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances): d["closing_debit"] = d["opening_debit"] + d["debit"] d["closing_credit"] = d["opening_credit"] + d["credit"] - prepare_opening_closing(d) + if show_net_values: + prepare_opening_closing(d) def calculate_total_row(accounts, company_currency): @@ -375,7 +378,7 @@ def prepare_data(accounts, filters, parent_children_map, company_currency): for d in accounts: # Prepare opening closing for group account - if parent_children_map.get(d.account): + if parent_children_map.get(d.account) and filters.get("show_net_values"): prepare_opening_closing(d) has_value = False From e0da8d261f7b0f73f898bc80f51788843cae6051 Mon Sep 17 00:00:00 2001 From: milanpethani Date: Wed, 20 Sep 2023 11:58:46 +0530 Subject: [PATCH 50/51] fix: set customer currency in pos_invoice if exists if currency exists in the profile and customer currency doesn't exists still it will update currency to None, so update customer currency only if exists (cherry picked from commit 041d52e82880610a99903ecf4c459e98bd1fff76) --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 7c013b6abef..d3058e44509 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -518,7 +518,7 @@ class POSInvoice(SalesInvoice): selling_price_list = ( customer_price_list or customer_group_price_list or profile.get("selling_price_list") ) - if customer_currency != profile.get("currency"): + if customer_currency and customer_currency != profile.get("currency"): self.set("currency", customer_currency) else: From 25f800d3f5cfd4a3c4d58b455b54d47a782d0d76 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 24 Sep 2023 22:24:56 +0530 Subject: [PATCH 51/51] fix(Material Request): consider project for item details (backport #37215) (#37221) fix(Material Request): consider project for item details (#37215) fix(Material Request): project in item details (cherry picked from commit 7c4ebe27334b188c9fce225103a1e9ce9b64f40e) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- erpnext/stock/doctype/material_request/material_request.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index b096b024f44..2632501b718 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -218,7 +218,8 @@ frappe.ui.form.on('Material Request', { plc_conversion_rate: 1, rate: item.rate, uom: item.uom, - conversion_factor: item.conversion_factor + conversion_factor: item.conversion_factor, + project: item.project, }, overwrite_warehouse: overwrite_warehouse },